diff --git a/app/(account)/AccountChrome.tsx b/app/(account)/AccountChrome.tsx new file mode 100644 index 0000000..80e2fcb --- /dev/null +++ b/app/(account)/AccountChrome.tsx @@ -0,0 +1,14 @@ +// app/(account)/AccountChrome.tsx +"use client"; + +import TopNav from "@/components/TopNav"; +import BuilderNav from "@/components/BuilderNav"; + +export default function AccountChrome() { + return ( + <> + + + + ); +} \ No newline at end of file diff --git a/app/(account)/account/page.tsx b/app/(account)/account/page.tsx index df735d8..3492f35 100644 --- a/app/(account)/account/page.tsx +++ b/app/(account)/account/page.tsx @@ -3,41 +3,207 @@ import { useAuth } from "@/context/AuthContext"; +function StatChip({ + label, + value, +}: { + label: string; + value: React.ReactNode; +}) { + return ( +
+
+ {label} +
+
{value}
+
+ ); +} + +function Card({ + title, + description, + children, + badge, +}: { + title: string; + description?: string; + children: React.ReactNode; + badge?: React.ReactNode; +}) { + return ( +
+
+
+

{title}

+ {description ? ( +

{description}

+ ) : null} +
+ {badge ?
{badge}
: null} +
+ +
{children}
+
+ ); +} + +function Row({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + export default function AccountPage() { const { user, loading } = useAuth(); - if (loading) return
Loading…
; + if (loading) return
Loading…
; if (!user) { return ( -
- You’re not logged in. Please log in to view your account. +
+

+ You’re not logged in +

+

+ Please log in to view your account. +

); } + const planLabel = user.role === "ADMIN" ? "Admin" : "Beta Access"; + return ( -
-
-

Profile

-

- Basic account info (we’ll expand this soon). -

+
+ {/* Quick stats (lightweight “premium” feel) */} + {/*
+ + + + +
*/} + +
+ + Read-only (for now) + + } + > +
+ + + +
+
-
-
- Email:{" "} - {user.email} -
-
- Display name:{" "} - {user.displayName || "—"} -
-
- Role:{" "} - {user.role} -
+
+ + Coming soon + + } + > +
+ + + +
+
+
+ +
+ + Coming soon + + } + > +
+
+
2FA
+
+ Not enabled yet (on the roadmap). +
+
+ +
+
+ Active sessions +
+
+ We’ll show devices + allow logout everywhere. +
+
+
+
+
+ +
+ +
+
+
+ Delete account +
+
+ Removes your account and builds. (We’ll add this flow later.) +
+
+ +
+
); diff --git a/app/(account)/layout.tsx b/app/(account)/layout.tsx index a77905a..53d7393 100644 --- a/app/(account)/layout.tsx +++ b/app/(account)/layout.tsx @@ -1,56 +1,57 @@ // app/(account)/layout.tsx import Link from "next/link"; +import AccountChrome from "./AccountChrome"; const nav = [ { href: "/account", label: "My Account" }, { href: "/account/settings", label: "Settings" }, + { href: "/vault", label: "My Vault" }, ]; -export default function AccountLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function AccountLayout({ children }: { children: React.ReactNode }) { return (
-
+ + +
{/* Header */} -
+
-

Account

-

+

+ Account +
+

+ Settings & Profile +

+

Profile, password, and account settings.

- + ← Back to Builder
-
-
); -} \ No newline at end of file +} +export default TopNav; \ No newline at end of file diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx index 48c923a..3d91305 100644 --- a/context/AuthContext.tsx +++ b/context/AuthContext.tsx @@ -30,6 +30,19 @@ type AuthContextValue = { }) => Promise; logout: () => void; getAuthHeaders: () => HeadersInit; + + /** + * Used for non-password auth flows (ex: magic link). + * Persists session exactly like login/register. + */ + setSession: ( + token: string, + user: { + email: string; + displayName: string | null; + role: string; + } + ) => void; }; const AuthContext = createContext(undefined); @@ -42,6 +55,25 @@ export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); + const persistAuth = useCallback( + (nextToken: string | null, nextUser: AuthUser) => { + if (typeof window === "undefined") return; + + if (nextToken) { + window.localStorage.setItem(TOKEN_KEY, nextToken); + } else { + window.localStorage.removeItem(TOKEN_KEY); + } + + if (nextUser) { + window.localStorage.setItem(USER_KEY, JSON.stringify(nextUser)); + } else { + window.localStorage.removeItem(USER_KEY); + } + }, + [] + ); + // Hydrate from localStorage on first load useEffect(() => { if (typeof window === "undefined") return; @@ -49,15 +81,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { const storedToken = window.localStorage.getItem(TOKEN_KEY); const storedUser = window.localStorage.getItem(USER_KEY); - if (storedToken) { - setToken(storedToken); - } + if (storedToken) setToken(storedToken); if (storedUser) { try { setUser(JSON.parse(storedUser)); } catch { - // bad JSON? wipe it window.localStorage.removeItem(USER_KEY); } } @@ -65,21 +94,17 @@ export function AuthProvider({ children }: { children: ReactNode }) { setLoading(false); }, []); - const persistAuth = useCallback((nextToken: string | null, nextUser: AuthUser) => { - if (typeof window === "undefined") return; - - if (nextToken) { - window.localStorage.setItem(TOKEN_KEY, nextToken); - } else { - window.localStorage.removeItem(TOKEN_KEY); - } - - if (nextUser) { - window.localStorage.setItem(USER_KEY, JSON.stringify(nextUser)); - } else { - window.localStorage.removeItem(USER_KEY); - } - }, []); + const setSession = useCallback( + ( + nextToken: string, + nextUser: { email: string; displayName: string | null; role: string } + ) => { + setToken(nextToken); + setUser(nextUser); + persistAuth(nextToken, nextUser); + }, + [persistAuth] + ); const login = useCallback( async ({ email, password }: { email: string; password: string }) => { @@ -98,7 +123,6 @@ export function AuthProvider({ children }: { children: ReactNode }) { const data = await res.json(); - // Adjust these to match your backend response shape const nextToken: string = data.token ?? data.accessToken; const nextUser: AuthUser = data.user ?? @@ -108,14 +132,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { role: data.role ?? "USER", } as AuthUser); - setToken(nextToken); - setUser(nextUser); - persistAuth(nextToken, nextUser); + setSession(nextToken, nextUser as NonNullable); } finally { setLoading(false); } }, - [persistAuth] + [setSession] ); const register = useCallback( @@ -152,14 +174,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { role: data.role ?? "USER", } as AuthUser); - setToken(nextToken); - setUser(nextUser); - persistAuth(nextToken, nextUser); + setSession(nextToken, nextUser as NonNullable); } finally { setLoading(false); } }, - [persistAuth] + [setSession] ); const logout = useCallback(() => { @@ -180,12 +200,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { register, logout, getAuthHeaders, + setSession, // ✅ important }; return {children}; } -// 🔑 This is what your useApi hook imports export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) {