Files
honeyDueWeb/src/lib/demo/data-provider.ts
T
Trey t 42e7bedea4 Replace hand-rolled auth with Ory Kratos browser flows
The honeyDue Go API no longer owns identity — Ory Kratos at
NEXT_PUBLIC_KRATOS_URL does. Rewrite the web app's auth layer to use Kratos
browser self-service flows and the ory_kratos_session cookie.

- Kratos client (src/lib/kratos/): flow init/fetch/submit, whoami, logout,
  message helpers, and the useKratosFlow lifecycle hook.
- Generic flow renderer (src/components/auth/): KratosFlowForm renders
  ui.nodes (inputs, oidc social buttons, hidden csrf), KratosMessages
  surfaces flow-level messages, AuthGate guards /app via whoami.
- Auth pages (login/register/forgot-password/verify-email/reset-password)
  rewritten as Kratos login/registration/recovery/verification/settings
  flows. Password change in settings now uses the Kratos settings flow.
- Proxy + serverFetch forward the ory_kratos_session cookie to the Go API
  instead of "Authorization: Token". Deleted /api/auth/{login,logout,me}.
- Middleware does a cheap ory_kratos_session cookie pre-filter; AuthGate's
  whoami call is authoritative.
- auth store rewritten around whoami + GET /auth/me; removed dead auth API
  functions, types/auth, validations/auth, code-input.
- Added NEXT_PUBLIC_KRATOS_URL to config (.env.example) and CLAUDE.md.

npm run build passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:16:49 -05:00

178 lines
6.2 KiB
TypeScript

// ---------------------------------------------------------------------------
// 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>;
/**
* Real mode: drives the Ory Kratos browser logout flow (clears the
* session cookie and navigates the browser). Demo mode: a no-op.
*/
logout(): Promise<void>;
};
}
// Re-export types that hooks/consumers will need
export type {
CreateResidenceRequest,
UpdateResidenceRequest,
CreateTaskRequest,
UpdateTaskRequest,
CreateCompletionRequest,
CreateContractorRequest,
UpdateContractorRequest,
CreateDocumentRequest,
UpdateDocumentRequest,
DocumentListParams,
UpdatePreferencesRequest,
};