"use client"; import React, { createContext, useContext, useEffect, useState, ReactNode, useCallback, } from "react"; import { useAuth } from "@/contexts/AuthContext"; import { type ApiKeyState, type ApiKeyProvider, type UserProfile as ApiUserProfile, getUserProfile, isMfaRequiredError, saveApiKey, updateUserMfaOnLogin, updateUserProfile, } from "@/app/lib/mikeApi"; interface UserProfile { displayName: string | null; organisation: string | null; messageCreditsUsed: number; creditsResetDate: string; creditsRemaining: number; tier: string; titleModel: string; tabularModel: string; mfaOnLogin: boolean; apiKeys: ApiKeyState; } interface UserProfileContextType { profile: UserProfile | null; loading: boolean; updateDisplayName: (name: string) => Promise; updateOrganisation: (organisation: string) => Promise; updateModelPreference: ( field: "titleModel" | "tabularModel", value: string, ) => Promise; updateMfaOnLogin: (enabled: boolean) => Promise; updateApiKey: ( provider: ApiKeyProvider, value: string | null, ) => Promise; reloadProfile: () => Promise; incrementMessageCredits: () => Promise; } const UserProfileContext = createContext( undefined, ); const API_KEY_PROVIDERS: ApiKeyProvider[] = [ "claude", "gemini", "openai", "openrouter", "courtlistener", ]; function emptyApiKeys(): ApiKeyState { return { claude: { configured: false, source: null }, gemini: { configured: false, source: null }, openai: { configured: false, source: null }, openrouter: { configured: false, source: null }, courtlistener: { configured: false, source: null }, }; } function toProfile(data: ApiUserProfile): UserProfile { const { apiKeyStatus, ...profile } = data; const apiKeys = emptyApiKeys(); for (const provider of API_KEY_PROVIDERS) { apiKeys[provider] = { configured: !!apiKeyStatus[provider], source: apiKeyStatus.sources?.[provider] ?? (apiKeyStatus[provider] ? "user" : null), }; } return { ...profile, mfaOnLogin: profile.mfaOnLogin === true, apiKeys, }; } export function UserProfileProvider({ children }: { children: ReactNode }) { const { user, isAuthenticated } = useAuth(); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const userId = user?.id ?? null; const loadProfile = useCallback(async () => { try { const profileData = await getUserProfile(); setProfile(toProfile(profileData)); } catch { // Calculate a default future reset date for fallback const futureResetDate = new Date(); futureResetDate.setDate(futureResetDate.getDate() + 30); // Set fallback profile data on exception setProfile({ displayName: null, organisation: null, messageCreditsUsed: 0, creditsResetDate: futureResetDate.toISOString(), creditsRemaining: 999999, // temporarily unlimited tier: "Free", titleModel: "gemini-3.1-flash-lite-preview", tabularModel: "gemini-3-flash-preview", mfaOnLogin: false, apiKeys: emptyApiKeys(), }); } finally { setLoading(false); } }, []); useEffect(() => { if (isAuthenticated && userId) { setLoading(true); loadProfile(); } else { setProfile(null); setLoading(false); } }, [isAuthenticated, userId, loadProfile]); const updateDisplayName = useCallback( async (displayName: string): Promise => { if (!user) { return false; } try { const updated = await updateUserProfile({ displayName }); setProfile((prev) => prev ? { ...prev, ...toProfile(updated) } : null, ); return true; } catch { return false; } }, [user], ); const updateOrganisation = useCallback( async (organisation: string): Promise => { if (!user) return false; try { const updated = await updateUserProfile({ organisation }); setProfile((prev) => prev ? { ...prev, ...toProfile(updated) } : null, ); return true; } catch (error) { if (isMfaRequiredError(error)) throw error; return false; } }, [user], ); const updateModelPreference = useCallback( async ( field: "titleModel" | "tabularModel", value: string, ): Promise => { if (!user) return false; try { const updated = await updateUserProfile({ [field]: value, }); setProfile((prev) => prev ? { ...prev, ...toProfile(updated) } : null, ); return true; } catch { return false; } }, [user], ); const updateMfaOnLogin = useCallback( async (enabled: boolean): Promise => { if (!user) return false; try { const updated = await updateUserMfaOnLogin(enabled); setProfile((prev) => prev ? { ...prev, ...toProfile(updated) } : null, ); return true; } catch (error) { if (isMfaRequiredError(error)) throw error; return false; } }, [user], ); const updateApiKey = useCallback( async ( provider: ApiKeyProvider, value: string | null, ): Promise => { if (!user) return false; const normalized = value?.trim() ? value.trim() : null; try { await saveApiKey(provider, normalized); setProfile((prev) => prev ? { ...prev, apiKeys: { ...prev.apiKeys, [provider]: { configured: !!normalized, source: normalized ? "user" : null, }, }, } : null, ); return true; } catch (error) { if (isMfaRequiredError(error)) throw error; return false; } }, [user], ); const reloadProfile = useCallback(async () => { if (userId) { await loadProfile(); } }, [userId, loadProfile]); const incrementMessageCredits = useCallback(async (): Promise => { if (!user || !profile) { return false; } // Check if user has credits remaining if (profile.creditsRemaining <= 0) { return false; } return false; }, [user, profile]); return ( {children} ); } export function useUserProfile() { const context = useContext(UserProfileContext); if (context === undefined) { throw new Error( "useUserProfile must be used within a UserProfileProvider", ); } return context; }