Implement API improvements Phases 1-4

This commit is contained in:
gpt-engineer-app[bot]
2025-10-31 12:33:27 +00:00
parent ca9aa757ae
commit c70c5a4150
13 changed files with 214 additions and 119 deletions

View File

@@ -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">

View File

@@ -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);
},