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:
@@ -0,0 +1,162 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// Notifications API client (client-side)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import { apiFetch } from './client';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Request / response shapes
|
||||
// TODO: import from @/lib/types once the shared types package is finalised
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface NotificationResponse {
|
||||
id: number;
|
||||
title: string;
|
||||
body: string;
|
||||
notification_type: string;
|
||||
is_read: boolean;
|
||||
data?: Record<string, unknown>;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface NotificationListResponse {
|
||||
count: number;
|
||||
results: NotificationResponse[];
|
||||
}
|
||||
|
||||
export interface UnreadCountResponse {
|
||||
unread_count: number;
|
||||
}
|
||||
|
||||
export interface NotificationPreferencesResponse {
|
||||
task_reminders: boolean;
|
||||
task_completions: boolean;
|
||||
residence_updates: boolean;
|
||||
share_notifications: boolean;
|
||||
marketing: boolean;
|
||||
}
|
||||
|
||||
export interface UpdatePreferencesRequest {
|
||||
task_reminders?: boolean;
|
||||
task_completions?: boolean;
|
||||
residence_updates?: boolean;
|
||||
share_notifications?: boolean;
|
||||
marketing?: boolean;
|
||||
}
|
||||
|
||||
export interface RegisterDeviceRequest {
|
||||
registration_id: string;
|
||||
platform: 'ios' | 'android' | 'web';
|
||||
device_name?: string;
|
||||
}
|
||||
|
||||
export interface DeviceResponse {
|
||||
id: number;
|
||||
registration_id: string;
|
||||
platform: string;
|
||||
device_name: string;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface UnregisterDeviceRequest {
|
||||
registration_id: string;
|
||||
platform?: string;
|
||||
}
|
||||
|
||||
export interface MessageResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** List notifications with optional pagination. */
|
||||
export function listNotifications(
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
): Promise<NotificationListResponse> {
|
||||
const params = new URLSearchParams();
|
||||
if (limit != null) params.set('limit', String(limit));
|
||||
if (offset != null) params.set('offset', String(offset));
|
||||
const qs = params.toString();
|
||||
return apiFetch<NotificationListResponse>(
|
||||
`/notifications/${qs ? `?${qs}` : ''}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** Get unread notification count. */
|
||||
export function getUnreadCount(): Promise<UnreadCountResponse> {
|
||||
return apiFetch<UnreadCountResponse>('/notifications/unread-count/');
|
||||
}
|
||||
|
||||
/** Mark a single notification as read. */
|
||||
export function markAsRead(id: number): Promise<MessageResponse> {
|
||||
return apiFetch<MessageResponse>(`/notifications/${id}/read/`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
/** Mark all notifications as read. */
|
||||
export function markAllAsRead(): Promise<MessageResponse> {
|
||||
return apiFetch<MessageResponse>('/notifications/mark-all-read/', {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
/** Get notification preferences. */
|
||||
export function getPreferences(): Promise<NotificationPreferencesResponse> {
|
||||
return apiFetch<NotificationPreferencesResponse>(
|
||||
'/notifications/preferences/',
|
||||
);
|
||||
}
|
||||
|
||||
/** Update notification preferences. */
|
||||
export function updatePreferences(
|
||||
data: UpdatePreferencesRequest,
|
||||
): Promise<NotificationPreferencesResponse> {
|
||||
return apiFetch<NotificationPreferencesResponse>(
|
||||
'/notifications/preferences/',
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** Register a push notification device. */
|
||||
export function registerDevice(
|
||||
data: RegisterDeviceRequest,
|
||||
): Promise<DeviceResponse> {
|
||||
return apiFetch<DeviceResponse>('/notifications/devices/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
/** List registered devices. */
|
||||
export function listDevices(): Promise<DeviceResponse[]> {
|
||||
return apiFetch<DeviceResponse[]>('/notifications/devices/');
|
||||
}
|
||||
|
||||
/** Unregister a push notification device by registration ID. */
|
||||
export function unregisterDevice(
|
||||
data: UnregisterDeviceRequest,
|
||||
): Promise<MessageResponse> {
|
||||
return apiFetch<MessageResponse>('/notifications/devices/unregister/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
/** Delete a device by ID. */
|
||||
export function deleteDevice(
|
||||
id: number,
|
||||
platform?: string,
|
||||
): Promise<MessageResponse> {
|
||||
const qs = platform ? `?platform=${platform}` : '';
|
||||
return apiFetch<MessageResponse>(`/notifications/devices/${id}/${qs}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user