mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 13:11:16 -05:00
Implement API improvements Phases 1-4
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -8,6 +8,7 @@ import { Progress } from '@/components/ui/progress';
|
||||
import { Mail, Info, CheckCircle2, Circle, Loader2 } from 'lucide-react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { handleError, handleSuccess } from '@/lib/errorHandler';
|
||||
import { useEmailChangeStatus } from '@/hooks/security/useEmailChangeStatus';
|
||||
|
||||
interface EmailChangeStatusProps {
|
||||
currentEmail: string;
|
||||
@@ -15,55 +16,19 @@ interface EmailChangeStatusProps {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
type EmailChangeData = {
|
||||
has_pending_change: boolean;
|
||||
current_email?: string;
|
||||
new_email?: string;
|
||||
current_email_verified?: boolean;
|
||||
new_email_verified?: boolean;
|
||||
change_sent_at?: string;
|
||||
};
|
||||
|
||||
export function EmailChangeStatus({
|
||||
currentEmail,
|
||||
pendingEmail,
|
||||
onCancel
|
||||
}: EmailChangeStatusProps) {
|
||||
const [verificationStatus, setVerificationStatus] = useState({
|
||||
oldEmailVerified: false,
|
||||
newEmailVerified: false
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [resending, setResending] = useState(false);
|
||||
const { data: emailStatus, isLoading } = useEmailChangeStatus();
|
||||
|
||||
const checkVerificationStatus = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase.rpc('get_email_change_status');
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
const emailData = data as EmailChangeData;
|
||||
|
||||
if (emailData.has_pending_change) {
|
||||
setVerificationStatus({
|
||||
oldEmailVerified: emailData.current_email_verified || false,
|
||||
newEmailVerified: emailData.new_email_verified || false
|
||||
});
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
handleError(error, { action: 'Check verification status' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
const verificationStatus = {
|
||||
oldEmailVerified: emailStatus?.current_email_verified || false,
|
||||
newEmailVerified: emailStatus?.new_email_verified || false
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkVerificationStatus();
|
||||
// Poll every 30 seconds
|
||||
const interval = setInterval(checkVerificationStatus, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const handleResendVerification = async () => {
|
||||
setResending(true);
|
||||
try {
|
||||
@@ -88,7 +53,7 @@ export function EmailChangeStatus({
|
||||
(verificationStatus.oldEmailVerified ? 50 : 0) +
|
||||
(verificationStatus.newEmailVerified ? 50 : 0);
|
||||
|
||||
if (loading) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card className="border-blue-500/30">
|
||||
<CardContent className="flex items-center justify-center py-8">
|
||||
|
||||
@@ -14,6 +14,7 @@ import { TOTPSetup } from '@/components/auth/TOTPSetup';
|
||||
import { GoogleIcon } from '@/components/icons/GoogleIcon';
|
||||
import { DiscordIcon } from '@/components/icons/DiscordIcon';
|
||||
import { PasswordUpdateDialog } from './PasswordUpdateDialog';
|
||||
import { useSessions } from '@/hooks/security/useSessions';
|
||||
import {
|
||||
getUserIdentities,
|
||||
checkDisconnectSafety,
|
||||
@@ -37,14 +38,14 @@ export function SecurityTab() {
|
||||
const [disconnectingProvider, setDisconnectingProvider] = useState<OAuthProvider | null>(null);
|
||||
const [hasPassword, setHasPassword] = useState(false);
|
||||
const [addingPassword, setAddingPassword] = useState(false);
|
||||
const [sessions, setSessions] = useState<AuthSession[]>([]);
|
||||
const [loadingSessions, setLoadingSessions] = useState(true);
|
||||
const [sessionToRevoke, setSessionToRevoke] = useState<{ id: string; isCurrent: boolean } | null>(null);
|
||||
|
||||
// Fetch sessions using hook
|
||||
const { data: sessions = [], isLoading: loadingSessions, refetch: refetchSessions } = useSessions(user?.id);
|
||||
|
||||
// Load user identities on mount
|
||||
useEffect(() => {
|
||||
loadIdentities();
|
||||
fetchSessions();
|
||||
}, []);
|
||||
|
||||
const loadIdentities = async () => {
|
||||
@@ -145,35 +146,6 @@ export function SecurityTab() {
|
||||
setAddingPassword(false);
|
||||
};
|
||||
|
||||
const fetchSessions = async () => {
|
||||
if (!user) return;
|
||||
|
||||
setLoadingSessions(true);
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase.rpc('get_my_sessions');
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
setSessions((data as AuthSession[]) || []);
|
||||
} catch (error: unknown) {
|
||||
logger.error('Failed to fetch sessions', {
|
||||
userId: user.id,
|
||||
action: 'fetch_sessions',
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
handleError(error, {
|
||||
action: 'Load active sessions',
|
||||
userId: user.id
|
||||
});
|
||||
setSessions([]);
|
||||
} finally {
|
||||
setLoadingSessions(false);
|
||||
}
|
||||
};
|
||||
|
||||
const initiateSessionRevoke = async (sessionId: string) => {
|
||||
// Get current session to check if revoking self
|
||||
const { data: { session: currentSession } } = await supabase.auth.getSession();
|
||||
@@ -192,7 +164,7 @@ export function SecurityTab() {
|
||||
{
|
||||
onSuccess: () => {
|
||||
if (!sessionToRevoke.isCurrent) {
|
||||
fetchSessions();
|
||||
refetchSessions();
|
||||
}
|
||||
setSessionToRevoke(null);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user