import React, { createContext, useContext, useEffect, useState } from 'react'; import type { User, Session } from '@supabase/supabase-js'; import { supabase } from '../lib/supabaseClient'; import { TradingAuthProvider, useTradingAuth } from '../lib/tradingAuth'; import { fetchCurrentUserProfile, fetchTradeProfiles } from '../lib/profileApi'; // Define the shape of our extended user profile export interface UserProfile { user_id: string; first_name: string; last_name: string; email: string; role: string; // Alpaca Settings ALPACA_API_KEY?: string; ALPACA_SECRET_KEY?: string; REAL_ALPACA_API_KEY?: string; REAL_ALPACA_SECRET_KEY?: string; // Bot Settings trade_enable: boolean; drop_threshold_for_buy?: string | number; gain_threshold_for_sell?: string | number; market_poll_interval_in_seconds?: string | number; } interface AuthContextType { session: Session | null; user: User | null; profile: UserProfile | null; loading: boolean; signOut: () => Promise; refreshProfile: () => Promise; } const AuthContext = createContext(undefined); const buildFallbackProfile = (authUser: User | null): UserProfile | null => { if (!authUser?.id) return null; const displayName = String((authUser as any)?.display_name || (authUser as any)?.user_metadata?.displayName || '').trim(); const parts = displayName ? displayName.split(/\s+/) : []; return { user_id: authUser.id, first_name: parts[0] || '', last_name: parts.slice(1).join(' '), email: authUser.email || '', role: String((authUser as any)?.role || (authUser as any)?.user_metadata?.role || 'member'), trade_enable: true, }; }; export const shouldCreateDefaultProfile = (profiles: Array<{ id?: string }> | null | undefined) => !profiles || profiles.length === 0; export const buildDefaultProfilePayload = (userId: string) => ({ user_id: userId, name: 'My First Strategy', allocated_capital: 1000, risk_per_trade_percent: 1, symbols: 'BTC/USDT, ETH/USDT', is_active: false, strategy_config: { rules: [ { ruleId: 'TrendBiasRule', enabled: true, params: { fastPeriod: 50, slowPeriod: 200 } }, { ruleId: 'MomentumRule', enabled: true, params: { rsiPeriod: 14, overbought: 70, oversold: 30 } }, { ruleId: 'ZoneRule', enabled: true, params: { zonePercent: 1.5 } }, { ruleId: 'SessionRule', enabled: true, params: { sessions: 'London,NY' } }, { ruleId: 'EntryTriggerRule', enabled: true, params: { showPatterns: true } }, { ruleId: 'RiskManagementRule', enabled: true, params: { maxRisk: 2.0 } }, { ruleId: 'AIAnalysisRule', enabled: false, params: { minConfidence: 0.7 } }, ], riskLimits: { maxDailyLossUsd: 50, maxOpenTrades: 3, maxConsecutiveLosses: 2 }, execution: { orderType: 'market', cooldownMinutes: 30, entryMode: 'both' }, }, }); export function AuthProvider({ children }: { children: React.ReactNode }) { return ( {children} ); } function AuthBridge({ children }: { children: React.ReactNode }) { const tradingAuth = useTradingAuth(); const [session, setSession] = useState(null); const [user, setUser] = useState(null); const [profile, setProfile] = useState(null); const [profileLoading, setProfileLoading] = useState(true); useEffect(() => { let active = true; const syncSession = async () => { if (!tradingAuth.user?.id) { if (!active) return; setSession(null); setUser(null); setProfile(null); setProfileLoading(false); return; } const { data: { session: nextSession } } = await supabase.auth.getSession(); if (!active) return; const normalizedSession = (nextSession as Session | null) ?? null; const normalizedUser = (normalizedSession?.user as User | null) ?? buildFallbackAuthUser(tradingAuth.user); setSession(normalizedSession); setUser(normalizedUser); await fetchProfile(tradingAuth.user.id, normalizedUser); }; void syncSession(); return () => { active = false; }; }, [tradingAuth.user?.id]); const fetchProfile = async (_userId: string, authUserOverride?: User | null) => { try { const currentProfile = await fetchCurrentUserProfile(); setProfile(currentProfile as UserProfile); await ensureDefaultProfile(); } catch (err) { console.error('Unexpected error fetching profile:', err); setProfile(buildFallbackProfile(authUserOverride ?? user)); } finally { setProfileLoading(false); } }; const ensureDefaultProfile = async () => { try { const profiles = await fetchTradeProfiles({ ensureDefault: true }); if (shouldCreateDefaultProfile(profiles)) { console.log('[Auth] No profiles found after bootstrap ensureDefault call'); } else { window.dispatchEvent(new Event('profiles-updated')); } } catch (err) { console.error('[Auth] Error ensuring default profile:', err); } }; const signOut = async () => { await supabase.auth.signOut(); tradingAuth.logout(); setSession(null); setUser(null); setProfile(null); }; const refreshProfile = async () => { if (user) { await fetchProfile(user.id); } } const value = { session, user, profile, loading: tradingAuth.isLoading || profileLoading, signOut, refreshProfile }; return {children}; } const buildFallbackAuthUser = (authUser: { id: string; email?: string; role?: string; name?: string; } | null): User | null => { if (!authUser?.id) return null; return { id: authUser.id, email: authUser.email || '', aud: 'authenticated', app_metadata: {}, user_metadata: { role: authUser.role || 'member', displayName: authUser.name || authUser.email || '', }, created_at: new Date(0).toISOString(), } as User; }; export const useAuth = () => { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; };