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. - - - -
- - -
-
- - -

- Email is managed by your SSO provider. -

-
-
- - -

- Upload a new profile picture. -

-
-
- - -
- -
- - -
-
- -

- Permanently delete your account and all associated data. -

-
-
-
- - - - -
-
- - - - - - Notification Preferences - - Choose how you want to be notified. - - - -
- - -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- - - - -
-
- - - - - - Calendar & Availability - - Manage your calendar display, default availability, and iCal - integrations. - - - -
- - Display - -
- - -
-
- - -
-
- - -
-
- -
- - Availability - -
- -

- Define your typical available hours (e.g., - Monday-Friday, 9 AM - 5 PM). -

- -
-
- -

- Min time before a booking can be made. -

-
- -
-
-
- -

- Max time in advance a booking can be made. -

- -
-
- -
- - iCalendar Integration - -
- - - -
-
- - - -
-
-
-
- - - - -
-
- - - - - - Sharing & Privacy - - Control who can see your calendar and book time with you. - - - -
- - -
-
- -

- (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. - -

- -
-
- - -
-
- - -

- Prevent specific users from seeing your calendar or - booking time. -

-
-
-
- - - - -
-
- - - - - - Appearance - - Customize the look and feel of the application. - - - -
- - -
-
- - -
-
- - -
-
-
- - - - -
-
-
-
-
- ); +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 ( +
+ + + + + + + + + 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. + + + +
+ + +
+
+ + +

+ Email is managed by your SSO provider. +

+
+
+ + +

+ Upload a new profile picture. +

+
+
+ + +
+
+ + +
+
+ +

+ Permanently delete your account and all associated data. +

+
+
+
+
+ ); + + case 'notifications': + return ( + + + + Notification Preferences + + Choose how you want to be notified. + + + +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ ); + + case 'calendarAvailability': + return ( + + + + Calendar & Availability + + Manage your calendar display, default availability, and iCal + integrations. + + + +
+ Display +
+ + +
+
+ + +
+
+ + +
+
+ +
+ + Availability + +
+ +

+ Define your typical available hours (e.g., Monday-Friday, + 9 AM - 5 PM). +

+ +
+
+ +

+ Min time before a booking can be made. +

+
+ +
+
+
+ +

+ Max time in advance a booking can be made. +

+ +
+
+ +
+ + iCalendar Integration + +
+ + + +
+
+ + + +
+
+
+
+
+ ); + + case 'sharingPrivacy': + return ( + + + + Sharing & Privacy + + Control who can see your calendar and book time with you. + + + +
+ + +
+
+ +

+ (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. + +

+ +
+
+ + +
+
+ + +

+ Prevent specific users from seeing your calendar or booking + time. +

+
+
+
+
+ ); + + case 'appearance': + return ( + + + + Appearance + + Customize the look and feel of the application. + + + +
+ + +
+
+ + +
+
+ + +
+
+
+
+ ); + + default: + return null; + } + }; + + return ( +
+
+
+
+

Settings

+
+ +
+ +
{renderSettingsContent()}
+
+ + + + +
+
+
+ ); +} 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 ( +
+ + + + + + + + + + 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}
); -}; +}