import React, { createContext, useContext, useEffect, useState, useCallback } from 'react'; import { supabase } from '@/lib/supabase'; import { User } from '@supabase/supabase-js'; import { isMobileApp, getNativeWalletAddress, getNativeAccountName } from '@/lib/mobile-bridge'; // Session timeout configuration const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes const ACTIVITY_CHECK_INTERVAL_MS = 60 * 1000; // Check every 1 minute const LAST_ACTIVITY_KEY = 'last_activity_timestamp'; const REMEMBER_ME_KEY = 'remember_me'; interface AuthContextType { user: User | null; loading: boolean; isAdmin: boolean; signIn: (email: string, password: string, rememberMe?: boolean) => Promise<{ error: Error | null }>; signUp: (email: string, password: string, username: string, referralCode?: string) => Promise<{ error: Error | null }>; signOut: () => Promise; checkAdminStatus: () => Promise; } const AuthContext = createContext(undefined); export const useAuth = () => { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }; export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [isAdmin, setIsAdmin] = useState(false); // ======================================== // SESSION TIMEOUT MANAGEMENT // ======================================== // Update last activity timestamp const updateLastActivity = useCallback(() => { localStorage.setItem(LAST_ACTIVITY_KEY, Date.now().toString()); }, []); const signOut = useCallback(async () => { setIsAdmin(false); setUser(null); localStorage.removeItem(LAST_ACTIVITY_KEY); localStorage.removeItem(REMEMBER_ME_KEY); await supabase.auth.signOut(); }, []); // Check if session has timed out const checkSessionTimeout = useCallback(async () => { if (!user) return; // Skip timeout check if "Remember Me" is enabled const rememberMe = localStorage.getItem(REMEMBER_ME_KEY); if (rememberMe === 'true') { return; // Don't timeout if user chose to be remembered } const lastActivity = localStorage.getItem(LAST_ACTIVITY_KEY); if (!lastActivity) { updateLastActivity(); return; } const lastActivityTime = parseInt(lastActivity, 10); const now = Date.now(); const inactiveTime = now - lastActivityTime; if (inactiveTime >= SESSION_TIMEOUT_MS) { if (import.meta.env.DEV) console.log('⏱️ Session timeout - logging out due to inactivity'); await signOut(); } }, [user, updateLastActivity, signOut]); // Setup activity listeners useEffect(() => { if (!user) return; // Update activity on user interactions const activityEvents = ['mousedown', 'keydown', 'scroll', 'touchstart']; const handleActivity = () => { updateLastActivity(); }; // Register event listeners activityEvents.forEach((event) => { window.addEventListener(event, handleActivity); }); // Initial activity timestamp updateLastActivity(); // Check for timeout periodically const timeoutChecker = setInterval(checkSessionTimeout, ACTIVITY_CHECK_INTERVAL_MS); // Cleanup return () => { activityEvents.forEach((event) => { window.removeEventListener(event, handleActivity); }); clearInterval(timeoutChecker); }; }, [user, updateLastActivity, checkSessionTimeout]); const checkAdminStatus = useCallback(async () => { // Admin wallet whitelist (blockchain-based auth) const ADMIN_WALLETS = [ '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', // Founder (original) '5DFwqK698vL4gXHEcanaewnAqhxJ2rjhAogpSTHw3iwGDwd3', // Founder delegate (initial KYC member) '5GgTgG9sRmPQAYU1RsTejZYnZRjwzKZKWD3awtuqjHioki45', // Founder (current dev wallet) ]; try { // PRIMARY: Check wallet-based admin (blockchain auth) const connectedWallet = localStorage.getItem('selectedWallet'); if (import.meta.env.DEV) console.log('🔍 Admin check - Connected wallet:', connectedWallet); if (import.meta.env.DEV) console.log('🔍 Admin check - Whitelist:', ADMIN_WALLETS); if (connectedWallet && ADMIN_WALLETS.includes(connectedWallet)) { if (import.meta.env.DEV) console.log('✅ Admin access granted (wallet-based)'); setIsAdmin(true); return true; } // SECONDARY: Supabase admin_roles check disabled (table may not exist) // Admin access is primarily wallet-based via the whitelist above if (import.meta.env.DEV) console.log('❌ Admin access denied (wallet not in whitelist)'); setIsAdmin(false); return false; } catch (err) { if (import.meta.env.DEV) console.error('Admin check error:', err); setIsAdmin(false); return false; } }, []); // Setup native mobile wallet if running in mobile app const setupMobileWallet = useCallback(() => { if (isMobileApp()) { const nativeAddress = getNativeWalletAddress(); const nativeAccountName = getNativeAccountName(); if (nativeAddress) { // Store native wallet address for admin checks and wallet operations localStorage.setItem('selectedWallet', nativeAddress); if (nativeAccountName) { localStorage.setItem('selectedWalletName', nativeAccountName); } if (import.meta.env.DEV) { console.log('[Mobile] Native wallet detected:', nativeAddress); } // Dispatch wallet change event window.dispatchEvent(new Event('walletChanged')); } } }, []); useEffect(() => { // Setup mobile wallet first setupMobileWallet(); // Check active sessions and sets the user supabase.auth.getSession().then(({ data: { session } }) => { setUser(session?.user ?? null); checkAdminStatus(); // Check admin status regardless of Supabase session setLoading(false); }).catch(() => { // If Supabase is not available, still check wallet-based admin checkAdminStatus(); setLoading(false); }); // Listen for changes on auth state const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { setUser(session?.user ?? null); checkAdminStatus(); // Check admin status on auth change setLoading(false); }); // Listen for wallet changes (from PezkuwiContext or native bridge) const handleWalletChange = () => { checkAdminStatus(); }; window.addEventListener('walletChanged', handleWalletChange); // Listen for native bridge ready event (mobile app) const handleNativeReady = () => { if (import.meta.env.DEV) { console.log('[Mobile] Native bridge ready'); } setupMobileWallet(); checkAdminStatus(); }; window.addEventListener('pezkuwi-native-ready', handleNativeReady); return () => { subscription.unsubscribe(); window.removeEventListener('walletChanged', handleWalletChange); window.removeEventListener('pezkuwi-native-ready', handleNativeReady); }; }, [checkAdminStatus, setupMobileWallet]); const signIn = async (email: string, password: string, rememberMe: boolean = false) => { try { const { data, error } = await supabase.auth.signInWithPassword({ email, password, }); if (!error && data.user) { // Store remember me preference if (rememberMe) { localStorage.setItem(REMEMBER_ME_KEY, 'true'); } else { localStorage.removeItem(REMEMBER_ME_KEY); } await checkAdminStatus(); } return { error }; } catch { return { error: { message: 'Authentication service unavailable. Please try again later.' } }; } }; const signUp = async (email: string, password: string, username: string, referralCode?: string) => { try { const { data, error } = await supabase.auth.signUp({ email, password, options: { data: { username, referral_code: referralCode || null, }, }, }); if (!error && data.user) { // Create profile in profiles table with referral code await supabase.from('profiles').insert({ id: data.user.id, username, email, referred_by: referralCode || null, }); // If there's a referral code, track it if (referralCode) { // You can add logic here to reward the referrer // For example, update their referral count or add rewards if (import.meta.env.DEV) console.log(`User registered with referral code: ${referralCode}`); } } return { error }; } catch { return { error: { message: 'Registration service unavailable. Please try again later.' } }; } }; return ( {children} ); };