From 5d812884790b2059eb45cbdb324926086748b73a Mon Sep 17 00:00:00 2001
From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com>
Date: Mon, 23 Jun 2025 11:20:09 +0200
Subject: [PATCH] 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.
---
src/app/settings/page.tsx | 483 +-------------------
src/components/misc/settings-dropdown.tsx | 158 +++++++
src/components/misc/settings-page.tsx | 467 +++++++++++++++++++
src/components/misc/settings-switcher.tsx | 123 +++++
src/components/misc/user-card.tsx | 2 +-
src/components/misc/user-dropdown.tsx | 10 +-
src/components/ui/button.tsx | 2 +-
src/components/ui/card.tsx | 2 +-
src/components/wrappers/settings-scroll.tsx | 20 +-
9 files changed, 770 insertions(+), 497 deletions(-)
create mode 100644 src/components/misc/settings-dropdown.tsx
create mode 100644 src/components/misc/settings-page.tsx
create mode 100644 src/components/misc/settings-switcher.tsx
diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx
index 563ebab..e0ad2a5 100644
--- a/src/app/settings/page.tsx
+++ b/src/app/settings/page.tsx
@@ -1,482 +1,5 @@
-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 { 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';
+import SettingsPage from '@/components/misc/settings-page';
-export default function SettingsPage() {
- return (
-
-
-
-
- Account
- Notifications
- Calendar
- Privacy
- Appearance
-
-
-
-
-
-
- Account Settings
-
- Manage your account details and preferences.
-
-
-
-
- Display Name
-
-
-
-
Email Address
-
-
- Email is managed by your SSO provider.
-
-
-
-
Profile Picture
-
-
- Upload a new profile picture.
-
-
-
- Timezone
-
-
-
-
- Language
-
-
-
-
-
- English
- German
-
-
-
-
-
Delete Account
-
- Permanently delete your account and all associated data.
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
-
-
- Notification Preferences
-
- Choose how you want to be notified.
-
-
-
-
-
- Enable All Email Notifications
-
-
-
-
-
-
- New Meeting Bookings
-
-
-
-
-
- Meeting Confirmations/Cancellations
-
-
-
-
-
- Meeting Reminders
-
-
-
-
- Remind me before
-
-
-
-
-
- 15 minutes
- 30 minutes
- 1 hour
- 1 day
-
-
-
-
-
- Friend Requests
-
-
-
-
-
- Group Invitations/Updates
-
-
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
-
-
- Calendar & Availability
-
- Manage your calendar display, default availability, and iCal
- integrations.
-
-
-
-
-
- Display
-
-
-
- Default Calendar View
-
-
-
-
-
-
- Day
- Week
- Month
-
-
-
-
- Week Starts On
-
-
-
-
-
- Sunday
- Monday
-
-
-
-
-
- Show Weekends
-
-
-
-
-
-
-
- Availability
-
-
-
Working Hours
-
- Define your typical available hours (e.g.,
- Monday-Friday, 9 AM - 5 PM).
-
-
- Set Working Hours
-
-
-
-
- Minimum Notice for Bookings
-
-
- Min time before a booking can be made.
-
-
-
-
-
-
-
- Booking Window (days in advance)
-
-
- Max time in advance a booking can be made.
-
-
-
-
-
-
-
- iCalendar Integration
-
-
- Import iCal Feed URL
-
-
- Add Feed
-
-
-
- Export Your Calendar
-
- Get iCal Export URL
-
-
- Download .ics File
-
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
-
-
- Sharing & Privacy
-
- Control who can see your calendar and book time with you.
-
-
-
-
-
- Default Calendar Visibility
-
-
-
-
-
-
-
- Private (Only You)
-
-
- Free/Busy for Friends
-
-
- Full Details for Friends
-
-
-
-
-
-
- Who Can See Your Full Calendar Details?
-
-
- (Override for Default Visibility)
-
-
- This setting will override the default visibility for
- your calendar. You can set specific friends or groups to
- see your full calendar details.
-
-
-
-
-
-
-
- Only Me
- My Friends
-
- Specific Friends/Groups (manage separately)
-
-
-
-
-
-
- Who Can Book Time With You?
-
-
-
-
-
-
- No One
- My Friends
-
- Specific Friends/Groups (manage separately)
-
-
-
-
-
-
Blocked Users
-
- Manage Blocked Users
-
-
- Prevent specific users from seeing your calendar or
- booking time.
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
-
-
- Appearance
-
- Customize the look and feel of the application.
-
-
-
-
- Theme
-
-
-
-
-
- Light
- Dark
- System Default
-
-
-
-
- Date Format
-
-
-
-
-
- DD/MM/YYYY
- MM/DD/YYYY
- YYYY-MM-DD
-
-
-
-
- Time Format
-
-
-
-
-
- 24-hour
- 12-hour
-
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
- );
+export default function Page() {
+ return ;
}
diff --git a/src/components/misc/settings-dropdown.tsx b/src/components/misc/settings-dropdown.tsx
new file mode 100644
index 0000000..63a7b20
--- /dev/null
+++ b/src/components/misc/settings-dropdown.tsx
@@ -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 (
+
+
+
+
+
+
+
+ {currentSectionData?.label}
+
+ {currentSectionData?.description}
+
+
+
+
+
+
+
+
+
+
+ No settings found.
+
+ {settingsSections.map((section) => {
+ const Icon = section.icon;
+ return (
+ handleSelect(section.value)}
+ className='flex items-center justify-between p-3'
+ >
+
+
+
+ {section.label}
+
+ {section.description}
+
+
+
+
+
+ );
+ })}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/misc/settings-page.tsx b/src/components/misc/settings-page.tsx
new file mode 100644
index 0000000..fb90614
--- /dev/null
+++ b/src/components/misc/settings-page.tsx
@@ -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 (
+
+
+
+ Account Settings
+
+ Manage your account details and preferences.
+
+
+
+
+ Display Name
+
+
+
+
Email Address
+
+
+ Email is managed by your SSO provider.
+
+
+
+
Profile Picture
+
+
+ Upload a new profile picture.
+
+
+
+ Timezone
+
+
+
+ Language
+
+
+
+
+
+ English
+ German
+
+
+
+
+
Delete Account
+
+ Permanently delete your account and all associated data.
+
+
+
+
+
+ );
+
+ case 'notifications':
+ return (
+
+
+
+ Notification Preferences
+
+ Choose how you want to be notified.
+
+
+
+
+
+ Enable All Email Notifications
+
+
+
+
+
+
+ New Meeting Bookings
+
+
+
+
+
+ Meeting Confirmations/Cancellations
+
+
+
+
+
+ Meeting Reminders
+
+
+
+
+ Remind me before
+
+
+
+
+
+ 15 minutes
+ 30 minutes
+ 1 hour
+ 1 day
+
+
+
+
+
+ Friend Requests
+
+
+
+
+
+ Group Invitations/Updates
+
+
+
+
+
+
+
+ );
+
+ case 'calendarAvailability':
+ return (
+
+
+
+ Calendar & Availability
+
+ Manage your calendar display, default availability, and iCal
+ integrations.
+
+
+
+
+ Display
+
+
+ Default Calendar View
+
+
+
+
+
+
+ Day
+ Week
+ Month
+
+
+
+
+ Week Starts On
+
+
+
+
+
+ Sunday
+ Monday
+
+
+
+
+
+ Show Weekends
+
+
+
+
+
+
+
+ Availability
+
+
+
Working Hours
+
+ Define your typical available hours (e.g., Monday-Friday,
+ 9 AM - 5 PM).
+
+
+ Set Working Hours
+
+
+
+
+ Minimum Notice for Bookings
+
+
+ Min time before a booking can be made.
+
+
+
+
+
+
+
+ Booking Window (days in advance)
+
+
+ Max time in advance a booking can be made.
+
+
+
+
+
+
+
+ iCalendar Integration
+
+
+ Import iCal Feed URL
+
+
+ Add Feed
+
+
+
+ Export Your Calendar
+
+ Get iCal Export URL
+
+
+ Download .ics File
+
+
+
+
+
+
+ );
+
+ case 'sharingPrivacy':
+ return (
+
+
+
+ Sharing & Privacy
+
+ Control who can see your calendar and book time with you.
+
+
+
+
+
+ Default Calendar Visibility
+
+
+
+
+
+
+
+ Private (Only You)
+
+
+ Free/Busy for Friends
+
+
+ Full Details for Friends
+
+
+
+
+
+
+ Who Can See Your Full Calendar Details?
+
+
+ (Override for Default Visibility)
+
+
+ This setting will override the default visibility for your
+ calendar. You can set specific friends or groups to see
+ your full calendar details.
+
+
+
+
+
+
+
+ Only Me
+ My Friends
+
+ Specific Friends/Groups (manage separately)
+
+
+
+
+
+
+ Who Can Book Time With You?
+
+
+
+
+
+
+ No One
+ My Friends
+
+ Specific Friends/Groups (manage separately)
+
+
+
+
+
+
Blocked Users
+
Manage Blocked Users
+
+ Prevent specific users from seeing your calendar or booking
+ time.
+
+
+
+
+
+ );
+
+ case 'appearance':
+ return (
+
+
+
+ Appearance
+
+ Customize the look and feel of the application.
+
+
+
+
+ Theme
+
+
+
+
+
+ Light
+ Dark
+ System Default
+
+
+
+
+ Date Format
+
+
+
+
+
+ DD/MM/YYYY
+ MM/DD/YYYY
+ YYYY-MM-DD
+
+
+
+
+ Time Format
+
+
+
+
+
+ 24-hour
+ 12-hour
+
+
+
+
+
+
+ );
+
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+
+
+
{renderSettingsContent()}
+
+
+ router.back()} variant='secondary'>
+ Exit
+
+ Save Changes
+
+
+
+
+ );
+}
diff --git a/src/components/misc/settings-switcher.tsx b/src/components/misc/settings-switcher.tsx
new file mode 100644
index 0000000..40e6a05
--- /dev/null
+++ b/src/components/misc/settings-switcher.tsx
@@ -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 (
+
+
+ {title}
+
+
+
+
+
+ {selectedOption?.label || placeholder}
+ {selectedOption?.description && (
+
+ {selectedOption.description}
+
+ )}
+
+
+
+
+
+
+
+
+ No option found.
+
+ {options.map((option) => (
+ handleSelect(option.value)}
+ className='flex items-center justify-between'
+ >
+
+ {option.label}
+ {option.description && (
+
+ {option.description}
+
+ )}
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/misc/user-card.tsx b/src/components/misc/user-card.tsx
index faefc35..457d0fc 100644
--- a/src/components/misc/user-card.tsx
+++ b/src/components/misc/user-card.tsx
@@ -21,7 +21,7 @@ export default function UserCard() {
)}
{data?.data.user.name}
-
+
{data?.data.user.email}
diff --git a/src/components/misc/user-dropdown.tsx b/src/components/misc/user-dropdown.tsx
index e55f4bb..03249c6 100644
--- a/src/components/misc/user-dropdown.tsx
+++ b/src/components/misc/user-dropdown.tsx
@@ -41,11 +41,13 @@ export default function UserDropdown() {
- Settings
+
+ Settings
+
-
- Logout
-
+
+ Logout
+
);
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index a5eec23..b6c034f 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
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: {
variant: {
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index 7ba53ea..940a845 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -126,7 +126,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
);
diff --git a/src/components/wrappers/settings-scroll.tsx b/src/components/wrappers/settings-scroll.tsx
index e0f7251..a647493 100644
--- a/src/components/wrappers/settings-scroll.tsx
+++ b/src/components/wrappers/settings-scroll.tsx
@@ -1,16 +1,16 @@
-import React from 'react';
+import { cn } from '@/lib/utils';
+import type * as React from 'react';
-interface ScrollableContentWrapperProps {
- children: React.ReactNode;
+interface ScrollableSettingsWrapperProps {
className?: string;
+ children: React.ReactNode;
}
-export const ScrollableSettingsWrapper: React.FC<
- ScrollableContentWrapperProps
-> = ({ children, className = '' }) => {
+export function ScrollableSettingsWrapper({
+ className,
+ children,
+}: ScrollableSettingsWrapperProps) {
return (
-
- {children}
-
+ {children}
);
-};
+}