Refactor: Complete API and cache improvements

This commit is contained in:
gpt-engineer-app[bot]
2025-10-31 12:22:06 +00:00
parent 0d16bb511c
commit 631ce9c89e
8 changed files with 219 additions and 254 deletions

View File

@@ -13,6 +13,7 @@ import { Skeleton } from '@/components/ui/skeleton';
import { useAuth } from '@/hooks/useAuth';
import { useProfile } from '@/hooks/useProfile';
import { useUnitPreferences } from '@/hooks/useUnitPreferences';
import { useProfileLocationMutation } from '@/hooks/profile/useProfileLocationMutation';
import { supabase } from '@/integrations/supabase/client';
import { handleError, handleSuccess, AppError } from '@/lib/errorHandler';
import { logger } from '@/lib/logger';
@@ -30,8 +31,8 @@ export function LocationTab() {
const { user } = useAuth();
const { data: profile, refreshProfile } = useProfile(user?.id);
const { preferences: unitPreferences, updatePreferences: updateUnitPreferences } = useUnitPreferences();
const { updateLocation, isUpdating } = useProfileLocationMutation();
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [parks, setParks] = useState<ParkOption[]>([]);
const [accessibility, setAccessibility] = useState<AccessibilityOptions>(DEFAULT_ACCESSIBILITY_OPTIONS);
@@ -171,42 +172,11 @@ export function LocationTab() {
const onSubmit = async (data: LocationFormData) => {
if (!user) return;
setSaving(true);
try {
const validatedData = locationFormSchema.parse(data);
const validatedAccessibility = accessibilityOptionsSchema.parse(accessibility);
const previousProfile = {
personal_location: profile?.personal_location,
home_park_id: profile?.home_park_id,
timezone: profile?.timezone,
preferred_language: profile?.preferred_language,
preferred_pronouns: profile?.preferred_pronouns
};
const { error: profileError } = await supabase
.from('profiles')
.update({
preferred_pronouns: validatedData.preferred_pronouns || null,
timezone: validatedData.timezone,
preferred_language: validatedData.preferred_language,
personal_location: validatedData.personal_location || null,
home_park_id: validatedData.home_park_id || null,
updated_at: new Date().toISOString()
})
.eq('user_id', user.id);
if (profileError) {
logger.error('Failed to update profile', {
userId: user.id,
action: 'update_profile_location',
error: profileError.message,
errorCode: profileError.code
});
throw profileError;
}
// Update accessibility preferences first
const { error: accessibilityError } = await supabase
.from('user_preferences')
.update({
@@ -227,34 +197,20 @@ export function LocationTab() {
await updateUnitPreferences(unitPreferences);
await supabase.from('profile_audit_log').insert([{
user_id: user.id,
changed_by: user.id,
action: 'location_info_updated',
changes: JSON.parse(JSON.stringify({
previous: {
profile: previousProfile,
accessibility: DEFAULT_ACCESSIBILITY_OPTIONS
},
updated: {
profile: validatedData,
accessibility: validatedAccessibility
},
timestamp: new Date().toISOString()
}))
}]);
// Update profile via mutation hook with complete validated data
const locationData: LocationFormData = {
personal_location: validatedData.personal_location || null,
home_park_id: validatedData.home_park_id || null,
timezone: validatedData.timezone,
preferred_language: validatedData.preferred_language,
preferred_pronouns: validatedData.preferred_pronouns || null,
};
await refreshProfile();
logger.info('Location and info settings updated', {
userId: user.id,
action: 'update_location_info'
updateLocation.mutate(locationData, {
onSuccess: () => {
refreshProfile();
}
});
handleSuccess(
'Settings saved',
'Your location, personal information, accessibility, and unit preferences have been updated.'
);
} catch (error: unknown) {
logger.error('Error saving location settings', {
userId: user.id,
@@ -277,8 +233,6 @@ export function LocationTab() {
userId: user.id
});
}
} finally {
setSaving(false);
}
};
@@ -558,8 +512,8 @@ export function LocationTab() {
{/* Save Button */}
<div className="flex justify-end">
<Button type="submit" disabled={saving}>
{saving ? 'Saving...' : 'Save Settings'}
<Button type="submit" disabled={isUpdating}>
{isUpdating ? 'Saving...' : 'Save Settings'}
</Button>
</div>
</form>

View File

@@ -11,6 +11,7 @@ import { handleError, handleSuccess, AppError } from '@/lib/errorHandler';
import { logger } from '@/lib/logger';
import { useAuth } from '@/hooks/useAuth';
import { useProfile } from '@/hooks/useProfile';
import { usePrivacyMutations } from '@/hooks/privacy/usePrivacyMutations';
import { supabase } from '@/integrations/supabase/client';
import { Eye, UserX, Shield, Search } from 'lucide-react';
import { BlockedUsers } from '@/components/privacy/BlockedUsers';
@@ -21,7 +22,7 @@ import { z } from 'zod';
export function PrivacyTab() {
const { user } = useAuth();
const { data: profile, refreshProfile } = useProfile(user?.id);
const [loading, setLoading] = useState(false);
const { updatePrivacy, isUpdating } = usePrivacyMutations();
const [preferences, setPreferences] = useState<PrivacySettings | null>(null);
const form = useForm<PrivacyFormData>({
@@ -134,106 +135,17 @@ export function PrivacyTab() {
}
};
const onSubmit = async (data: PrivacyFormData) => {
const onSubmit = (data: PrivacyFormData) => {
if (!user) return;
setLoading(true);
try {
// Validate the form data
const validated = privacyFormSchema.parse(data);
// Update profile privacy settings
const { error: profileError } = await supabase
.from('profiles')
.update({
privacy_level: validated.privacy_level,
show_pronouns: validated.show_pronouns,
updated_at: new Date().toISOString()
})
.eq('user_id', user.id);
if (profileError) {
logger.error('Failed to update profile privacy', {
userId: user.id,
action: 'update_profile_privacy',
error: profileError.message,
errorCode: profileError.code
});
throw profileError;
updatePrivacy.mutate(data, {
onSuccess: () => {
refreshProfile();
// Extract privacy settings (exclude profile fields)
const { privacy_level, show_pronouns, ...privacySettings } = data;
setPreferences(privacySettings);
}
// Extract privacy settings (exclude profile fields)
const { privacy_level, show_pronouns, ...privacySettings } = validated;
// Update user preferences
const { error: prefsError } = await supabase
.from('user_preferences')
.upsert([{
user_id: user.id,
privacy_settings: privacySettings,
updated_at: new Date().toISOString()
}]);
if (prefsError) {
logger.error('Failed to update privacy preferences', {
userId: user.id,
action: 'update_privacy_preferences',
error: prefsError.message,
errorCode: prefsError.code
});
throw prefsError;
}
// Log to audit trail
await supabase.from('profile_audit_log').insert([{
user_id: user.id,
changed_by: user.id,
action: 'privacy_settings_updated',
changes: JSON.parse(JSON.stringify({
previous: preferences,
updated: privacySettings,
timestamp: new Date().toISOString()
}))
}]);
await refreshProfile();
setPreferences(privacySettings);
logger.info('Privacy settings updated successfully', {
userId: user.id,
action: 'update_privacy_settings'
});
handleSuccess(
'Privacy settings updated',
'Your privacy preferences have been successfully saved.'
);
} catch (error: unknown) {
logger.error('Failed to update privacy settings', {
userId: user.id,
action: 'update_privacy_settings',
error: error instanceof Error ? error.message : String(error)
});
if (error instanceof z.ZodError) {
handleError(
new AppError(
'Invalid privacy settings',
'VALIDATION_ERROR',
error.issues.map(e => e.message).join(', ')
),
{ action: 'Validate privacy settings', userId: user.id }
);
} else {
handleError(error, {
action: 'Update privacy settings',
userId: user.id
});
}
} finally {
setLoading(false);
}
});
};
return (
@@ -450,8 +362,8 @@ export function PrivacyTab() {
{/* Save Button */}
<div className="flex justify-end">
<Button type="submit" disabled={loading}>
{loading ? 'Saving...' : 'Save Privacy Settings'}
<Button type="submit" disabled={isUpdating}>
{isUpdating ? 'Saving...' : 'Save Privacy Settings'}
</Button>
</div>
</form>

View File

@@ -6,6 +6,7 @@ import { Separator } from '@/components/ui/separator';
import { Badge } from '@/components/ui/badge';
import { handleError, handleSuccess } from '@/lib/errorHandler';
import { useAuth } from '@/hooks/useAuth';
import { useSecurityMutations } from '@/hooks/security/useSecurityMutations';
import { Shield, Key, Smartphone, Globe, Loader2, Monitor, Tablet, Trash2 } from 'lucide-react';
import { format } from 'date-fns';
import { Skeleton } from '@/components/ui/skeleton';
@@ -29,6 +30,7 @@ import { SessionRevokeConfirmDialog } from './SessionRevokeConfirmDialog';
export function SecurityTab() {
const { user } = useAuth();
const navigate = useNavigate();
const { revokeSession, isRevoking } = useSecurityMutations();
const [passwordDialogOpen, setPasswordDialogOpen] = useState(false);
const [identities, setIdentities] = useState<UserIdentity[]>([]);
const [loadingIdentities, setLoadingIdentities] = useState(true);
@@ -182,33 +184,23 @@ export function SecurityTab() {
setSessionToRevoke({ id: sessionId, isCurrent: !!isCurrentSession });
};
const confirmRevokeSession = async () => {
const confirmRevokeSession = () => {
if (!sessionToRevoke) return;
const { error } = await supabase.rpc('revoke_my_session', { session_id: sessionToRevoke.id });
if (error) {
logger.error('Failed to revoke session', {
userId: user?.id,
action: 'revoke_session',
sessionId: sessionToRevoke.id,
error: error.message
});
handleError(error, { action: 'Revoke session', userId: user?.id });
} else {
handleSuccess('Success', 'Session revoked successfully');
if (sessionToRevoke.isCurrent) {
// Redirect to login after revoking current session
setTimeout(() => {
window.location.href = '/auth';
}, 1000);
} else {
fetchSessions();
revokeSession.mutate(
{ sessionId: sessionToRevoke.id, isCurrent: sessionToRevoke.isCurrent },
{
onSuccess: () => {
if (!sessionToRevoke.isCurrent) {
fetchSessions();
}
setSessionToRevoke(null);
},
onError: () => {
setSessionToRevoke(null);
}
}
}
setSessionToRevoke(null);
);
};
const getDeviceIcon = (userAgent: string | null) => {