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
+76
View File
@@ -0,0 +1,76 @@
import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';
// ---------------------------------------------------------------------------
// POST /api/auth/login
// ---------------------------------------------------------------------------
// Special route handler for login. On success, sets the auth token in an
// httpOnly cookie so it is never exposed to client-side JavaScript.
// ---------------------------------------------------------------------------
const API_BASE_URL =
process.env.API_URL ||
process.env.NEXT_PUBLIC_API_URL ||
'https://mycrib.treytartt.com/api';
const COOKIE_NAME = 'casera-token';
const COOKIE_MAX_AGE = 60 * 60 * 24 * 30; // 30 days
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const upstream = await fetch(`${API_BASE_URL}/auth/login/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timezone':
request.headers.get('x-timezone') ||
Intl.DateTimeFormat().resolvedOptions().timeZone,
},
cache: 'no-store',
body: JSON.stringify(body),
});
const data = await upstream.json().catch(() => null);
if (!upstream.ok) {
return NextResponse.json(
data || { error: 'Login failed' },
{ status: upstream.status },
);
}
// Extract token from Go API response
// The Go API returns { token: "...", user: { ... } }
const token: string | undefined = data?.token;
if (!token) {
return NextResponse.json(
{ error: 'No token in response' },
{ status: 500 },
);
}
// Set httpOnly cookie
const cookieStore = await cookies();
cookieStore.set(COOKIE_NAME, token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
maxAge: COOKIE_MAX_AGE,
});
// Return the full response (including user data) to the client,
// but strip the raw token since it is now in the cookie.
const { token: _stripped, ...safeData } = data;
return NextResponse.json(safeData, { status: 200 });
} catch (error) {
console.error('[auth/login] Error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 },
);
}
}