mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 11:51:14 -05:00
Refactor: Implement desktop layout modernization
This commit is contained in:
@@ -244,7 +244,7 @@ export function AccountProfileTab() {
|
||||
const isDeactivated = profile?.deactivated || false;
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-6">
|
||||
{/* Deletion Status Banner */}
|
||||
{deletionRequest && (
|
||||
<DeletionStatusBanner
|
||||
@@ -253,26 +253,93 @@ export function AccountProfileTab() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Profile Picture */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">Profile Picture</h3>
|
||||
<PhotoUpload
|
||||
variant="avatar"
|
||||
maxFiles={1}
|
||||
maxSizeMB={1}
|
||||
existingPhotos={avatarUrl ? [avatarUrl] : []}
|
||||
onUploadComplete={handleAvatarUpload}
|
||||
currentImageId={avatarImageId}
|
||||
onError={(error) => {
|
||||
handleError(new Error(error), { action: 'Upload avatar' });
|
||||
}}
|
||||
/>
|
||||
{/* Profile Picture + Account Info Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Profile Picture */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Profile Picture</CardTitle>
|
||||
<CardDescription>Upload your profile picture</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PhotoUpload
|
||||
variant="avatar"
|
||||
maxFiles={1}
|
||||
maxSizeMB={1}
|
||||
existingPhotos={avatarUrl ? [avatarUrl] : []}
|
||||
onUploadComplete={handleAvatarUpload}
|
||||
currentImageId={avatarImageId}
|
||||
onError={(error) => {
|
||||
handleError(new Error(error), { action: 'Upload avatar' });
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Account Information */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Account Information</CardTitle>
|
||||
<CardDescription>View your account details</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{pendingEmail && (
|
||||
<EmailChangeStatus
|
||||
currentEmail={user?.email || ''}
|
||||
pendingEmail={pendingEmail}
|
||||
onCancel={() => setShowCancelEmailDialog(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="p-4 bg-muted/50 rounded-lg space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">Email Address</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<p className="text-sm text-muted-foreground">{user?.email}</p>
|
||||
{pendingEmail ? (
|
||||
<Badge variant="secondary" className="bg-blue-500/10 text-blue-500 border-blue-500/20 text-xs">
|
||||
Change Pending
|
||||
</Badge>
|
||||
) : user?.email_confirmed_at ? (
|
||||
<Badge variant="secondary" className="text-xs">Verified</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-xs">Pending Verification</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowEmailDialog(true)}
|
||||
disabled={!!pendingEmail}
|
||||
>
|
||||
<Mail className="w-4 h-4 mr-2" />
|
||||
Change
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-medium">Account Created</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{profile?.created_at ? new Date(profile.created_at).toLocaleDateString() : 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Profile Information */}
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Profile Information</CardTitle>
|
||||
<CardDescription>Update your public profile details</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<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">
|
||||
@@ -393,86 +460,57 @@ export function AccountProfileTab() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={
|
||||
loading ||
|
||||
isDeactivated ||
|
||||
isSaving ||
|
||||
usernameValidation.isChecking ||
|
||||
usernameValidation.isAvailable === false
|
||||
}
|
||||
>
|
||||
{loading || isSaving ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
|
||||
{lastSaved && !loading && !isSaving && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Last saved {formatDistanceToNow(lastSaved, { addSuffix: true })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{isDeactivated && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your account is deactivated. Profile editing is disabled.
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Account Information */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">Account Information</h3>
|
||||
<div className="space-y-4">
|
||||
{pendingEmail && (
|
||||
<EmailChangeStatus
|
||||
currentEmail={user?.email || ''}
|
||||
pendingEmail={pendingEmail}
|
||||
onCancel={() => setShowCancelEmailDialog(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="p-4 bg-muted/50 rounded-lg space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">Email Address</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<p className="text-sm text-muted-foreground">{user?.email}</p>
|
||||
{pendingEmail ? (
|
||||
<Badge variant="secondary" className="bg-blue-500/10 text-blue-500 border-blue-500/20 text-xs">
|
||||
Change Pending
|
||||
</Badge>
|
||||
) : user?.email_confirmed_at ? (
|
||||
<Badge variant="secondary" className="text-xs">Verified</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-xs">Pending Verification</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowEmailDialog(true)}
|
||||
disabled={!!pendingEmail}
|
||||
type="submit"
|
||||
disabled={
|
||||
loading ||
|
||||
isDeactivated ||
|
||||
isSaving ||
|
||||
usernameValidation.isChecking ||
|
||||
usernameValidation.isAvailable === false
|
||||
}
|
||||
>
|
||||
<Mail className="w-4 h-4 mr-2" />
|
||||
Change Email
|
||||
{loading || isSaving ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
|
||||
{lastSaved && !loading && !isSaving && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Last saved {formatDistanceToNow(lastSaved, { addSuffix: true })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-medium">Account Created</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{profile?.created_at ? new Date(profile.created_at).toLocaleDateString() : 'N/A'}
|
||||
{isDeactivated && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your account is deactivated. Profile editing is disabled.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Danger Zone */}
|
||||
<Card className="border-destructive/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-destructive">Danger Zone</CardTitle>
|
||||
<CardDescription>Irreversible account actions</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setShowDeletionDialog(true)}
|
||||
disabled={!!deletionRequest}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
Delete Account
|
||||
</Button>
|
||||
{deletionRequest && (
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
Account deletion already in progress
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Email Change Dialog */}
|
||||
{user && (
|
||||
@@ -486,29 +524,6 @@ export function AccountProfileTab() {
|
||||
|
||||
{/* Cancel Email Change Dialog */}
|
||||
<AlertDialog open={showCancelEmailDialog} onOpenChange={setShowCancelEmailDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Cancel Email Change?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will cancel your pending email change to <strong>{pendingEmail}</strong>.
|
||||
Your email will remain as <strong>{user?.email}</strong>.
|
||||
You can start a new email change request at any time.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={cancellingEmail}>Keep Change</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleCancelEmailChange}
|
||||
disabled={cancellingEmail}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{cancellingEmail ? 'Cancelling...' : 'Yes, Cancel Change'}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Danger Zone */}
|
||||
<Card className="border-destructive">
|
||||
@@ -538,6 +553,28 @@ export function AccountProfileTab() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Cancel Email Change?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will cancel your pending email change to <strong>{pendingEmail}</strong>.
|
||||
Your email will remain as <strong>{user?.email}</strong>.
|
||||
You can start a new email change request at any time.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={cancellingEmail}>Keep Change</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleCancelEmailChange}
|
||||
disabled={cancellingEmail}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{cancellingEmail ? 'Cancelling...' : 'Yes, Cancel Change'}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Account Deletion Dialog */}
|
||||
<AccountDeletionDialog
|
||||
open={showDeletionDialog}
|
||||
|
||||
@@ -281,16 +281,16 @@ export function DataExportTab() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Personal Statistics */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BarChart3 className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Personal Statistics</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Statistics + Recent Activity Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Personal Statistics */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<BarChart3 className="w-5 h-5" />
|
||||
<CardTitle>Personal Statistics</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Your activity and contribution statistics on ThrillWiki
|
||||
</CardDescription>
|
||||
@@ -334,22 +334,16 @@ export function DataExportTab() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Export Your Data */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Download className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Export Your Data</h3>
|
||||
</div>
|
||||
|
||||
{/* Account Activity */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Download Your Data</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<Activity className="w-5 h-5" />
|
||||
<CardTitle>Account Activity</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Export all your ThrillWiki data in JSON format. This includes your profile, reviews, lists, and activity history.
|
||||
Recent account activity and changes
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
@@ -437,24 +431,6 @@ export function DataExportTab() {
|
||||
{exporting ? 'Exporting Data...' : 'Export My Data'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Account Activity */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Activity className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Account Activity</h3>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardDescription>
|
||||
Recent account activity and changes
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{recentActivity.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground text-center py-4">
|
||||
@@ -480,6 +456,104 @@ export function DataExportTab() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Export Your Data - Full Width */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Download className="w-5 h-5" />
|
||||
<CardTitle>Export Your Data</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Export all your ThrillWiki data in JSON format. This includes your profile, reviews, lists, and activity history.
|
||||
</CardDescription>
|
||||
</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>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Card, CardContent, CardDescription, CardHeader } from '@/components/ui/card';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
@@ -304,16 +304,17 @@ export function LocationTab() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Location Settings</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
{/* Location Settings + Personal Information Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Location Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className="w-5 h-5" />
|
||||
<CardTitle>Location Settings</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Set your location for better personalized content and timezone display.
|
||||
</CardDescription>
|
||||
@@ -425,18 +426,14 @@ export function LocationTab() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Personal Information</h3>
|
||||
</div>
|
||||
|
||||
{/* Personal Information */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-5 h-5" />
|
||||
<CardTitle>Personal Information</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Optional personal information that can be displayed on your profile.
|
||||
</CardDescription>
|
||||
@@ -462,16 +459,15 @@ export function LocationTab() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Ruler className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Units & Measurements</h3>
|
||||
</div>
|
||||
|
||||
{/* Unit Preferences + Accessibility Options Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Unit Preferences */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Ruler className="w-5 h-5" />
|
||||
<CardTitle>Units & Measurements</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Choose your preferred measurement system for displaying distances, speeds, and other measurements.
|
||||
</CardDescription>
|
||||
@@ -499,18 +495,14 @@ export function LocationTab() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Accessibility className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Accessibility Options</h3>
|
||||
</div>
|
||||
|
||||
{/* Accessibility Options */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Accessibility className="w-5 h-5" />
|
||||
<CardTitle>Accessibility Options</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Customize the interface to meet your accessibility needs.
|
||||
</CardDescription>
|
||||
@@ -564,6 +556,7 @@ export function LocationTab() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={saving}>
|
||||
{saving ? 'Saving...' : 'Save Settings'}
|
||||
|
||||
@@ -239,13 +239,16 @@ export function NotificationsTab() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Notification Channels</CardTitle>
|
||||
<CardDescription>
|
||||
Choose which channels you'd like to receive notifications through
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
{/* Notification Channels + Frequency Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Notification Channels */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Notification Channels</CardTitle>
|
||||
<CardDescription>
|
||||
Choose which channels you'd like to receive notifications through
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
@@ -298,16 +301,17 @@ export function NotificationsTab() {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Notification Frequency</CardTitle>
|
||||
<CardDescription>
|
||||
Control how often you receive notifications
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
{/* Notification Frequency */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Notification Frequency</CardTitle>
|
||||
<CardDescription>
|
||||
Control how often you receive notifications
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Digest Frequency</Label>
|
||||
@@ -357,9 +361,11 @@ export function NotificationsTab() {
|
||||
Limit the number of notifications you receive per hour
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Workflow Preferences - Full Width */}
|
||||
{Object.keys(groupedTemplates).map((category) => (
|
||||
<Card key={category}>
|
||||
<CardHeader>
|
||||
|
||||
@@ -234,17 +234,17 @@ export function PrivacyTab() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
{/* Profile Visibility */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Eye className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Profile Visibility</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
{/* Profile Visibility + Activity & Content Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Profile Visibility */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Eye className="w-5 h-5" />
|
||||
<CardTitle>Profile Visibility</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Control who can see your profile and personal information.
|
||||
</CardDescription>
|
||||
@@ -363,19 +363,14 @@ export function PrivacyTab() {
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Activity & Content */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Activity & Content</h3>
|
||||
</div>
|
||||
|
||||
{/* Activity & Content */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="w-5 h-5" />
|
||||
<CardTitle>Activity & Content</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Control the visibility of your activities and content.
|
||||
</CardDescription>
|
||||
@@ -407,21 +402,17 @@ export function PrivacyTab() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Search & Discovery */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Search className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Search & Discovery</h3>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardDescription>
|
||||
Control how others can find and discover your profile.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Search className="w-5 h-5" />
|
||||
<CardTitle>Search & Discovery</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Control how others can find and discover your profile.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
@@ -437,28 +428,22 @@ export function PrivacyTab() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Blocked Users */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<UserX className="w-5 h-5" />
|
||||
<h3 className="text-lg font-medium">Blocked Users</h3>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardDescription>
|
||||
Manage users you have blocked from interacting with you.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<BlockedUsers />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<UserX className="w-5 h-5" />
|
||||
<CardTitle>Blocked Users</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Manage users you have blocked from interacting with you.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<BlockedUsers />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="flex justify-end">
|
||||
|
||||
@@ -83,58 +83,48 @@ export default function UserSettings() {
|
||||
<div className="min-h-screen bg-background">
|
||||
<Header />
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<Settings className="w-8 h-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Settings</h1>
|
||||
<p className="text-muted-foreground">Manage your account preferences and privacy settings</p>
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<Settings className="w-8 h-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Settings</h1>
|
||||
<p className="text-muted-foreground">Manage your account preferences and privacy settings</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Settings Tabs */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
||||
<TabsList className="grid w-full grid-cols-2 lg:grid-cols-6 h-auto p-1">
|
||||
{/* Settings Tabs */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
||||
<TabsList className="grid w-full grid-cols-2 lg:grid-cols-6 h-auto p-1">
|
||||
{tabs.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
return (
|
||||
<TabsTrigger
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
className="flex flex-col sm:flex-row items-center gap-2 h-auto py-3 px-2 text-xs sm:text-sm"
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
<span className="hidden sm:inline lg:inline">{tab.label}</span>
|
||||
<span className="sm:hidden lg:hidden">{tab.label.split(' ')[0]}</span>
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
|
||||
{tabs.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
const Component = tab.component;
|
||||
return (
|
||||
<TabsTrigger
|
||||
<TabsContent
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
className="flex flex-col sm:flex-row items-center gap-2 h-auto py-3 px-2 text-xs sm:text-sm"
|
||||
className="focus-visible:outline-none"
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
<span className="hidden sm:inline lg:inline">{tab.label}</span>
|
||||
<span className="sm:hidden lg:hidden">{tab.label.split(' ')[0]}</span>
|
||||
</TabsTrigger>
|
||||
<Component />
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
|
||||
{tabs.map((tab) => {
|
||||
const Component = tab.component;
|
||||
return (
|
||||
<TabsContent
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
className="space-y-6 focus-visible:outline-none"
|
||||
>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<tab.icon className="w-5 h-5" />
|
||||
{tab.label}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Component />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user