mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 09:11:13 -05:00
Fix: Implement complete fix
This commit is contained in:
105
src/components/auth/AuthDiagnostics.tsx
Normal file
105
src/components/auth/AuthDiagnostics.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { authStorage } from '@/lib/authStorage';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
|
export function AuthDiagnostics() {
|
||||||
|
const [diagnostics, setDiagnostics] = useState<any>(null);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const runDiagnostics = async () => {
|
||||||
|
const storageStatus = authStorage.getStorageStatus();
|
||||||
|
const { data: { session }, error: sessionError } = await supabase.auth.getSession();
|
||||||
|
|
||||||
|
const results = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
storage: storageStatus,
|
||||||
|
session: {
|
||||||
|
exists: !!session,
|
||||||
|
user: session?.user?.email || null,
|
||||||
|
expiresAt: session?.expires_at || null,
|
||||||
|
error: sessionError?.message || null,
|
||||||
|
},
|
||||||
|
network: {
|
||||||
|
online: navigator.onLine,
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
url: window.location.href,
|
||||||
|
isIframe: window.self !== window.top,
|
||||||
|
cookiesEnabled: navigator.cookieEnabled,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setDiagnostics(results);
|
||||||
|
console.log('[Auth Diagnostics]', results);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Run diagnostics on mount if there's a session issue
|
||||||
|
const checkSession = async () => {
|
||||||
|
const { data: { session } } = await supabase.auth.getSession();
|
||||||
|
if (!session) {
|
||||||
|
await runDiagnostics();
|
||||||
|
setIsOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only run if not already authenticated
|
||||||
|
const timer = setTimeout(checkSession, 3000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!isOpen || !diagnostics) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="fixed bottom-4 right-4 w-96 z-50 shadow-lg">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center justify-between">
|
||||||
|
<span>Authentication Diagnostics</span>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => setIsOpen(false)}>✕</Button>
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>Debug information for session issues</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2 text-sm">
|
||||||
|
<div>
|
||||||
|
<strong>Storage Type:</strong>{' '}
|
||||||
|
<Badge variant={diagnostics.storage.persistent ? 'default' : 'destructive'}>
|
||||||
|
{diagnostics.storage.type}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Session Status:</strong>{' '}
|
||||||
|
<Badge variant={diagnostics.session.exists ? 'default' : 'destructive'}>
|
||||||
|
{diagnostics.session.exists ? 'Active' : 'None'}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
{diagnostics.session.user && (
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
User: {diagnostics.session.user}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{diagnostics.session.error && (
|
||||||
|
<div className="text-xs text-destructive">
|
||||||
|
Error: {diagnostics.session.error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<strong>Cookies Enabled:</strong>{' '}
|
||||||
|
<Badge variant={diagnostics.environment.cookiesEnabled ? 'default' : 'destructive'}>
|
||||||
|
{diagnostics.environment.cookiesEnabled ? 'Yes' : 'No'}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
{diagnostics.environment.isIframe && (
|
||||||
|
<div className="text-xs text-yellow-600 dark:text-yellow-400">
|
||||||
|
⚠️ Running in iframe - storage may be restricted
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button onClick={runDiagnostics} variant="outline" size="sm" className="w-full mt-2">
|
||||||
|
Refresh Diagnostics
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
26
src/components/auth/StorageWarning.tsx
Normal file
26
src/components/auth/StorageWarning.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
import { AlertCircle } from "lucide-react";
|
||||||
|
import { authStorage } from "@/lib/authStorage";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function StorageWarning() {
|
||||||
|
const [showWarning, setShowWarning] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const status = authStorage.getStorageStatus();
|
||||||
|
setShowWarning(!status.persistent);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!showWarning) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert variant="destructive" className="mb-4">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertTitle>Storage Restricted</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Your browser is blocking session storage. You'll need to sign in again if you reload the page.
|
||||||
|
To fix this, please enable cookies and local storage for this site.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,8 +10,10 @@ interface AuthContextType {
|
|||||||
profile: Profile | null;
|
profile: Profile | null;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
pendingEmail: string | null;
|
pendingEmail: string | null;
|
||||||
|
sessionError: string | null;
|
||||||
signOut: () => Promise<void>;
|
signOut: () => Promise<void>;
|
||||||
refreshProfile: () => Promise<void>;
|
refreshProfile: () => Promise<void>;
|
||||||
|
verifySession: () => Promise<boolean>;
|
||||||
clearPendingEmail: () => void;
|
clearPendingEmail: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,14 +25,17 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
|||||||
const [profile, setProfile] = useState<Profile | null>(null);
|
const [profile, setProfile] = useState<Profile | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [pendingEmail, setPendingEmail] = useState<string | null>(null);
|
const [pendingEmail, setPendingEmail] = useState<string | null>(null);
|
||||||
|
const [sessionError, setSessionError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Refs for lifecycle and cleanup management
|
// Refs for lifecycle and cleanup management
|
||||||
const isMountedRef = useRef(true);
|
const isMountedRef = useRef(true);
|
||||||
const profileFetchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const profileFetchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const profileRetryCountRef = useRef(0);
|
||||||
|
const sessionVerifiedRef = useRef(false);
|
||||||
const novuUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const novuUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const previousEmailRef = useRef<string | null>(null);
|
const previousEmailRef = useRef<string | null>(null);
|
||||||
|
|
||||||
const fetchProfile = async (userId: string) => {
|
const fetchProfile = async (userId: string, retryCount = 0) => {
|
||||||
try {
|
try {
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
@@ -39,10 +44,22 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
|||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
|
|
||||||
if (error && error.code !== 'PGRST116') {
|
if (error && error.code !== 'PGRST116') {
|
||||||
console.error('Error fetching profile:', error);
|
console.error('[Auth] Error fetching profile:', error);
|
||||||
|
|
||||||
// Show user-friendly error notification
|
// Retry up to 3 times with exponential backoff
|
||||||
if (isMountedRef.current) {
|
if (retryCount < 3 && isMountedRef.current) {
|
||||||
|
const delay = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s
|
||||||
|
console.log(`[Auth] Retrying profile fetch in ${delay}ms (attempt ${retryCount + 1}/3)`);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isMountedRef.current) {
|
||||||
|
fetchProfile(userId, retryCount + 1);
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show user-friendly error notification only after all retries
|
||||||
|
if (isMountedRef.current && retryCount >= 3) {
|
||||||
toast({
|
toast({
|
||||||
title: "Profile Loading Error",
|
title: "Profile Loading Error",
|
||||||
description: "Unable to load your profile. Please refresh the page or try again later.",
|
description: "Unable to load your profile. Please refresh the page or try again later.",
|
||||||
@@ -55,17 +72,19 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
|||||||
// Only update state if component is still mounted
|
// Only update state if component is still mounted
|
||||||
if (isMountedRef.current) {
|
if (isMountedRef.current) {
|
||||||
setProfile(data as Profile);
|
setProfile(data as Profile);
|
||||||
|
profileRetryCountRef.current = 0; // Reset retry count on success
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching profile:', error);
|
console.error('[Auth] Error fetching profile:', error);
|
||||||
|
|
||||||
// Show user-friendly error notification
|
// Retry logic for network errors
|
||||||
if (isMountedRef.current) {
|
if (retryCount < 3 && isMountedRef.current) {
|
||||||
toast({
|
const delay = Math.pow(2, retryCount) * 1000;
|
||||||
title: "Profile Loading Error",
|
setTimeout(() => {
|
||||||
description: "An unexpected error occurred while loading your profile.",
|
if (isMountedRef.current) {
|
||||||
variant: "destructive",
|
fetchProfile(userId, retryCount + 1);
|
||||||
});
|
}
|
||||||
|
}, delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -76,20 +95,41 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
// Verify session is still valid
|
||||||
// Get initial session
|
const verifySession = async () => {
|
||||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
try {
|
||||||
if (!isMountedRef.current) return;
|
const { data: { session }, error } = await supabase.auth.getSession();
|
||||||
|
|
||||||
setSession(session);
|
if (error) {
|
||||||
setUser(session?.user ?? null);
|
console.error('[Auth] Session verification failed:', error);
|
||||||
if (session?.user) {
|
setSessionError(error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
console.log('[Auth] No active session found');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Auth] Session verified:', session.user.email);
|
||||||
|
sessionVerifiedRef.current = true;
|
||||||
|
|
||||||
|
// Update state if session was found but not set
|
||||||
|
if (!user && isMountedRef.current) {
|
||||||
|
setSession(session);
|
||||||
|
setUser(session.user);
|
||||||
fetchProfile(session.user.id);
|
fetchProfile(session.user.id);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
});
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Auth] Session verification error:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Listen for auth changes
|
useEffect(() => {
|
||||||
|
// CRITICAL: Set up listener FIRST to catch all events
|
||||||
const {
|
const {
|
||||||
data: { subscription },
|
data: { subscription },
|
||||||
} = supabase.auth.onAuthStateChange((event, session) => {
|
} = supabase.auth.onAuthStateChange((event, session) => {
|
||||||
@@ -100,12 +140,22 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
|||||||
const currentEmail = session?.user?.email;
|
const currentEmail = session?.user?.email;
|
||||||
const newEmailPending = session?.user?.new_email;
|
const newEmailPending = session?.user?.new_email;
|
||||||
|
|
||||||
|
// Clear any error
|
||||||
|
setSessionError(null);
|
||||||
|
|
||||||
// Explicitly handle SIGNED_IN event for iframe compatibility
|
// Explicitly handle SIGNED_IN event for iframe compatibility
|
||||||
if (event === 'SIGNED_IN' && session) {
|
if (event === 'SIGNED_IN' && session) {
|
||||||
console.log('[Auth] SIGNED_IN detected, setting session and user');
|
console.log('[Auth] SIGNED_IN detected, setting session and user');
|
||||||
setSession(session);
|
setSession(session);
|
||||||
setUser(session.user);
|
setUser(session.user);
|
||||||
|
sessionVerifiedRef.current = true;
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
} else if (event === 'SIGNED_OUT') {
|
||||||
|
console.log('[Auth] SIGNED_OUT detected, clearing session');
|
||||||
|
setSession(null);
|
||||||
|
setUser(null);
|
||||||
|
setProfile(null);
|
||||||
|
sessionVerifiedRef.current = false;
|
||||||
} else {
|
} else {
|
||||||
setSession(session);
|
setSession(session);
|
||||||
setUser(session?.user ?? null);
|
setUser(session?.user ?? null);
|
||||||
@@ -200,9 +250,47 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// THEN get initial session
|
||||||
|
supabase.auth.getSession().then(({ data: { session }, error }) => {
|
||||||
|
if (!isMountedRef.current) return;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('[Auth] Initial session fetch error:', error);
|
||||||
|
setSessionError(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSession(session);
|
||||||
|
setUser(session?.user ?? null);
|
||||||
|
if (session?.user) {
|
||||||
|
fetchProfile(session.user.id);
|
||||||
|
sessionVerifiedRef.current = true;
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Session verification fallback after 2 seconds
|
||||||
|
const verificationTimeout = setTimeout(() => {
|
||||||
|
if (!sessionVerifiedRef.current && isMountedRef.current) {
|
||||||
|
console.log('[Auth] Session not verified, attempting manual verification');
|
||||||
|
verifySession();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
// Handle page visibility changes
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
if (document.visibilityState === 'visible' && isMountedRef.current) {
|
||||||
|
console.log('[Auth] Tab became visible, verifying session');
|
||||||
|
verifySession();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isMountedRef.current = false;
|
isMountedRef.current = false;
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
|
clearTimeout(verificationTimeout);
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
// Clear any pending timeouts
|
// Clear any pending timeouts
|
||||||
if (profileFetchTimeoutRef.current) {
|
if (profileFetchTimeoutRef.current) {
|
||||||
@@ -234,8 +322,10 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) {
|
|||||||
profile,
|
profile,
|
||||||
loading,
|
loading,
|
||||||
pendingEmail,
|
pendingEmail,
|
||||||
|
sessionError,
|
||||||
signOut,
|
signOut,
|
||||||
refreshProfile,
|
refreshProfile,
|
||||||
|
verifySession,
|
||||||
clearPendingEmail,
|
clearPendingEmail,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ class AuthStorage {
|
|||||||
private storage: Storage | null = null;
|
private storage: Storage | null = null;
|
||||||
private memoryStorage: Map<string, string> = new Map();
|
private memoryStorage: Map<string, string> = new Map();
|
||||||
private storageType: 'localStorage' | 'sessionStorage' | 'memory' = 'memory';
|
private storageType: 'localStorage' | 'sessionStorage' | 'memory' = 'memory';
|
||||||
|
private sessionRecoveryAttempted = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Try localStorage first
|
// Try localStorage first
|
||||||
@@ -28,32 +29,113 @@ class AuthStorage {
|
|||||||
this.storageType = 'memory';
|
this.storageType = 'memory';
|
||||||
console.error('[AuthStorage] Both localStorage and sessionStorage blocked, using in-memory storage ⛔');
|
console.error('[AuthStorage] Both localStorage and sessionStorage blocked, using in-memory storage ⛔');
|
||||||
console.error('[AuthStorage] Sessions will NOT persist across page reloads!');
|
console.error('[AuthStorage] Sessions will NOT persist across page reloads!');
|
||||||
|
|
||||||
|
// Attempt to recover session from URL
|
||||||
|
this.attemptSessionRecoveryFromURL();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen for storage events to sync across tabs (when possible)
|
||||||
|
if (this.storage) {
|
||||||
|
window.addEventListener('storage', this.handleStorageChange.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private attemptSessionRecoveryFromURL() {
|
||||||
|
if (this.sessionRecoveryAttempted) return;
|
||||||
|
this.sessionRecoveryAttempted = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const urlParams = new URLSearchParams(window.location.hash.substring(1));
|
||||||
|
const accessToken = urlParams.get('access_token');
|
||||||
|
const refreshToken = urlParams.get('refresh_token');
|
||||||
|
|
||||||
|
if (accessToken && refreshToken) {
|
||||||
|
console.log('[AuthStorage] Recovering session from URL parameters');
|
||||||
|
// Store in memory
|
||||||
|
this.memoryStorage.set('sb-auth-token', JSON.stringify({
|
||||||
|
access_token: accessToken,
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
expires_at: Date.now() + 3600000, // 1 hour
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Clean URL
|
||||||
|
window.history.replaceState({}, document.title, window.location.pathname);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AuthStorage] Failed to recover session from URL:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleStorageChange(event: StorageEvent) {
|
||||||
|
// Sync auth state across tabs
|
||||||
|
if (event.key?.startsWith('sb-') && event.newValue) {
|
||||||
|
console.log('[AuthStorage] Syncing auth state across tabs');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getItem(key: string): string | null {
|
getItem(key: string): string | null {
|
||||||
if (this.storage) {
|
try {
|
||||||
return this.storage.getItem(key);
|
if (this.storage) {
|
||||||
|
const value = this.storage.getItem(key);
|
||||||
|
if (value) {
|
||||||
|
// Verify it's not expired
|
||||||
|
if (key.includes('auth-token')) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
if (parsed.expires_at && parsed.expires_at < Date.now()) {
|
||||||
|
console.warn('[AuthStorage] Token expired, removing');
|
||||||
|
this.removeItem(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return this.memoryStorage.get(key) || null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AuthStorage] Error reading from storage:', error);
|
||||||
|
return this.memoryStorage.get(key) || null;
|
||||||
}
|
}
|
||||||
return this.memoryStorage.get(key) || null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setItem(key: string, value: string): void {
|
setItem(key: string, value: string): void {
|
||||||
if (this.storage) {
|
try {
|
||||||
this.storage.setItem(key, value);
|
if (this.storage) {
|
||||||
} else {
|
this.storage.setItem(key, value);
|
||||||
|
}
|
||||||
|
// Always keep in memory as backup
|
||||||
|
this.memoryStorage.set(key, value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AuthStorage] Error writing to storage:', error);
|
||||||
|
// Fallback to memory only
|
||||||
this.memoryStorage.set(key, value);
|
this.memoryStorage.set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItem(key: string): void {
|
removeItem(key: string): void {
|
||||||
if (this.storage) {
|
try {
|
||||||
this.storage.removeItem(key);
|
if (this.storage) {
|
||||||
} else {
|
this.storage.removeItem(key);
|
||||||
|
}
|
||||||
|
this.memoryStorage.delete(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AuthStorage] Error removing from storage:', error);
|
||||||
this.memoryStorage.delete(key);
|
this.memoryStorage.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get storage status for diagnostics
|
||||||
|
getStorageStatus(): { type: string; persistent: boolean; warning: string | null } {
|
||||||
|
return {
|
||||||
|
type: this.storageType,
|
||||||
|
persistent: this.storageType !== 'memory',
|
||||||
|
warning: this.storageType === 'memory'
|
||||||
|
? 'Sessions will not persist across page reloads. Please enable cookies/storage for this site.'
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authStorage = new AuthStorage();
|
export const authStorage = new AuthStorage();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { supabase } from '@/integrations/supabase/client';
|
|||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { TurnstileCaptcha } from '@/components/auth/TurnstileCaptcha';
|
import { TurnstileCaptcha } from '@/components/auth/TurnstileCaptcha';
|
||||||
import { notificationService } from '@/lib/notificationService';
|
import { notificationService } from '@/lib/notificationService';
|
||||||
|
import { StorageWarning } from '@/components/auth/StorageWarning';
|
||||||
export default function Auth() {
|
export default function Auth() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -70,6 +71,11 @@ export default function Auth() {
|
|||||||
setSignInCaptchaToken(null);
|
setSignInCaptchaToken(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('[Auth] Attempting sign in...', {
|
||||||
|
email: formData.email,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
error
|
error
|
||||||
@@ -80,19 +86,54 @@ export default function Auth() {
|
|||||||
captchaToken: tokenToUse
|
captchaToken: tokenToUse
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
toast({
|
|
||||||
title: "Welcome back!",
|
console.log('[Auth] Sign in successful', {
|
||||||
description: "You've been signed in successfully."
|
user: data.user?.email,
|
||||||
|
session: !!data.session,
|
||||||
|
sessionExpiry: data.session?.expires_at
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Verify session was stored
|
||||||
|
setTimeout(async () => {
|
||||||
|
const { data: { session } } = await supabase.auth.getSession();
|
||||||
|
if (!session) {
|
||||||
|
console.error('[Auth] Session not found after login!');
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Session Error",
|
||||||
|
description: "Login succeeded but session was not stored. Please check your browser settings and enable cookies/storage."
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('[Auth] Session verified after login');
|
||||||
|
toast({
|
||||||
|
title: "Welcome back!",
|
||||||
|
description: "You've been signed in successfully."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Reset CAPTCHA widget to force fresh token generation
|
// Reset CAPTCHA widget to force fresh token generation
|
||||||
setSignInCaptchaKey(prev => prev + 1);
|
setSignInCaptchaKey(prev => prev + 1);
|
||||||
|
|
||||||
|
console.error('[Auth] Sign in error:', error);
|
||||||
|
|
||||||
|
// Enhanced error messages
|
||||||
|
let errorMessage = error.message;
|
||||||
|
if (error.message.includes('Invalid login credentials')) {
|
||||||
|
errorMessage = 'Invalid email or password. Please try again.';
|
||||||
|
} else if (error.message.includes('Email not confirmed')) {
|
||||||
|
errorMessage = 'Please confirm your email address before signing in.';
|
||||||
|
} else if (error.message.includes('Too many requests')) {
|
||||||
|
errorMessage = 'Too many login attempts. Please wait a few minutes and try again.';
|
||||||
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Sign in failed",
|
title: "Sign in failed",
|
||||||
description: error.message
|
description: errorMessage
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -251,6 +292,7 @@ export default function Auth() {
|
|||||||
|
|
||||||
<main className="container mx-auto px-4 py-16">
|
<main className="container mx-auto px-4 py-16">
|
||||||
<div className="max-w-md mx-auto">
|
<div className="max-w-md mx-auto">
|
||||||
|
<StorageWarning />
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
<div className="flex items-center justify-center gap-2 mb-4">
|
<div className="flex items-center justify-center gap-2 mb-4">
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user