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
119 lines
3.2 KiB
TypeScript
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>;
|
|
}
|