feat: Implement settings dropdown and page components
- Added `SettingsDropdown` component for selecting settings sections with icons and descriptions. - Created `SettingsPage` component to manage user settings, including account details, notifications, calendar availability, privacy, and appearance. - Introduced `SettingsSwitcher` for selecting options within settings. - Integrated command and dialog components for improved user interaction. - Updated `UserDropdown` to include links for settings and logout. - Refactored button styles and card footer layout for consistency. - Added popover functionality for dropdown menus. - Updated dependencies in `yarn.lock` for new components.
This commit is contained in:
parent
6a5ad338ba
commit
5d81288479
9 changed files with 770 additions and 497 deletions
|
@ -1,482 +1,5 @@
|
||||||
import { Button } from '@/components/ui/button';
|
import SettingsPage from '@/components/misc/settings-page';
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
||||||
import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function Page() {
|
||||||
return (
|
return <SettingsPage />;
|
||||||
<div className='fixed inset-0 flex items-center justify-center p-4 bg-background/50 backdrop-blur-sm'>
|
|
||||||
<div className='rounded-lg border bg-card text-card-foreground shadow-xl max-w-[700px] w-full h-auto max-h-[calc(100vh-2rem)] flex flex-col'>
|
|
||||||
<Tabs
|
|
||||||
defaultValue='general'
|
|
||||||
className='w-full flex flex-col flex-grow min-h-0'
|
|
||||||
>
|
|
||||||
<TabsList className='grid w-full grid-cols-3 sm:grid-cols-5'>
|
|
||||||
<TabsTrigger value='general'>Account</TabsTrigger>
|
|
||||||
<TabsTrigger value='notifications'>Notifications</TabsTrigger>
|
|
||||||
<TabsTrigger value='calendarAvailability'>Calendar</TabsTrigger>
|
|
||||||
<TabsTrigger value='sharingPrivacy'>Privacy</TabsTrigger>
|
|
||||||
<TabsTrigger value='appearance'>Appearance</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value='general' className='flex-grow overflow-hidden'>
|
|
||||||
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
|
||||||
<ScrollableSettingsWrapper>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Account Settings</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Manage your account details and preferences.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className='space-y-6'>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='displayName'>Display Name</Label>
|
|
||||||
<Input id='displayName' placeholder='Your Name' />
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='email'>Email Address</Label>
|
|
||||||
<Input
|
|
||||||
id='email'
|
|
||||||
type='email'
|
|
||||||
placeholder='your.email@example.com'
|
|
||||||
readOnly
|
|
||||||
value='user-email@example.com'
|
|
||||||
/>
|
|
||||||
<p className='text-sm text-muted-foreground'>
|
|
||||||
Email is managed by your SSO provider.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='profilePicture'>Profile Picture</Label>
|
|
||||||
<Input id='profilePicture' type='file' />
|
|
||||||
<p className='text-sm text-muted-foreground'>
|
|
||||||
Upload a new profile picture.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='timezone'>Timezone</Label>
|
|
||||||
<Input id='displayName' placeholder='Europe/Berlin' />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='language'>Language</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='language'>
|
|
||||||
<SelectValue placeholder='Select language' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='en'>English</SelectItem>
|
|
||||||
<SelectItem value='de'>German</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='pt-4'>
|
|
||||||
<Button variant='secondary'>Delete Account</Button>
|
|
||||||
<p className='text-sm text-muted-foreground pt-1'>
|
|
||||||
Permanently delete your account and all associated data.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</ScrollableSettingsWrapper>
|
|
||||||
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
|
||||||
<Button variant='secondary'>Exit</Button>
|
|
||||||
<Button>Save Changes</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent
|
|
||||||
value='notifications'
|
|
||||||
className='flex-grow overflow-hidden'
|
|
||||||
>
|
|
||||||
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
|
||||||
<ScrollableSettingsWrapper>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Notification Preferences</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Choose how you want to be notified.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className='space-y-6'>
|
|
||||||
<div className='flex items-center justify-between space-x-2 p-3 rounded-md border'>
|
|
||||||
<Label
|
|
||||||
htmlFor='masterEmailNotifications'
|
|
||||||
className='font-normal'
|
|
||||||
>
|
|
||||||
Enable All Email Notifications
|
|
||||||
</Label>
|
|
||||||
<Switch id='masterEmailNotifications' />
|
|
||||||
</div>
|
|
||||||
<div className='space-y-4 pl-2 border-l-2 ml-2'>
|
|
||||||
<div className='flex items-center justify-between space-x-2'>
|
|
||||||
<Label
|
|
||||||
htmlFor='newMeetingBookings'
|
|
||||||
className='font-normal'
|
|
||||||
>
|
|
||||||
New Meeting Bookings
|
|
||||||
</Label>
|
|
||||||
<Switch id='newMeetingBookings' />
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center justify-between space-x-2'>
|
|
||||||
<Label
|
|
||||||
htmlFor='meetingConfirmations'
|
|
||||||
className='font-normal'
|
|
||||||
>
|
|
||||||
Meeting Confirmations/Cancellations
|
|
||||||
</Label>
|
|
||||||
<Switch id='meetingConfirmations' />
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center justify-between space-x-2'>
|
|
||||||
<Label
|
|
||||||
htmlFor='enableMeetingReminders'
|
|
||||||
className='font-normal'
|
|
||||||
>
|
|
||||||
Meeting Reminders
|
|
||||||
</Label>
|
|
||||||
<Switch id='enableMeetingReminders' />
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2 pl-6'>
|
|
||||||
<Label htmlFor='remindBefore'>Remind me before</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='remindBefore'>
|
|
||||||
<SelectValue placeholder='Select reminder time' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='15m'>15 minutes</SelectItem>
|
|
||||||
<SelectItem value='30m'>30 minutes</SelectItem>
|
|
||||||
<SelectItem value='1h'>1 hour</SelectItem>
|
|
||||||
<SelectItem value='1d'>1 day</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center justify-between space-x-2'>
|
|
||||||
<Label htmlFor='friendRequests' className='font-normal'>
|
|
||||||
Friend Requests
|
|
||||||
</Label>
|
|
||||||
<Switch id='friendRequests' />
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center justify-between space-x-2'>
|
|
||||||
<Label htmlFor='groupUpdates' className='font-normal'>
|
|
||||||
Group Invitations/Updates
|
|
||||||
</Label>
|
|
||||||
<Switch id='groupUpdates' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</ScrollableSettingsWrapper>
|
|
||||||
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
|
||||||
<Button variant='secondary'>Exit</Button>
|
|
||||||
<Button>Save Changes</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent
|
|
||||||
value='calendarAvailability'
|
|
||||||
className='flex-grow overflow-hidden'
|
|
||||||
>
|
|
||||||
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
|
||||||
<ScrollableSettingsWrapper>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Calendar & Availability</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Manage your calendar display, default availability, and iCal
|
|
||||||
integrations.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className='space-y-6'>
|
|
||||||
<fieldset className='space-y-4 p-4 border rounded-md'>
|
|
||||||
<legend className='text-sm font-medium px-1'>
|
|
||||||
Display
|
|
||||||
</legend>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='defaultCalendarView'>
|
|
||||||
Default Calendar View
|
|
||||||
</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='defaultCalendarView'>
|
|
||||||
<SelectValue placeholder='Select view' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='day'>Day</SelectItem>
|
|
||||||
<SelectItem value='week'>Week</SelectItem>
|
|
||||||
<SelectItem value='month'>Month</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='weekStartsOn'>Week Starts On</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='weekStartsOn'>
|
|
||||||
<SelectValue placeholder='Select day' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='sunday'>Sunday</SelectItem>
|
|
||||||
<SelectItem value='monday'>Monday</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center justify-between space-x-2'>
|
|
||||||
<Label htmlFor='showWeekends' className='font-normal'>
|
|
||||||
Show Weekends
|
|
||||||
</Label>
|
|
||||||
<Switch id='showWeekends' defaultChecked />
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset className='space-y-4 p-4 border rounded-md'>
|
|
||||||
<legend className='text-sm font-medium px-1'>
|
|
||||||
Availability
|
|
||||||
</legend>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label>Working Hours</Label>
|
|
||||||
<p className='text-sm text-muted-foreground'>
|
|
||||||
Define your typical available hours (e.g.,
|
|
||||||
Monday-Friday, 9 AM - 5 PM).
|
|
||||||
</p>
|
|
||||||
<Button variant='outline_muted' size='sm'>
|
|
||||||
Set Working Hours
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='minNoticeBooking'>
|
|
||||||
Minimum Notice for Bookings
|
|
||||||
</Label>
|
|
||||||
<p className='text-sm text-muted-foreground'>
|
|
||||||
Min time before a booking can be made.
|
|
||||||
</p>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Input
|
|
||||||
id='bookingWindow'
|
|
||||||
type='text'
|
|
||||||
placeholder='e.g., 1h'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='bookingWindow'>
|
|
||||||
Booking Window (days in advance)
|
|
||||||
</Label>
|
|
||||||
<p className='text-sm text-muted-foreground'>
|
|
||||||
Max time in advance a booking can be made.
|
|
||||||
</p>
|
|
||||||
<Input
|
|
||||||
id='bookingWindow'
|
|
||||||
type='number'
|
|
||||||
placeholder='e.g., 30d'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset className='space-y-4 p-4 border rounded-md'>
|
|
||||||
<legend className='text-sm font-medium px-1'>
|
|
||||||
iCalendar Integration
|
|
||||||
</legend>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='icalImport'>Import iCal Feed URL</Label>
|
|
||||||
<Input
|
|
||||||
id='icalImport'
|
|
||||||
type='url'
|
|
||||||
placeholder='https://calendar.example.com/feed.ics'
|
|
||||||
/>
|
|
||||||
<Button size='sm' className='mt-1'>
|
|
||||||
Add Feed
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label>Export Your Calendar</Label>
|
|
||||||
<Button variant='outline_muted' size='sm'>
|
|
||||||
Get iCal Export URL
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant='outline_muted'
|
|
||||||
size='sm'
|
|
||||||
className='ml-2'
|
|
||||||
>
|
|
||||||
Download .ics File
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</CardContent>
|
|
||||||
</ScrollableSettingsWrapper>
|
|
||||||
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
|
||||||
<Button variant='secondary'>Exit</Button>
|
|
||||||
<Button>Save Changes</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent
|
|
||||||
value='sharingPrivacy'
|
|
||||||
className='flex-grow overflow-hidden'
|
|
||||||
>
|
|
||||||
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
|
||||||
<ScrollableSettingsWrapper>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Sharing & Privacy</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Control who can see your calendar and book time with you.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className='space-y-6'>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='defaultVisibility'>
|
|
||||||
Default Calendar Visibility
|
|
||||||
</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='defaultVisibility'>
|
|
||||||
<SelectValue placeholder='Select visibility' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='private'>
|
|
||||||
Private (Only You)
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value='freebusy'>
|
|
||||||
Free/Busy for Friends
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value='fulldetails'>
|
|
||||||
Full Details for Friends
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='whoCanSeeFull'>
|
|
||||||
Who Can See Your Full Calendar Details?
|
|
||||||
</Label>
|
|
||||||
<p className='text-sm text-muted-foreground'>
|
|
||||||
(Override for Default Visibility)
|
|
||||||
<br />
|
|
||||||
<span className='text-sm text-muted-foreground'>
|
|
||||||
This setting will override the default visibility for
|
|
||||||
your calendar. You can set specific friends or groups to
|
|
||||||
see your full calendar details.
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='whoCanSeeFull'>
|
|
||||||
<SelectValue placeholder='Select audience' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='me'>Only Me</SelectItem>
|
|
||||||
<SelectItem value='friends'>My Friends</SelectItem>
|
|
||||||
<SelectItem value='specific'>
|
|
||||||
Specific Friends/Groups (manage separately)
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='whoCanBook'>
|
|
||||||
Who Can Book Time With You?
|
|
||||||
</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='whoCanBook'>
|
|
||||||
<SelectValue placeholder='Select audience' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='none'>No One</SelectItem>
|
|
||||||
<SelectItem value='friends'>My Friends</SelectItem>
|
|
||||||
<SelectItem value='specific'>
|
|
||||||
Specific Friends/Groups (manage separately)
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label>Blocked Users</Label>
|
|
||||||
<Button variant='outline_muted'>
|
|
||||||
Manage Blocked Users
|
|
||||||
</Button>
|
|
||||||
<p className='text-sm text-muted-foreground'>
|
|
||||||
Prevent specific users from seeing your calendar or
|
|
||||||
booking time.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</ScrollableSettingsWrapper>
|
|
||||||
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
|
||||||
<Button variant='secondary'>Exit</Button>
|
|
||||||
<Button>Save Changes</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value='appearance' className='flex-grow overflow-hidden'>
|
|
||||||
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
|
||||||
<ScrollableSettingsWrapper>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Appearance</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Customize the look and feel of the application.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className='space-y-6'>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='theme'>Theme</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='theme'>
|
|
||||||
<SelectValue placeholder='Select theme' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='light'>Light</SelectItem>
|
|
||||||
<SelectItem value='dark'>Dark</SelectItem>
|
|
||||||
<SelectItem value='system'>System Default</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='dateFormat'>Date Format</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='dateFormat'>
|
|
||||||
<SelectValue placeholder='Select date format' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='ddmmyyyy'>DD/MM/YYYY</SelectItem>
|
|
||||||
<SelectItem value='mmddyyyy'>MM/DD/YYYY</SelectItem>
|
|
||||||
<SelectItem value='yyyymmdd'>YYYY-MM-DD</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className='space-y-2'>
|
|
||||||
<Label htmlFor='timeFormat'>Time Format</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id='timeFormat'>
|
|
||||||
<SelectValue placeholder='Select time format' />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value='24h'>24-hour</SelectItem>
|
|
||||||
<SelectItem value='12h'>12-hour</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</ScrollableSettingsWrapper>
|
|
||||||
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
|
||||||
<Button variant='secondary'>Exit</Button>
|
|
||||||
<Button>Save Changes</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
158
src/components/misc/settings-dropdown.tsx
Normal file
158
src/components/misc/settings-dropdown.tsx
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from '@/components/ui/command';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
Check,
|
||||||
|
ChevronDown,
|
||||||
|
User,
|
||||||
|
Bell,
|
||||||
|
Calendar,
|
||||||
|
Shield,
|
||||||
|
Palette,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
interface SettingsSection {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
description: string;
|
||||||
|
icon: React.ComponentType<{ className?: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SettingsDropdownProps {
|
||||||
|
currentSection: string;
|
||||||
|
onSectionChange: (section: string) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsSections: SettingsSection[] = [
|
||||||
|
{
|
||||||
|
label: 'Account',
|
||||||
|
value: 'general',
|
||||||
|
description: 'Manage your account details and preferences',
|
||||||
|
icon: User,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Notifications',
|
||||||
|
value: 'notifications',
|
||||||
|
description: 'Choose how you want to be notified',
|
||||||
|
icon: Bell,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Calendar',
|
||||||
|
value: 'calendarAvailability',
|
||||||
|
description: 'Manage calendar display and availability',
|
||||||
|
icon: Calendar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Privacy',
|
||||||
|
value: 'sharingPrivacy',
|
||||||
|
description: 'Control who can see your calendar',
|
||||||
|
icon: Shield,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Appearance',
|
||||||
|
value: 'appearance',
|
||||||
|
description: 'Customize the look and feel',
|
||||||
|
icon: Palette,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function SettingsDropdown({
|
||||||
|
currentSection,
|
||||||
|
onSectionChange,
|
||||||
|
className,
|
||||||
|
}: SettingsDropdownProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const currentSectionData = settingsSections.find(
|
||||||
|
(section) => section.value === currentSection,
|
||||||
|
);
|
||||||
|
const CurrentIcon = currentSectionData?.icon || User;
|
||||||
|
|
||||||
|
const handleSelect = (value: string) => {
|
||||||
|
onSectionChange(value);
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('w-full max-w-md', className)}>
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='outline_muted'
|
||||||
|
role='combobox'
|
||||||
|
aria-expanded={open}
|
||||||
|
className='w-full justify-between bg-white text-black h-auto py-3'
|
||||||
|
>
|
||||||
|
<div className='flex items-center gap-3'>
|
||||||
|
<CurrentIcon className='h-4 w-4 text-muted-foreground' />
|
||||||
|
<div className='flex flex-col items-start text-left'>
|
||||||
|
<span className='font-medium'>{currentSectionData?.label}</span>
|
||||||
|
<span className='text-xs text-muted-foreground'>
|
||||||
|
{currentSectionData?.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ChevronDown className='ml-2 h-4 w-4 shrink-0 opacity-50' />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className='w-full p-0' align='start'>
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder='Search settings...' />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No settings found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{settingsSections.map((section) => {
|
||||||
|
const Icon = section.icon;
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={section.value}
|
||||||
|
value={section.value}
|
||||||
|
onSelect={() => handleSelect(section.value)}
|
||||||
|
className='flex items-center justify-between p-3'
|
||||||
|
>
|
||||||
|
<div className='flex items-center gap-3'>
|
||||||
|
<Icon className='h-4 w-4 text-muted-foreground' />
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<span className='font-medium'>{section.label}</span>
|
||||||
|
<span className='text-xs text-muted-foreground'>
|
||||||
|
{section.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
'ml-2 h-4 w-4',
|
||||||
|
currentSection === section.value
|
||||||
|
? 'opacity-100'
|
||||||
|
: 'opacity-0',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
467
src/components/misc/settings-page.tsx
Normal file
467
src/components/misc/settings-page.tsx
Normal file
|
@ -0,0 +1,467 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { SettingsDropdown } from '@/components/misc/settings-dropdown';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
export default function SettingsPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [currentSection, setCurrentSection] = useState('general');
|
||||||
|
|
||||||
|
const renderSettingsContent = () => {
|
||||||
|
switch (currentSection) {
|
||||||
|
case 'general':
|
||||||
|
return (
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Account Settings</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Manage your account details and preferences.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='displayName'>Display Name</Label>
|
||||||
|
<Input id='displayName' placeholder='Your Name' />
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='email'>Email Address</Label>
|
||||||
|
<Input
|
||||||
|
id='email'
|
||||||
|
type='email'
|
||||||
|
placeholder='your.email@example.com'
|
||||||
|
readOnly
|
||||||
|
value='user-email@example.com'
|
||||||
|
/>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Email is managed by your SSO provider.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='profilePicture'>Profile Picture</Label>
|
||||||
|
<Input id='profilePicture' type='file' />
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Upload a new profile picture.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='timezone'>Timezone</Label>
|
||||||
|
<Input id='displayName' placeholder='Europe/Berlin' />
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='language'>Language</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='language'>
|
||||||
|
<SelectValue placeholder='Select language' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='en'>English</SelectItem>
|
||||||
|
<SelectItem value='de'>German</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='pt-4'>
|
||||||
|
<Button variant='secondary'>Delete Account</Button>
|
||||||
|
<p className='text-sm text-muted-foreground pt-1'>
|
||||||
|
Permanently delete your account and all associated data.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'notifications':
|
||||||
|
return (
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Notification Preferences</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Choose how you want to be notified.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<div className='flex items-center justify-between space-x-2 p-3 rounded-md border'>
|
||||||
|
<Label
|
||||||
|
htmlFor='masterEmailNotifications'
|
||||||
|
className='font-normal'
|
||||||
|
>
|
||||||
|
Enable All Email Notifications
|
||||||
|
</Label>
|
||||||
|
<Switch id='masterEmailNotifications' />
|
||||||
|
</div>
|
||||||
|
<div className='space-y-4 pl-2 border-l-2 ml-2'>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label htmlFor='newMeetingBookings' className='font-normal'>
|
||||||
|
New Meeting Bookings
|
||||||
|
</Label>
|
||||||
|
<Switch id='newMeetingBookings' />
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label
|
||||||
|
htmlFor='meetingConfirmations'
|
||||||
|
className='font-normal'
|
||||||
|
>
|
||||||
|
Meeting Confirmations/Cancellations
|
||||||
|
</Label>
|
||||||
|
<Switch id='meetingConfirmations' />
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label
|
||||||
|
htmlFor='enableMeetingReminders'
|
||||||
|
className='font-normal'
|
||||||
|
>
|
||||||
|
Meeting Reminders
|
||||||
|
</Label>
|
||||||
|
<Switch id='enableMeetingReminders' />
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2 pl-6'>
|
||||||
|
<Label htmlFor='remindBefore'>Remind me before</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='remindBefore'>
|
||||||
|
<SelectValue placeholder='Select reminder time' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='15m'>15 minutes</SelectItem>
|
||||||
|
<SelectItem value='30m'>30 minutes</SelectItem>
|
||||||
|
<SelectItem value='1h'>1 hour</SelectItem>
|
||||||
|
<SelectItem value='1d'>1 day</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label htmlFor='friendRequests' className='font-normal'>
|
||||||
|
Friend Requests
|
||||||
|
</Label>
|
||||||
|
<Switch id='friendRequests' />
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label htmlFor='groupUpdates' className='font-normal'>
|
||||||
|
Group Invitations/Updates
|
||||||
|
</Label>
|
||||||
|
<Switch id='groupUpdates' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'calendarAvailability':
|
||||||
|
return (
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Calendar & Availability</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Manage your calendar display, default availability, and iCal
|
||||||
|
integrations.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<fieldset className='space-y-4 p-4 border rounded-md'>
|
||||||
|
<legend className='text-sm font-medium px-1'>Display</legend>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='defaultCalendarView'>
|
||||||
|
Default Calendar View
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='defaultCalendarView'>
|
||||||
|
<SelectValue placeholder='Select view' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='day'>Day</SelectItem>
|
||||||
|
<SelectItem value='week'>Week</SelectItem>
|
||||||
|
<SelectItem value='month'>Month</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='weekStartsOn'>Week Starts On</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='weekStartsOn'>
|
||||||
|
<SelectValue placeholder='Select day' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='sunday'>Sunday</SelectItem>
|
||||||
|
<SelectItem value='monday'>Monday</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label htmlFor='showWeekends' className='font-normal'>
|
||||||
|
Show Weekends
|
||||||
|
</Label>
|
||||||
|
<Switch id='showWeekends' defaultChecked />
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset className='space-y-4 p-4 border rounded-md'>
|
||||||
|
<legend className='text-sm font-medium px-1'>
|
||||||
|
Availability
|
||||||
|
</legend>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label>Working Hours</Label>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Define your typical available hours (e.g., Monday-Friday,
|
||||||
|
9 AM - 5 PM).
|
||||||
|
</p>
|
||||||
|
<Button variant='outline_muted' size='sm'>
|
||||||
|
Set Working Hours
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='minNoticeBooking'>
|
||||||
|
Minimum Notice for Bookings
|
||||||
|
</Label>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Min time before a booking can be made.
|
||||||
|
</p>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Input
|
||||||
|
id='bookingWindow'
|
||||||
|
type='text'
|
||||||
|
placeholder='e.g., 1h'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='bookingWindow'>
|
||||||
|
Booking Window (days in advance)
|
||||||
|
</Label>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Max time in advance a booking can be made.
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
id='bookingWindow'
|
||||||
|
type='number'
|
||||||
|
placeholder='e.g., 30d'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset className='space-y-4 p-4 border rounded-md'>
|
||||||
|
<legend className='text-sm font-medium px-1'>
|
||||||
|
iCalendar Integration
|
||||||
|
</legend>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='icalImport'>Import iCal Feed URL</Label>
|
||||||
|
<Input
|
||||||
|
id='icalImport'
|
||||||
|
type='url'
|
||||||
|
placeholder='https://calendar.example.com/feed.ics'
|
||||||
|
/>
|
||||||
|
<Button size='sm' className='mt-1'>
|
||||||
|
Add Feed
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label>Export Your Calendar</Label>
|
||||||
|
<Button variant='outline_muted' size='sm'>
|
||||||
|
Get iCal Export URL
|
||||||
|
</Button>
|
||||||
|
<Button variant='outline_muted' size='sm' className='ml-2'>
|
||||||
|
Download .ics File
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'sharingPrivacy':
|
||||||
|
return (
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Sharing & Privacy</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Control who can see your calendar and book time with you.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='defaultVisibility'>
|
||||||
|
Default Calendar Visibility
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='defaultVisibility'>
|
||||||
|
<SelectValue placeholder='Select visibility' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='private'>
|
||||||
|
Private (Only You)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value='freebusy'>
|
||||||
|
Free/Busy for Friends
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value='fulldetails'>
|
||||||
|
Full Details for Friends
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='whoCanSeeFull'>
|
||||||
|
Who Can See Your Full Calendar Details?
|
||||||
|
</Label>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
(Override for Default Visibility)
|
||||||
|
<br />
|
||||||
|
<span className='text-sm text-muted-foreground'>
|
||||||
|
This setting will override the default visibility for your
|
||||||
|
calendar. You can set specific friends or groups to see
|
||||||
|
your full calendar details.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='whoCanSeeFull'>
|
||||||
|
<SelectValue placeholder='Select audience' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='me'>Only Me</SelectItem>
|
||||||
|
<SelectItem value='friends'>My Friends</SelectItem>
|
||||||
|
<SelectItem value='specific'>
|
||||||
|
Specific Friends/Groups (manage separately)
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='whoCanBook'>
|
||||||
|
Who Can Book Time With You?
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='whoCanBook'>
|
||||||
|
<SelectValue placeholder='Select audience' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='none'>No One</SelectItem>
|
||||||
|
<SelectItem value='friends'>My Friends</SelectItem>
|
||||||
|
<SelectItem value='specific'>
|
||||||
|
Specific Friends/Groups (manage separately)
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label>Blocked Users</Label>
|
||||||
|
<Button variant='outline_muted'>Manage Blocked Users</Button>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Prevent specific users from seeing your calendar or booking
|
||||||
|
time.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'appearance':
|
||||||
|
return (
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Appearance</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Customize the look and feel of the application.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='theme'>Theme</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='theme'>
|
||||||
|
<SelectValue placeholder='Select theme' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='light'>Light</SelectItem>
|
||||||
|
<SelectItem value='dark'>Dark</SelectItem>
|
||||||
|
<SelectItem value='system'>System Default</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='dateFormat'>Date Format</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='dateFormat'>
|
||||||
|
<SelectValue placeholder='Select date format' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='ddmmyyyy'>DD/MM/YYYY</SelectItem>
|
||||||
|
<SelectItem value='mmddyyyy'>MM/DD/YYYY</SelectItem>
|
||||||
|
<SelectItem value='yyyymmdd'>YYYY-MM-DD</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='timeFormat'>Time Format</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='timeFormat'>
|
||||||
|
<SelectValue placeholder='Select time format' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='24h'>24-hour</SelectItem>
|
||||||
|
<SelectItem value='12h'>12-hour</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='fixed inset-0 flex items-center justify-center p-4 bg-background/50 backdrop-blur-sm'>
|
||||||
|
<div className='rounded-lg border bg-card text-card-foreground shadow-xl max-w-[700px] w-full h-auto max-h-[calc(100vh-2rem)] flex flex-col'>
|
||||||
|
<div className='p-6 border-b'>
|
||||||
|
<div className='flex items-center justify-between mb-4'>
|
||||||
|
<h1 className='text-2xl font-semibold'>Settings</h1>
|
||||||
|
</div>
|
||||||
|
<SettingsDropdown
|
||||||
|
currentSection={currentSection}
|
||||||
|
onSectionChange={setCurrentSection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex-grow overflow-auto'>{renderSettingsContent()}</div>
|
||||||
|
<div>
|
||||||
|
<CardFooter className='border-t h-[60px] flex content-center justify-between'>
|
||||||
|
<Button onClick={() => router.back()} variant='secondary'>
|
||||||
|
Exit
|
||||||
|
</Button>
|
||||||
|
<Button variant='primary'>Save Changes</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
123
src/components/misc/settings-switcher.tsx
Normal file
123
src/components/misc/settings-switcher.tsx
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from '@/components/ui/command';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Check, ChevronDown } from 'lucide-react';
|
||||||
|
|
||||||
|
interface SettingsOption {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SettingsSwitcherProps {
|
||||||
|
title: string;
|
||||||
|
options: SettingsOption[];
|
||||||
|
defaultValue?: string;
|
||||||
|
onValueChange?: (value: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsSwitcher({
|
||||||
|
title,
|
||||||
|
options,
|
||||||
|
defaultValue,
|
||||||
|
onValueChange,
|
||||||
|
placeholder = 'Select option...',
|
||||||
|
searchPlaceholder = 'Search options...',
|
||||||
|
className,
|
||||||
|
}: SettingsSwitcherProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [selectedValue, setSelectedValue] = useState(
|
||||||
|
defaultValue || options[0]?.value || '',
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedOption = options.find(
|
||||||
|
(option) => option.value === selectedValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelect = (value: string) => {
|
||||||
|
setSelectedValue(value);
|
||||||
|
setOpen(false);
|
||||||
|
onValueChange?.(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('space-y-2', className)}>
|
||||||
|
<label className='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'>
|
||||||
|
{title}
|
||||||
|
</label>
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='outline_muted'
|
||||||
|
role='combobox'
|
||||||
|
aria-expanded={open}
|
||||||
|
className='w-full justify-between bg-white text-black'
|
||||||
|
>
|
||||||
|
<div className='flex flex-col items-start'>
|
||||||
|
<span>{selectedOption?.label || placeholder}</span>
|
||||||
|
{selectedOption?.description && (
|
||||||
|
<span className='text-xs text-muted-foreground'>
|
||||||
|
{selectedOption.description}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ChevronDown className='ml-2 h-4 w-4 shrink-0 opacity-50' />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className='w-full p-0' align='start'>
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder={searchPlaceholder} />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No option found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{options.map((option) => (
|
||||||
|
<CommandItem
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
onSelect={() => handleSelect(option.value)}
|
||||||
|
className='flex items-center justify-between'
|
||||||
|
>
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<span>{option.label}</span>
|
||||||
|
{option.description && (
|
||||||
|
<span className='text-xs text-muted-foreground'>
|
||||||
|
{option.description}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
'ml-2 h-4 w-4',
|
||||||
|
selectedValue === option.value
|
||||||
|
? 'opacity-100'
|
||||||
|
: 'opacity-0',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ export default function UserCard() {
|
||||||
)}
|
)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className='flex justify-center'>{data?.data.user.name}</div>
|
<div className='flex justify-center'>{data?.data.user.name}</div>
|
||||||
<div className='flex justify-center text-text-muted'>
|
<div className='flex justify-center text-text-muted text-[12px]'>
|
||||||
{data?.data.user.email}
|
{data?.data.user.email}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -41,11 +41,13 @@ export default function UserDropdown() {
|
||||||
<UserCard />
|
<UserCard />
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
<Link href='/settings'>
|
||||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem>
|
<Link href='/logout'>
|
||||||
<Link href='/logout'>Logout</Link>
|
<DropdownMenuItem>Logout</DropdownMenuItem>
|
||||||
</DropdownMenuItem>
|
</Link>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"radius-lg inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-button transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"radius-lg inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-button transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-nonef",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|
|
@ -126,7 +126,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot='card-footer'
|
data-slot='card-footer'
|
||||||
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
|
className={cn('flex items-center px-6', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import React from 'react';
|
import { cn } from '@/lib/utils';
|
||||||
|
import type * as React from 'react';
|
||||||
|
|
||||||
interface ScrollableContentWrapperProps {
|
interface ScrollableSettingsWrapperProps {
|
||||||
children: React.ReactNode;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScrollableSettingsWrapper: React.FC<
|
export function ScrollableSettingsWrapper({
|
||||||
ScrollableContentWrapperProps
|
className,
|
||||||
> = ({ children, className = '' }) => {
|
children,
|
||||||
|
}: ScrollableSettingsWrapperProps) {
|
||||||
return (
|
return (
|
||||||
<div className={`h-[500px] overflow-y-auto space-y-2 ${className}`}>
|
<div className={cn('overflow-y-auto h-full', className)}>{children}</div>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue