feat: Phase 4-5 — demo mode, polish, deploy, and bug fixes
Add demo mode with mock data provider, Docker deployment, Playwright tests, PostHog analytics, error boundaries, and SEO metadata. Fix residences API response unwrapping, kanban drag-and-drop with optimistic updates, trailing slash proxy redirects, and column name mismatches with Go API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataProvider interface — abstraction over real API vs. in-memory demo store
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types are imported from the API modules (the actual source of truth for hooks).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import type {
|
||||
CreateResidenceRequest,
|
||||
UpdateResidenceRequest,
|
||||
ResidenceResponse,
|
||||
MyResidenceResponse,
|
||||
ResidenceSummaryResponse,
|
||||
ShareCodeResponse,
|
||||
SharePackageResponse,
|
||||
GenerateShareCodeRequest,
|
||||
ResidenceUserResponse,
|
||||
TasksReportResponse,
|
||||
MessageResponse as ResidenceMessageResponse,
|
||||
} from '@/lib/api/residences';
|
||||
|
||||
import type {
|
||||
CreateTaskRequest,
|
||||
UpdateTaskRequest,
|
||||
TaskResponse,
|
||||
KanbanResponse,
|
||||
CompletionResponse,
|
||||
CreateCompletionRequest,
|
||||
MessageResponse as TaskMessageResponse,
|
||||
} from '@/lib/api/tasks';
|
||||
|
||||
import type {
|
||||
CreateContractorRequest,
|
||||
UpdateContractorRequest,
|
||||
ContractorResponse,
|
||||
ToggleFavoriteResponse,
|
||||
ContractorTaskResponse,
|
||||
} from '@/lib/api/contractors';
|
||||
|
||||
import type {
|
||||
DocumentListParams,
|
||||
CreateDocumentRequest,
|
||||
UpdateDocumentRequest,
|
||||
DocumentResponse,
|
||||
MessageResponse as DocMessageResponse,
|
||||
} from '@/lib/api/documents';
|
||||
|
||||
import type { StaticDataResponse } from '@/lib/api/lookups';
|
||||
|
||||
import type {
|
||||
NotificationListResponse,
|
||||
UnreadCountResponse,
|
||||
NotificationPreferencesResponse,
|
||||
UpdatePreferencesRequest,
|
||||
} from '@/lib/api/notifications';
|
||||
|
||||
import type {
|
||||
SubscriptionStatusResponse,
|
||||
FeatureBenefitResponse,
|
||||
UpgradeTriggerResponse,
|
||||
} from '@/lib/api/subscription';
|
||||
|
||||
import type { UserResponse } from '@/lib/api/auth';
|
||||
|
||||
// Unified MessageResponse (all API modules define the same shape)
|
||||
type MessageResponse = ResidenceMessageResponse | TaskMessageResponse | DocMessageResponse;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Domain-split interface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface DataProvider {
|
||||
basePath: string;
|
||||
|
||||
residences: {
|
||||
list(): Promise<ResidenceResponse[]>;
|
||||
get(id: number): Promise<ResidenceResponse>;
|
||||
create(data: CreateResidenceRequest): Promise<ResidenceResponse>;
|
||||
update(id: number, data: UpdateResidenceRequest): Promise<ResidenceResponse>;
|
||||
delete(id: number): Promise<MessageResponse>;
|
||||
getMyResidences(): Promise<MyResidenceResponse[]>;
|
||||
getSummary(): Promise<ResidenceSummaryResponse>;
|
||||
};
|
||||
|
||||
tasks: {
|
||||
list(days?: number): Promise<KanbanResponse>;
|
||||
get(id: number): Promise<TaskResponse>;
|
||||
create(data: CreateTaskRequest): Promise<TaskResponse>;
|
||||
update(id: number, data: UpdateTaskRequest): Promise<TaskResponse>;
|
||||
delete(id: number): Promise<MessageResponse>;
|
||||
getByResidence(residenceId: number, days?: number): Promise<KanbanResponse>;
|
||||
getCompletions(taskId: number): Promise<CompletionResponse[]>;
|
||||
createCompletion(data: CreateCompletionRequest): Promise<CompletionResponse>;
|
||||
createCompletionWithImages(
|
||||
data: { task_id: number; notes?: string; actual_cost?: number; completed_at?: string },
|
||||
images: File[],
|
||||
): Promise<CompletionResponse>;
|
||||
markInProgress(id: number): Promise<TaskResponse>;
|
||||
cancel(id: number): Promise<TaskResponse>;
|
||||
uncancel(id: number): Promise<TaskResponse>;
|
||||
archive(id: number): Promise<TaskResponse>;
|
||||
unarchive(id: number): Promise<TaskResponse>;
|
||||
quickComplete(id: number): Promise<void>;
|
||||
};
|
||||
|
||||
contractors: {
|
||||
list(): Promise<ContractorResponse[]>;
|
||||
get(id: number): Promise<ContractorResponse>;
|
||||
create(data: CreateContractorRequest): Promise<ContractorResponse>;
|
||||
update(id: number, data: UpdateContractorRequest): Promise<ContractorResponse>;
|
||||
delete(id: number): Promise<MessageResponse>;
|
||||
toggleFavorite(id: number): Promise<ToggleFavoriteResponse>;
|
||||
getTasks(id: number): Promise<ContractorTaskResponse[]>;
|
||||
};
|
||||
|
||||
documents: {
|
||||
list(params?: DocumentListParams): Promise<DocumentResponse[]>;
|
||||
listWarranties(): Promise<DocumentResponse[]>;
|
||||
get(id: number): Promise<DocumentResponse>;
|
||||
create(data: CreateDocumentRequest): Promise<DocumentResponse>;
|
||||
createWithFile(data: CreateDocumentRequest, file: File): Promise<DocumentResponse>;
|
||||
update(id: number, data: UpdateDocumentRequest): Promise<DocumentResponse>;
|
||||
delete(id: number): Promise<MessageResponse>;
|
||||
};
|
||||
|
||||
lookups: {
|
||||
getStaticData(): Promise<StaticDataResponse>;
|
||||
};
|
||||
|
||||
sharing: {
|
||||
getShareCode(residenceId: number): Promise<{ share_code: ShareCodeResponse | null }>;
|
||||
generateShareCode(residenceId: number, data?: GenerateShareCodeRequest): Promise<ShareCodeResponse>;
|
||||
generateSharePackage(residenceId: number, data?: GenerateShareCodeRequest): Promise<SharePackageResponse>;
|
||||
getResidenceUsers(residenceId: number): Promise<ResidenceUserResponse[]>;
|
||||
removeUser(residenceId: number, userId: number): Promise<MessageResponse>;
|
||||
joinWithCode(data: { code: string }): Promise<ResidenceResponse>;
|
||||
generateTasksReport(residenceId: number, email?: string): Promise<TasksReportResponse>;
|
||||
};
|
||||
|
||||
notifications: {
|
||||
list(limit?: number, offset?: number): Promise<NotificationListResponse>;
|
||||
getUnreadCount(): Promise<UnreadCountResponse>;
|
||||
getPreferences(): Promise<NotificationPreferencesResponse>;
|
||||
updatePreferences(data: UpdatePreferencesRequest): Promise<NotificationPreferencesResponse>;
|
||||
markAsRead(id: number): Promise<MessageResponse>;
|
||||
markAllAsRead(): Promise<MessageResponse>;
|
||||
};
|
||||
|
||||
subscription: {
|
||||
getStatus(): Promise<SubscriptionStatusResponse>;
|
||||
getFeatureBenefits(): Promise<FeatureBenefitResponse[]>;
|
||||
getUpgradeTriggers(): Promise<UpgradeTriggerResponse[]>;
|
||||
};
|
||||
|
||||
auth: {
|
||||
getCurrentUser(): Promise<UserResponse>;
|
||||
logout(): Promise<MessageResponse>;
|
||||
};
|
||||
}
|
||||
|
||||
// Re-export types that hooks/consumers will need
|
||||
export type {
|
||||
CreateResidenceRequest,
|
||||
UpdateResidenceRequest,
|
||||
CreateTaskRequest,
|
||||
UpdateTaskRequest,
|
||||
CreateCompletionRequest,
|
||||
CreateContractorRequest,
|
||||
UpdateContractorRequest,
|
||||
CreateDocumentRequest,
|
||||
UpdateDocumentRequest,
|
||||
DocumentListParams,
|
||||
UpdatePreferencesRequest,
|
||||
};
|
||||
Reference in New Issue
Block a user