42e7bedea4
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>
178 lines
6.2 KiB
TypeScript
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,
|
|
};
|