feat: complete Phase 3 — advanced features for Casera web app

Adds sharing (residence share codes, join, user management, .casera file
export/import), subscription status with feature comparison, notification
preferences with bell icon, profile settings (edit info, change password,
theme picker, delete account), onboarding wizard with create/join paths,
enhanced dashboard with stats cards, Recharts completion chart, recent
activity feed, and task report PDF download.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-03 09:31:29 -06:00
commit 5a50d77515
183 changed files with 34450 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
"use client";
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as tasksApi from '@/lib/api/tasks';
import type { CreateTaskRequest, UpdateTaskRequest, CreateCompletionRequest } from '@/lib/api/tasks';
// ---------------------------------------------------------------------------
// Query hooks
// ---------------------------------------------------------------------------
export function useTasks() {
return useQuery({
queryKey: ['tasks'],
queryFn: () => tasksApi.listTasks(),
});
}
export function useTasksByResidence(residenceId: number) {
return useQuery({
queryKey: ['tasks', 'by-residence', residenceId],
queryFn: () => tasksApi.getTasksByResidence(residenceId),
enabled: !!residenceId,
});
}
export function useTask(id: number) {
return useQuery({
queryKey: ['tasks', id],
queryFn: () => tasksApi.getTask(id),
enabled: !!id,
});
}
export function useKanbanBoard(residenceId: number) {
return useQuery({
queryKey: ['tasks', 'kanban', residenceId],
queryFn: () => tasksApi.getTasksByResidence(residenceId),
enabled: !!residenceId,
});
}
export function useTaskCompletions(taskId: number) {
return useQuery({
queryKey: ['tasks', taskId, 'completions'],
queryFn: () => tasksApi.getTaskCompletions(taskId),
enabled: !!taskId,
});
}
// ---------------------------------------------------------------------------
// Mutation hooks
// ---------------------------------------------------------------------------
export function useCreateTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateTaskRequest) => tasksApi.createTask(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
queryClient.invalidateQueries({ queryKey: ['residences'] });
},
});
}
export function useUpdateTask(id: number) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: UpdateTaskRequest) => tasksApi.updateTask(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
queryClient.invalidateQueries({ queryKey: ['tasks', id] });
queryClient.invalidateQueries({ queryKey: ['residences'] });
},
});
}
export function useDeleteTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: number) => tasksApi.deleteTask(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
queryClient.invalidateQueries({ queryKey: ['residences'] });
},
});
}
export function useMarkInProgress() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: number) => tasksApi.markInProgress(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
},
});
}
export function useCancelTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: number) => tasksApi.cancelTask(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
},
});
}
export function useArchiveTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: number) => tasksApi.archiveTask(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
},
});
}
export function useCreateCompletion() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
data,
images,
}: {
data: CreateCompletionRequest;
images: File[];
}) => {
if (images.length > 0) {
return tasksApi.createCompletionWithImages(
{
task_id: data.task_id,
notes: data.notes,
actual_cost: data.actual_cost,
completed_at: data.completed_at,
},
images,
);
}
return tasksApi.createCompletion(data);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
queryClient.invalidateQueries({ queryKey: ['residences'] });
},
});
}