Files
dark-lord/contexts/AccountContext.tsx
drjones 78a071ba02 Harden onion boot flow and deepen site surfaces
Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation.

Made-with: Cursor
2026-04-07 21:35:52 -07:00

119 lines
3.2 KiB
TypeScript

"use client";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from "react";
import {
clearSession,
getPublicProfile,
readSession,
registerAccount,
setSession,
updateDisplayName,
verifyCredentials,
type PublicCredentials,
} from "@/lib/cyberluxAccount";
export type AccountContextValue = {
user: PublicCredentials | null;
hydrated: boolean;
signIn: (username: string, password: string) => Promise<{ ok: true } | { ok: false; error: string }>;
signUp: (
username: string,
password: string,
displayName?: string,
) => Promise<{ ok: true } | { ok: false; error: string }>;
signOut: () => void;
refreshProfile: () => void;
saveDisplayName: (displayName: string) => { ok: true } | { ok: false; error: string };
};
const AccountContext = createContext<AccountContextValue | undefined>(undefined);
export function useAccount(): AccountContextValue {
const ctx = useContext(AccountContext);
if (!ctx) {
throw new Error("useAccount must be used within AccountProvider");
}
return ctx;
}
export function AccountProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<PublicCredentials | null>(null);
const [hydrated, setHydrated] = useState(false);
const refreshProfile = useCallback(() => {
const s = readSession();
if (!s) {
setUser(null);
return;
}
const p = getPublicProfile(s.username);
setUser(p);
}, []);
useEffect(() => {
refreshProfile();
setHydrated(true);
}, [refreshProfile]);
const signIn = useCallback(
async (username: string, password: string) => {
const res = await verifyCredentials(username, password);
if (!res.ok) return res;
setSession(res.profile.username);
setUser(res.profile);
return { ok: true as const };
},
[],
);
const signUp = useCallback(async (username: string, password: string, displayName?: string) => {
const reg = await registerAccount(username, password, displayName);
if (!reg.ok) return reg;
const res = await verifyCredentials(username, password);
if (!res.ok) return { ok: false, error: "Account created but sign-in failed — try again." };
setSession(res.profile.username);
setUser(res.profile);
return { ok: true as const };
}, []);
const signOut = useCallback(() => {
clearSession();
setUser(null);
}, []);
const saveDisplayName = useCallback(
(displayName: string): { ok: true } | { ok: false; error: string } => {
if (!user) return { ok: false, error: "Not signed in." };
const dn = displayName.trim();
if (dn.length < 2) return { ok: false, error: "Display name needs at least 2 characters." };
if (!updateDisplayName(user.username, dn)) return { ok: false, error: "Could not update profile." };
setUser({ ...user, displayName: dn });
return { ok: true };
},
[user],
);
const value = useMemo(
() => ({
user,
hydrated,
signIn,
signUp,
signOut,
refreshProfile,
saveDisplayName,
}),
[user, hydrated, signIn, signUp, signOut, refreshProfile, saveDisplayName],
);
return <AccountContext.Provider value={value}>{children}</AccountContext.Provider>;
}