mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 18:31:12 -05:00
Fix: Implement settings cleanup plan
This commit is contained in:
@@ -5,15 +5,13 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { useProfile } from '@/hooks/useProfile';
|
import { useProfile } from '@/hooks/useProfile';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { User, Upload, Trash2, Mail, AlertCircle, X, Check, Loader2 } from 'lucide-react';
|
import { Trash2, Mail, AlertCircle, X, Check, Loader2 } from 'lucide-react';
|
||||||
import { PhotoUpload } from '@/components/upload/PhotoUpload';
|
import { PhotoUpload } from '@/components/upload/PhotoUpload';
|
||||||
import { notificationService } from '@/lib/notificationService';
|
import { notificationService } from '@/lib/notificationService';
|
||||||
import { EmailChangeDialog } from './EmailChangeDialog';
|
import { EmailChangeDialog } from './EmailChangeDialog';
|
||||||
@@ -22,7 +20,7 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
|||||||
import { AccountDeletionDialog } from './AccountDeletionDialog';
|
import { AccountDeletionDialog } from './AccountDeletionDialog';
|
||||||
import { DeletionStatusBanner } from './DeletionStatusBanner';
|
import { DeletionStatusBanner } from './DeletionStatusBanner';
|
||||||
import { EmailChangeStatus } from './EmailChangeStatus';
|
import { EmailChangeStatus } from './EmailChangeStatus';
|
||||||
import { usernameSchema, displayNameSchema, bioSchema, personalLocationSchema, preferredPronounsSchema } from '@/lib/validation';
|
import { usernameSchema, displayNameSchema, bioSchema } from '@/lib/validation';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { AccountDeletionRequest } from '@/types/database';
|
import { AccountDeletionRequest } from '@/types/database';
|
||||||
import { handleError, handleSuccess, AppError } from '@/lib/errorHandler';
|
import { handleError, handleSuccess, AppError } from '@/lib/errorHandler';
|
||||||
@@ -35,11 +33,7 @@ import { cn } from '@/lib/utils';
|
|||||||
const profileSchema = z.object({
|
const profileSchema = z.object({
|
||||||
username: usernameSchema,
|
username: usernameSchema,
|
||||||
display_name: displayNameSchema,
|
display_name: displayNameSchema,
|
||||||
bio: bioSchema,
|
bio: bioSchema
|
||||||
preferred_pronouns: preferredPronounsSchema,
|
|
||||||
show_pronouns: z.boolean(),
|
|
||||||
preferred_language: z.string(),
|
|
||||||
personal_location: personalLocationSchema
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type ProfileFormData = z.infer<typeof profileSchema>;
|
type ProfileFormData = z.infer<typeof profileSchema>;
|
||||||
@@ -67,11 +61,7 @@ export function AccountProfileTab() {
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
username: profile?.username || '',
|
username: profile?.username || '',
|
||||||
display_name: profile?.display_name || '',
|
display_name: profile?.display_name || '',
|
||||||
bio: profile?.bio || '',
|
bio: profile?.bio || ''
|
||||||
preferred_pronouns: profile?.preferred_pronouns || '',
|
|
||||||
show_pronouns: profile?.show_pronouns || false,
|
|
||||||
preferred_language: profile?.preferred_language || 'en',
|
|
||||||
personal_location: profile?.personal_location || ''
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,9 +112,7 @@ export function AccountProfileTab() {
|
|||||||
const { data: result, error } = await supabase.rpc('update_profile', {
|
const { data: result, error } = await supabase.rpc('update_profile', {
|
||||||
p_username: data.username,
|
p_username: data.username,
|
||||||
p_display_name: data.display_name || null,
|
p_display_name: data.display_name || null,
|
||||||
p_bio: data.bio || null,
|
p_bio: data.bio || null
|
||||||
p_preferred_pronouns: data.preferred_pronouns || null,
|
|
||||||
p_personal_location: data.personal_location || null
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -340,8 +328,6 @@ export function AccountProfileTab() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||||
<h3 className="text-lg font-medium">Profile Information</h3>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="username">
|
<Label htmlFor="username">
|
||||||
@@ -411,55 +397,6 @@ export function AccountProfileTab() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="preferred_pronouns">Preferred Pronouns</Label>
|
|
||||||
<Input
|
|
||||||
id="preferred_pronouns"
|
|
||||||
{...form.register('preferred_pronouns')}
|
|
||||||
placeholder="e.g., they/them, she/her, he/him"
|
|
||||||
/>
|
|
||||||
{form.formState.errors.preferred_pronouns && (
|
|
||||||
<p className="text-sm text-destructive">
|
|
||||||
{form.formState.errors.preferred_pronouns.message}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="preferred_language">Preferred Language</Label>
|
|
||||||
<Select
|
|
||||||
value={form.watch('preferred_language')}
|
|
||||||
onValueChange={(value) => form.setValue('preferred_language', value)}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="en">English</SelectItem>
|
|
||||||
<SelectItem value="es">Español</SelectItem>
|
|
||||||
<SelectItem value="fr">Français</SelectItem>
|
|
||||||
<SelectItem value="de">Deutsch</SelectItem>
|
|
||||||
<SelectItem value="it">Italiano</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="personal_location">Personal Location</Label>
|
|
||||||
<Input
|
|
||||||
id="personal_location"
|
|
||||||
{...form.register('personal_location')}
|
|
||||||
placeholder="e.g., Los Angeles, CA"
|
|
||||||
/>
|
|
||||||
{form.formState.errors.personal_location && (
|
|
||||||
<p className="text-sm text-destructive">
|
|
||||||
{form.formState.errors.personal_location.message}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@@ -340,97 +340,12 @@ export function DataExportTab() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Activity className="w-5 h-5" />
|
<Activity className="w-5 h-5" />
|
||||||
<CardTitle>Account Activity</CardTitle>
|
<CardTitle>Recent Activity</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Recent account activity and changes
|
Recent account activity and changes
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
|
||||||
{rateLimited && nextAvailableAt && (
|
|
||||||
<div className="flex items-start gap-3 p-4 border border-yellow-500/20 bg-yellow-500/10 rounded-lg">
|
|
||||||
<Clock className="w-5 h-5 text-yellow-500 mt-0.5" />
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-sm font-medium">Rate Limited</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
You can export your data once per hour. Next export available at{' '}
|
|
||||||
{formatDate(nextAvailableAt)}.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Choose what to include in your export:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label htmlFor="include_reviews">Include Reviews</Label>
|
|
||||||
<Switch
|
|
||||||
id="include_reviews"
|
|
||||||
checked={exportOptions.include_reviews}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
setExportOptions({ ...exportOptions, include_reviews: checked })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label htmlFor="include_lists">Include Lists</Label>
|
|
||||||
<Switch
|
|
||||||
id="include_lists"
|
|
||||||
checked={exportOptions.include_lists}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
setExportOptions({ ...exportOptions, include_lists: checked })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label htmlFor="include_activity_log">Include Activity Log</Label>
|
|
||||||
<Switch
|
|
||||||
id="include_activity_log"
|
|
||||||
checked={exportOptions.include_activity_log}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
setExportOptions({ ...exportOptions, include_activity_log: checked })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label htmlFor="include_preferences">Include Preferences</Label>
|
|
||||||
<Switch
|
|
||||||
id="include_preferences"
|
|
||||||
checked={exportOptions.include_preferences}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
setExportOptions({ ...exportOptions, include_preferences: checked })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-start gap-3 p-4 border rounded-lg">
|
|
||||||
<AlertCircle className="w-5 h-5 text-blue-500 mt-0.5" />
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-sm font-medium">GDPR Compliance</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
This export includes all personal data we store about you. You can use this for backup purposes or to migrate to another service.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={handleDataExport}
|
|
||||||
disabled={exporting || rateLimited}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
<Download className="w-4 h-4 mr-2" />
|
|
||||||
{exporting ? 'Exporting Data...' : 'Export My Data'}
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{recentActivity.length === 0 ? (
|
{recentActivity.length === 0 ? (
|
||||||
<p className="text-sm text-muted-foreground text-center py-4">
|
<p className="text-sm text-muted-foreground text-center py-4">
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ export function SecurityTab() {
|
|||||||
icon: <DiscordIcon className="w-5 h-5" />
|
icon: <DiscordIcon className="w-5 h-5" />
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PasswordUpdateDialog
|
<PasswordUpdateDialog
|
||||||
@@ -247,16 +248,16 @@ export function SecurityTab() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-6">
|
||||||
{/* Password Section - Conditional based on auth method */}
|
{/* Password + Connected Accounts Grid */}
|
||||||
<div className="space-y-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<div className="flex items-center gap-2">
|
{/* Password Section */}
|
||||||
<Key className="w-5 h-5" />
|
|
||||||
<h3 className="text-lg font-medium">{hasPassword ? 'Change Password' : 'Add Password'}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Key className="w-5 h-5" />
|
||||||
|
<CardTitle>{hasPassword ? 'Change Password' : 'Add Password'}</CardTitle>
|
||||||
|
</div>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{hasPassword ? (
|
{hasPassword ? (
|
||||||
<>Update your password to keep your account secure.</>
|
<>Update your password to keep your account secure.</>
|
||||||
@@ -265,7 +266,7 @@ export function SecurityTab() {
|
|||||||
)}
|
)}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent>
|
||||||
{hasPassword ? (
|
{hasPassword ? (
|
||||||
<Button onClick={() => setPasswordDialogOpen(true)}>
|
<Button onClick={() => setPasswordDialogOpen(true)}>
|
||||||
Change Password
|
Change Password
|
||||||
@@ -284,22 +285,16 @@ export function SecurityTab() {
|
|||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
{/* Social Login Connections */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Globe className="w-5 h-5" />
|
|
||||||
<h3 className="text-lg font-medium">Connected Accounts</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{/* Connected Accounts */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Globe className="w-5 h-5" />
|
||||||
|
<CardTitle>Connected Accounts</CardTitle>
|
||||||
|
</div>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Manage your social login connections for easier access to your account.
|
Manage your social login connections for easier access to your account.
|
||||||
To enable social providers, configure them in your Supabase Auth settings.
|
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
@@ -364,31 +359,29 @@ export function SecurityTab() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
{/* Two-Factor Authentication - Full Width */}
|
||||||
|
|
||||||
{/* Two-Factor Authentication */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Smartphone className="w-5 h-5" />
|
|
||||||
<h3 className="text-lg font-medium">Two-Factor Authentication</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TOTPSetup />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
{/* Active Sessions & Login History */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Shield className="w-5 h-5" />
|
|
||||||
<h3 className="text-lg font-medium">
|
|
||||||
Active Sessions {sessions.length > 0 && `(${sessions.length})`}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Smartphone className="w-5 h-5" />
|
||||||
|
<CardTitle>Two-Factor Authentication</CardTitle>
|
||||||
|
</div>
|
||||||
|
<CardDescription>
|
||||||
|
Add an extra layer of security to your account with TOTP authentication
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<TOTPSetup />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Active Sessions - Full Width */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Shield className="w-5 h-5" />
|
||||||
|
<CardTitle>Active Sessions {sessions.length > 0 && `(${sessions.length})`}</CardTitle>
|
||||||
|
</div>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Review and manage your active login sessions across all devices
|
Review and manage your active login sessions across all devices
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
@@ -453,7 +446,6 @@ export function SecurityTab() {
|
|||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
|
||||||
|
|
||||||
<SessionRevokeConfirmDialog
|
<SessionRevokeConfirmDialog
|
||||||
open={!!sessionToRevoke}
|
open={!!sessionToRevoke}
|
||||||
|
|||||||
Reference in New Issue
Block a user