Refactor: Implement desktop layout modernization

This commit is contained in:
gpt-engineer-app[bot]
2025-10-14 20:16:24 +00:00
parent 2eec20f653
commit b8f2889b1d
6 changed files with 387 additions and 302 deletions

View File

@@ -244,7 +244,7 @@ export function AccountProfileTab() {
const isDeactivated = profile?.deactivated || false; const isDeactivated = profile?.deactivated || false;
return ( return (
<div className="space-y-8"> <div className="space-y-6">
{/* Deletion Status Banner */} {/* Deletion Status Banner */}
{deletionRequest && ( {deletionRequest && (
<DeletionStatusBanner <DeletionStatusBanner
@@ -253,9 +253,15 @@ export function AccountProfileTab() {
/> />
)} )}
{/* Profile Picture + Account Info Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Profile Picture */} {/* Profile Picture */}
<div className="space-y-4"> <Card>
<h3 className="text-lg font-medium">Profile Picture</h3> <CardHeader>
<CardTitle>Profile Picture</CardTitle>
<CardDescription>Upload your profile picture</CardDescription>
</CardHeader>
<CardContent>
<PhotoUpload <PhotoUpload
variant="avatar" variant="avatar"
maxFiles={1} maxFiles={1}
@@ -267,11 +273,72 @@ export function AccountProfileTab() {
handleError(new Error(error), { action: 'Upload avatar' }); 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> </div>
<Separator /> <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>
{/* Profile Information */} {/* Profile Information */}
<Card>
<CardHeader>
<CardTitle>Profile Information</CardTitle>
<CardDescription>Update your public profile details</CardDescription>
</CardHeader>
<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> <h3 className="text-lg font-medium">Profile Information</h3>
@@ -419,60 +486,31 @@ export function AccountProfileTab() {
</p> </p>
)} )}
</form> </form>
</CardContent>
</Card>
<Separator /> {/* Danger Zone */}
<Card className="border-destructive/50">
{/* Account Information */} <CardHeader>
<div className="space-y-4"> <CardTitle className="text-destructive">Danger Zone</CardTitle>
<h3 className="text-lg font-medium">Account Information</h3> <CardDescription>Irreversible account actions</CardDescription>
<div className="space-y-4"> </CardHeader>
{pendingEmail && ( <CardContent>
<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 <Button
variant="outline" variant="destructive"
size="sm" onClick={() => setShowDeletionDialog(true)}
onClick={() => setShowEmailDialog(true)} disabled={!!deletionRequest}
disabled={!!pendingEmail}
> >
<Mail className="w-4 h-4 mr-2" /> <Trash2 className="w-4 h-4 mr-2" />
Change Email Delete Account
</Button> </Button>
</div> {deletionRequest && (
<p className="text-sm text-muted-foreground mt-2">
<Separator /> Account deletion already in progress
<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> </p>
</div> )}
</div> </CardContent>
</div> </Card>
</div>
{/* Email Change Dialog */} {/* Email Change Dialog */}
{user && ( {user && (
@@ -486,29 +524,6 @@ export function AccountProfileTab() {
{/* Cancel Email Change Dialog */} {/* Cancel Email Change Dialog */}
<AlertDialog open={showCancelEmailDialog} onOpenChange={setShowCancelEmailDialog}> <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 */} {/* Danger Zone */}
<Card className="border-destructive"> <Card className="border-destructive">
@@ -538,6 +553,28 @@ export function AccountProfileTab() {
</CardContent> </CardContent>
</Card> </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 */} {/* Account Deletion Dialog */}
<AccountDeletionDialog <AccountDeletionDialog
open={showDeletionDialog} open={showDeletionDialog}

View File

@@ -281,16 +281,16 @@ export function DataExportTab() {
} }
return ( return (
<div className="space-y-8"> <div className="space-y-6">
{/* Statistics + Recent Activity Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Personal Statistics */} {/* 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>
<Card> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center gap-2">
<BarChart3 className="w-5 h-5" />
<CardTitle>Personal Statistics</CardTitle>
</div>
<CardDescription> <CardDescription>
Your activity and contribution statistics on ThrillWiki Your activity and contribution statistics on ThrillWiki
</CardDescription> </CardDescription>
@@ -334,20 +334,136 @@ export function DataExportTab() {
)} )}
</CardContent> </CardContent>
</Card> </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> <Card>
<CardHeader> <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>
Recent account activity and changes
</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>
<CardContent>
{recentActivity.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-4">
No recent activity to display
</p>
) : (
<div className="space-y-4">
{recentActivity.map((activity) => (
<div key={activity.id} className="flex items-start gap-3 pb-4 border-b last:border-0 last:pb-0">
<Activity className="w-4 h-4 text-muted-foreground mt-1" />
<div className="flex-1 space-y-1">
<p className="text-sm font-medium">
{formatActionName(activity.action)}
</p>
<p className="text-xs text-muted-foreground">
{formatDate(activity.created_at)}
</p>
</div>
</div>
))}
</div>
)}
</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> <CardDescription>
Export all your ThrillWiki data in JSON format. This includes your profile, reviews, lists, and activity history. Export all your ThrillWiki data in JSON format. This includes your profile, reviews, lists, and activity history.
</CardDescription> </CardDescription>
@@ -439,47 +555,5 @@ export function DataExportTab() {
</CardContent> </CardContent>
</Card> </Card>
</div> </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">
No recent activity to display
</p>
) : (
<div className="space-y-4">
{recentActivity.map((activity) => (
<div key={activity.id} className="flex items-start gap-3 pb-4 border-b last:border-0 last:pb-0">
<Activity className="w-4 h-4 text-muted-foreground mt-1" />
<div className="flex-1 space-y-1">
<p className="text-sm font-medium">
{formatActionName(activity.action)}
</p>
<p className="text-xs text-muted-foreground">
{formatDate(activity.created_at)}
</p>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div>
); );
} }

View File

@@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; 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 { Separator } from '@/components/ui/separator';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
@@ -304,16 +304,17 @@ export function LocationTab() {
} }
return ( return (
<div className="space-y-8"> <div className="space-y-6">
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<div className="space-y-4"> {/* Location Settings + Personal Information Grid */}
<div className="flex items-center gap-2"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<MapPin className="w-5 h-5" /> {/* Location Settings */}
<h3 className="text-lg font-medium">Location Settings</h3>
</div>
<Card> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center gap-2">
<MapPin className="w-5 h-5" />
<CardTitle>Location Settings</CardTitle>
</div>
<CardDescription> <CardDescription>
Set your location for better personalized content and timezone display. Set your location for better personalized content and timezone display.
</CardDescription> </CardDescription>
@@ -425,18 +426,14 @@ export function LocationTab() {
</div> </div>
</CardContent> </CardContent>
</Card> </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> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center gap-2">
<Calendar className="w-5 h-5" />
<CardTitle>Personal Information</CardTitle>
</div>
<CardDescription> <CardDescription>
Optional personal information that can be displayed on your profile. Optional personal information that can be displayed on your profile.
</CardDescription> </CardDescription>
@@ -462,16 +459,15 @@ export function LocationTab() {
</Card> </Card>
</div> </div>
<Separator /> {/* Unit Preferences + Accessibility Options Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="space-y-4"> {/* Unit Preferences */}
<div className="flex items-center gap-2">
<Ruler className="w-5 h-5" />
<h3 className="text-lg font-medium">Units & Measurements</h3>
</div>
<Card> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center gap-2">
<Ruler className="w-5 h-5" />
<CardTitle>Units & Measurements</CardTitle>
</div>
<CardDescription> <CardDescription>
Choose your preferred measurement system for displaying distances, speeds, and other measurements. Choose your preferred measurement system for displaying distances, speeds, and other measurements.
</CardDescription> </CardDescription>
@@ -499,18 +495,14 @@ export function LocationTab() {
</div> </div>
</CardContent> </CardContent>
</Card> </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> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center gap-2">
<Accessibility className="w-5 h-5" />
<CardTitle>Accessibility Options</CardTitle>
</div>
<CardDescription> <CardDescription>
Customize the interface to meet your accessibility needs. Customize the interface to meet your accessibility needs.
</CardDescription> </CardDescription>
@@ -564,6 +556,7 @@ export function LocationTab() {
</Card> </Card>
</div> </div>
{/* Save Button */}
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit" disabled={saving}> <Button type="submit" disabled={saving}>
{saving ? 'Saving...' : 'Save Settings'} {saving ? 'Saving...' : 'Save Settings'}

View File

@@ -239,6 +239,9 @@ export function NotificationsTab() {
</Card> </Card>
)} )}
{/* Notification Channels + Frequency Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Notification Channels */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Notification Channels</CardTitle> <CardTitle>Notification Channels</CardTitle>
@@ -301,6 +304,7 @@ export function NotificationsTab() {
</CardContent> </CardContent>
</Card> </Card>
{/* Notification Frequency */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Notification Frequency</CardTitle> <CardTitle>Notification Frequency</CardTitle>
@@ -359,7 +363,9 @@ export function NotificationsTab() {
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div>
{/* Workflow Preferences - Full Width */}
{Object.keys(groupedTemplates).map((category) => ( {Object.keys(groupedTemplates).map((category) => (
<Card key={category}> <Card key={category}>
<CardHeader> <CardHeader>

View File

@@ -234,17 +234,17 @@ export function PrivacyTab() {
}; };
return ( return (
<div className="space-y-8"> <div className="space-y-6">
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <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 */} {/* 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>
<Card> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center gap-2">
<Eye className="w-5 h-5" />
<CardTitle>Profile Visibility</CardTitle>
</div>
<CardDescription> <CardDescription>
Control who can see your profile and personal information. Control who can see your profile and personal information.
</CardDescription> </CardDescription>
@@ -363,19 +363,14 @@ export function PrivacyTab() {
</CardContent> </CardContent>
</Card> </Card>
</div>
<Separator />
{/* Activity & Content */} {/* 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>
<Card> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center gap-2">
<Shield className="w-5 h-5" />
<CardTitle>Activity & Content</CardTitle>
</div>
<CardDescription> <CardDescription>
Control the visibility of your activities and content. Control the visibility of your activities and content.
</CardDescription> </CardDescription>
@@ -407,17 +402,13 @@ export function PrivacyTab() {
</Card> </Card>
</div> </div>
<Separator />
{/* Search & Discovery */} {/* 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> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center gap-2">
<Search className="w-5 h-5" />
<CardTitle>Search & Discovery</CardTitle>
</div>
<CardDescription> <CardDescription>
Control how others can find and discover your profile. Control how others can find and discover your profile.
</CardDescription> </CardDescription>
@@ -437,19 +428,14 @@ export function PrivacyTab() {
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div>
<Separator />
{/* Blocked Users */} {/* 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> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center gap-2">
<UserX className="w-5 h-5" />
<CardTitle>Blocked Users</CardTitle>
</div>
<CardDescription> <CardDescription>
Manage users you have blocked from interacting with you. Manage users you have blocked from interacting with you.
</CardDescription> </CardDescription>
@@ -458,7 +444,6 @@ export function PrivacyTab() {
<BlockedUsers /> <BlockedUsers />
</CardContent> </CardContent>
</Card> </Card>
</div>
{/* Save Button */} {/* Save Button */}
<div className="flex justify-end"> <div className="flex justify-end">

View File

@@ -83,7 +83,7 @@ export default function UserSettings() {
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<Header /> <Header />
<div className="container mx-auto px-4 py-8"> <div className="container mx-auto px-4 py-8">
<div className="max-w-6xl mx-auto"> <div className="max-w-7xl mx-auto">
{/* Header */} {/* Header */}
<div className="flex items-center gap-3 mb-8"> <div className="flex items-center gap-3 mb-8">
<Settings className="w-8 h-8 text-primary" /> <Settings className="w-8 h-8 text-primary" />
@@ -118,19 +118,9 @@ export default function UserSettings() {
<TabsContent <TabsContent
key={tab.id} key={tab.id}
value={tab.id} value={tab.id}
className="space-y-6 focus-visible:outline-none" className="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 /> <Component />
</CardContent>
</Card>
</TabsContent> </TabsContent>
); );
})} })}