From 6a5ad338babdb1adb72ba63694ea1bccabcdfaf3 Mon Sep 17 00:00:00 2001 From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:01:20 +0200 Subject: [PATCH 01/38] feat: add Radix UI components and implement sidebar functionality - Added new Radix UI components: Dialog, Tooltip, Separator, and updated existing components. - Introduced a Sidebar component with collapsible functionality and mobile responsiveness. - Implemented a custom hook `useIsMobile` to manage mobile state. - Updated package dependencies in package.json and yarn.lock for new components. - Created utility components such as Button, Skeleton, and Input for consistent styling. feat: add AppSidebar component with collapsible functionality and sidebar menu - Introduced AppSidebar component for a customizable sidebar layout. - Implemented collapsible sections using Radix UI's Collapsible component. - Added sidebar menu items with icons and links for navigation. - Created Sidebar UI components including SidebarHeader, SidebarFooter, and SidebarMenu. - Integrated ThemePicker for theme selection within the sidebar. - Updated sidebar styles and layout for better responsiveness. chore: add @radix-ui/react-collapsible dependency - Added @radix-ui/react-collapsible package to manage collapsible UI elements. --- src/components/ui/sidebar.tsx | 725 ++++++++++++++++++++++++++++++++++ yarn.lock | 18 +- 2 files changed, 734 insertions(+), 9 deletions(-) create mode 100644 src/components/ui/sidebar.tsx diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx new file mode 100644 index 0000000..6b68b8f --- /dev/null +++ b/src/components/ui/sidebar.tsx @@ -0,0 +1,725 @@ +'use client'; + +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, VariantProps } from 'class-variance-authority'; +import { PanelLeftIcon } from 'lucide-react'; + +import { useIsMobile } from '@/hooks/use-mobile'; +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Separator } from '@/components/ui/separator'; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from '@/components/ui/sheet'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; + +const SIDEBAR_COOKIE_NAME = 'sidebar_state'; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = '16rem'; +const SIDEBAR_WIDTH_MOBILE = '18rem'; +const SIDEBAR_WIDTH_ICON = '4rem'; +const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; + +type SidebarContextProps = { + state: 'expanded' | 'collapsed'; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; +}; + +const SidebarContext = React.createContext(null); + +function useSidebar() { + const context = React.useContext(SidebarContext); + if (!context) { + throw new Error('useSidebar must be used within a SidebarProvider.'); + } + + return context; +} + +function SidebarProvider({ + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props +}: React.ComponentProps<'div'> & { + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; +}) { + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === 'function' ? value(open) : value; + if (setOpenProp) { + setOpenProp(openState); + } else { + _setOpen(openState); + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + }, + [setOpenProp, open], + ); + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); + }, [isMobile, setOpen, setOpenMobile]); + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault(); + toggleSidebar(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [toggleSidebar]); + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? 'expanded' : 'collapsed'; + + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + }), + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar], + ); + + return ( + + +
+ {children} +
+
+
+ ); +} + +function Sidebar({ + side = 'left', + variant = 'sidebar', + collapsible = 'offcanvas', + className, + children, + ...props +}: React.ComponentProps<'div'> & { + side?: 'left' | 'right'; + variant?: 'sidebar' | 'floating' | 'inset'; + collapsible?: 'offcanvas' | 'icon' | 'none'; +}) { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); + + if (collapsible === 'none') { + return ( +
+ {children} +
+ ); + } + + if (isMobile) { + return ( + + + + Sidebar + Displays the mobile sidebar. + +
{children}
+
+
+ ); + } + + return ( +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
+ ); +} + +function SidebarTrigger({ + className, + onClick, + ...props +}: React.ComponentProps) { + const { toggleSidebar } = useSidebar(); + + return ( + + ); +} + +function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) { + const { toggleSidebar } = useSidebar(); + + return ( + -

- 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}
); -}; +} From 13a99e9dc4a083477bb12b17eda5860c692a2506 Mon Sep 17 00:00:00 2001 From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:45:50 +0200 Subject: [PATCH 03/38] feat: tempcommit --- src/components/custom-ui/app-sidebar.tsx | 4 +- src/components/misc/settings-dropdown.tsx | 8 +- src/components/misc/settings-page.tsx | 128 ++++++++++++++-------- src/components/misc/settings-switcher.tsx | 123 --------------------- src/components/misc/user-dropdown.tsx | 1 + src/components/wrappers/group-wrapper.tsx | 23 ++++ 6 files changed, 113 insertions(+), 174 deletions(-) delete mode 100644 src/components/misc/settings-switcher.tsx create mode 100644 src/components/wrappers/group-wrapper.tsx diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx index f823970..ef01ca9 100644 --- a/src/components/custom-ui/app-sidebar.tsx +++ b/src/components/custom-ui/app-sidebar.tsx @@ -52,7 +52,7 @@ const items = [ }, { title: 'Events', - url: '#', + url: '/events', icon: CalendarClock, }, ]; @@ -114,7 +114,7 @@ export function AppSidebar() { diff --git a/src/components/misc/settings-dropdown.tsx b/src/components/misc/settings-dropdown.tsx index 63a7b20..5bf256c 100644 --- a/src/components/misc/settings-dropdown.tsx +++ b/src/components/misc/settings-dropdown.tsx @@ -57,19 +57,19 @@ const settingsSections: SettingsSection[] = [ { label: 'Calendar', value: 'calendarAvailability', - description: 'Manage calendar display and availability', + description: 'Manage calendar display, availability and iCal integration', icon: Calendar, }, { label: 'Privacy', value: 'sharingPrivacy', - description: 'Control who can see your calendar', + description: 'Control who can see your calendar and book time with you', icon: Shield, }, { label: 'Appearance', value: 'appearance', - description: 'Customize the look and feel', + description: 'Customize the look and feel of the application', icon: Palette, }, ]; @@ -99,7 +99,7 @@ export function SettingsDropdown({ variant='outline_muted' role='combobox' aria-expanded={open} - className='w-full justify-between bg-white text-black h-auto py-3' + className='w-full justify-between bg-popover text-text h-auto py-3' >
diff --git a/src/components/misc/settings-page.tsx b/src/components/misc/settings-page.tsx index fb90614..ce91579 100644 --- a/src/components/misc/settings-page.tsx +++ b/src/components/misc/settings-page.tsx @@ -23,10 +23,15 @@ import { } from '@/components/ui/select'; import { SettingsDropdown } from '@/components/misc/settings-dropdown'; import { useRouter } from 'next/navigation'; +import { useGetApiUserMe } from '@/generated/api/user/user'; +import { ThemePicker } from './theme-picker'; +import LabeledInput from '../custom-ui/labeled-input'; +import { GroupWrapper } from '../wrappers/group-wrapper'; export default function SettingsPage() { const router = useRouter(); const [currentSection, setCurrentSection] = useState('general'); + const { data } = useGetApiUserMe(); const renderSettingsContent = () => { switch (currentSection) { @@ -36,28 +41,79 @@ export default function SettingsPage() { Account Settings - - Manage your account details and preferences. - - -
- - -
-
- - -

- Email is managed by your SSO provider. -

-
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +

+ Email might be managed by your SSO provider. +

+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
@@ -67,7 +123,11 @@ export default function SettingsPage() {
- +
@@ -98,9 +158,6 @@ export default function SettingsPage() { Notification Preferences - - Choose how you want to be notified. -
@@ -175,10 +232,6 @@ export default function SettingsPage() { Calendar & Availability - - Manage your calendar display, default availability, and iCal - integrations. -
@@ -298,9 +351,6 @@ export default function SettingsPage() { Sharing & Privacy - - Control who can see your calendar and book time with you. -
@@ -386,23 +436,11 @@ export default function SettingsPage() { Appearance - - Customize the look and feel of the application. -
- +
diff --git a/src/components/misc/settings-switcher.tsx b/src/components/misc/settings-switcher.tsx deleted file mode 100644 index 40e6a05..0000000 --- a/src/components/misc/settings-switcher.tsx +++ /dev/null @@ -1,123 +0,0 @@ -'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-dropdown.tsx b/src/components/misc/user-dropdown.tsx index 03249c6..d9af19d 100644 --- a/src/components/misc/user-dropdown.tsx +++ b/src/components/misc/user-dropdown.tsx @@ -17,6 +17,7 @@ import UserCard from '@/components/misc/user-card'; export default function UserDropdown() { const { data } = useGetApiUserMe(); + return ( diff --git a/src/components/wrappers/group-wrapper.tsx b/src/components/wrappers/group-wrapper.tsx new file mode 100644 index 0000000..713b5d6 --- /dev/null +++ b/src/components/wrappers/group-wrapper.tsx @@ -0,0 +1,23 @@ +import { cn } from '@/lib/utils'; +import type * as React from 'react'; + +interface ScrollableSettingsWrapperProps { + className?: string; + legend?: string; + children: React.ReactNode; +} + +export function GroupWrapper({ + className, + legend, + children, +}: ScrollableSettingsWrapperProps) { + return ( +
+ {legend} + {children} +
+ ); +} From f658a95b16ca1ed5d24eaab1b84cf796bbed8322 Mon Sep 17 00:00:00 2001 From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:52:00 +0200 Subject: [PATCH 04/38] feat: tempcommit --- src/components/misc/settings-dropdown.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/misc/settings-dropdown.tsx b/src/components/misc/settings-dropdown.tsx index 5bf256c..c49629c 100644 --- a/src/components/misc/settings-dropdown.tsx +++ b/src/components/misc/settings-dropdown.tsx @@ -105,9 +105,9 @@ export function SettingsDropdown({
{currentSectionData?.label} - +

{currentSectionData?.description} - +

@@ -132,9 +132,9 @@ export function SettingsDropdown({
{section.label} - +

{section.description} - +

Date: Fri, 27 Jun 2025 09:33:05 +0200 Subject: [PATCH 05/38] style: update calendar styling to to make it pretty in both light- and dark-mode --- src/components/custom-toolbar.css | 2 +- src/components/react-big-calendar.css | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/custom-toolbar.css b/src/components/custom-toolbar.css index 3fba69f..9721364 100644 --- a/src/components/custom-toolbar.css +++ b/src/components/custom-toolbar.css @@ -5,7 +5,7 @@ gap: 12px; padding: calc(var(--spacing) * 2); padding-left: calc(50px + var(--spacing)); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: none; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } diff --git a/src/components/react-big-calendar.css b/src/components/react-big-calendar.css index 675898c..ee7afdd 100644 --- a/src/components/react-big-calendar.css +++ b/src/components/react-big-calendar.css @@ -83,7 +83,7 @@ button.rbc-input::-moz-focus-inner { } .rbc-off-range-bg { - background: #e6e6e6; + background: var(--color-neutral-700); } .rbc-header { @@ -596,7 +596,7 @@ button.rbc-input::-moz-focus-inner { min-height: 100%; /*Own changes 06*/ - background-color: #383838; + background-color: var(--color-neutral-700); /*Own changes 06*/ } .rbc-time-column .rbc-timeslot-group { @@ -606,7 +606,7 @@ button.rbc-input::-moz-focus-inner { } .rbc-timeslot-group { - border-bottom: 1px solid #8d8d8d; /*#ddd*/ + border-bottom: 1px solid var(--color-neutral-300); /*#ddd*/ min-height: 40px; display: -webkit-box; display: -ms-flexbox; @@ -624,7 +624,7 @@ button.rbc-input::-moz-focus-inner { flex: none; /*Own changes 07*/ - background-color: #8d8d8d; + background-color: var(--color-neutral-500); /*Own changes 07*/ } @@ -686,7 +686,7 @@ button.rbc-input::-moz-focus-inner { min-height: 1em; } .rbc-day-slot .rbc-time-slot { - border-top: 1px solid #383838; /*#f7f7f7*/ + border-top: 1px solid transparent; /*#f7f7f7*/ } .rbc-time-view-resources .rbc-time-gutter, @@ -782,7 +782,7 @@ button.rbc-input::-moz-focus-inner { position: relative; /*Own changes 05*/ - background-color: #555555; + background-color: var(--color-neutral-500); /*Own changes 05*/ } .rbc-time-view .rbc-allday-cell + .rbc-allday-cell { @@ -872,7 +872,7 @@ button.rbc-input::-moz-focus-inner { } .rbc-time-header-content { - border-bottom: 2px solid #717171; /*#ddd*/ + border-bottom: 2px solid var(--color-neutral-400); /*#ddd*/ } .rbc-time-column :last-child { @@ -890,7 +890,7 @@ button.rbc-input::-moz-focus-inner { /*Own changes 09*/ } .rbc-time-content > * + * > * { - border-left: 1px solid #c6c6c6; /*#ddd*/ + border-left: 1px solid var(--color-neutral-300); /*#ddd*/ } .rbc-rtl .rbc-time-content > * + * > * { border-left-width: 0; From a308158ca706cd9f482458da8159ad7b89a13f6c Mon Sep 17 00:00:00 2001 From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com> Date: Thu, 26 Jun 2025 20:25:13 +0200 Subject: [PATCH 06/38] feat: tempcommit --- src/components/custom-ui/labeled-input.tsx | 51 +++++++--- src/components/misc/settings-dropdown.tsx | 4 +- src/components/misc/settings-page.tsx | 107 ++++++++++++--------- src/components/wrappers/group-wrapper.tsx | 6 +- 4 files changed, 102 insertions(+), 66 deletions(-) diff --git a/src/components/custom-ui/labeled-input.tsx b/src/components/custom-ui/labeled-input.tsx index 4746a31..23601d9 100644 --- a/src/components/custom-ui/labeled-input.tsx +++ b/src/components/custom-ui/labeled-input.tsx @@ -1,5 +1,9 @@ import { Input, Textarea } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; +import React from 'react'; +import { Button } from '../ui/button'; +import { Eye, EyeOff } from 'lucide-react'; +import { cn } from '@/lib/utils'; export default function LabeledInput({ type, @@ -12,7 +16,7 @@ export default function LabeledInput({ error, ...rest }: { - type: 'text' | 'email' | 'password'; + type: 'text' | 'email' | 'password' | 'file'; label: string; placeholder?: string; value?: string; @@ -21,6 +25,8 @@ export default function LabeledInput({ autocomplete?: string; error?: string; } & React.InputHTMLAttributes) { + const [passwordVisible, setPasswordVisible] = React.useState(false); + return (
@@ -33,21 +39,36 @@ export default function LabeledInput({ rows={3} /> ) : ( - + + + + {type === 'password' && ( + + )} + )} + {error &&

{error}

}
); diff --git a/src/components/misc/settings-dropdown.tsx b/src/components/misc/settings-dropdown.tsx index c49629c..6eaae8d 100644 --- a/src/components/misc/settings-dropdown.tsx +++ b/src/components/misc/settings-dropdown.tsx @@ -45,13 +45,13 @@ const settingsSections: SettingsSection[] = [ { label: 'Account', value: 'general', - description: 'Manage your account details and preferences', + description: 'Manage account details', icon: User, }, { label: 'Notifications', value: 'notifications', - description: 'Choose how you want to be notified', + description: 'Choose notification Preferences', icon: Bell, }, { diff --git a/src/components/misc/settings-page.tsx b/src/components/misc/settings-page.tsx index ce91579..34e1dd0 100644 --- a/src/components/misc/settings-page.tsx +++ b/src/components/misc/settings-page.tsx @@ -27,6 +27,9 @@ import { useGetApiUserMe } from '@/generated/api/user/user'; import { ThemePicker } from './theme-picker'; import LabeledInput from '../custom-ui/labeled-input'; import { GroupWrapper } from '../wrappers/group-wrapper'; +import { Avatar } from '../ui/avatar'; +import Image from 'next/image'; +import { User } from 'lucide-react'; export default function SettingsPage() { const router = useRouter(); @@ -43,51 +46,50 @@ export default function SettingsPage() { Account Settings - +
- - + >
- - + >
- - + >
-
- - + -

+ label='Email Address' + placeholder='Your E-Mail' + defaultValue={data?.data.user.email ?? ''} + > + + Email might be managed by your SSO provider. -

+
- -
+ +
-
- - -

- Upload a new profile picture. -

+
+ + + {data?.data.user.image ? ( + Avatar + ) : ( + + )} +
@@ -143,9 +158,9 @@ export default function SettingsPage() {
-

+ Permanently delete your account and all associated data. -

+
@@ -277,10 +292,10 @@ export default function SettingsPage() {
-

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

+ @@ -289,9 +304,9 @@ export default function SettingsPage() { -

+ Min time before a booking can be made. -

+
Booking Window (days in advance) -

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

+ Who Can See Your Full Calendar Details? -

+ (Override for Default Visibility)
@@ -386,7 +401,7 @@ export default function SettingsPage() { calendar. You can set specific friends or groups to see your full calendar details. -

+ + + {data?.data.user.image ? ( + Avatar + ) : ( + + )} + + +
+ + ); +} diff --git a/src/components/misc/settings-page.tsx b/src/components/misc/settings-page.tsx index 34e1dd0..33a083b 100644 --- a/src/components/misc/settings-page.tsx +++ b/src/components/misc/settings-page.tsx @@ -30,6 +30,7 @@ import { GroupWrapper } from '../wrappers/group-wrapper'; import { Avatar } from '../ui/avatar'; import Image from 'next/image'; import { User } from 'lucide-react'; +import ProfilePictureUpload from './profile-picture-upload'; export default function SettingsPage() { const router = useRouter(); @@ -48,24 +49,26 @@ export default function SettingsPage() {
-
-
- + +
+
+ +
+
+ +
-
- -
-
+
-
- - - {data?.data.user.image ? ( - Avatar - ) : ( - - )} - -
-
- - -
-
- - -
-
+ +
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
Permanently delete your account and all associated data. From 6c734f2d19d8f1839f44b62859dc13630203e128 Mon Sep 17 00:00:00 2001 From: Micha Date: Fri, 27 Jun 2025 10:06:57 +0200 Subject: [PATCH 08/38] style: improve layout and responsiveness of custom toolbar --- src/app/(main)/home/page.tsx | 12 +++++++----- src/components/custom-toolbar.css | 20 +++++++++++++++++--- src/components/custom-toolbar.tsx | 7 ++----- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/app/(main)/home/page.tsx b/src/app/(main)/home/page.tsx index 1cf8a90..a3f904d 100644 --- a/src/app/(main)/home/page.tsx +++ b/src/app/(main)/home/page.tsx @@ -7,11 +7,13 @@ export default function Home() { const { data } = useGetApiUserMe(); return ( -
- +
+
+ +
); } diff --git a/src/components/custom-toolbar.css b/src/components/custom-toolbar.css index 9721364..37a2a4d 100644 --- a/src/components/custom-toolbar.css +++ b/src/components/custom-toolbar.css @@ -1,10 +1,14 @@ /* Container der Toolbar */ .custom-toolbar { display: flex; - flex-direction: column; - gap: 12px; + gap: 8px; padding: calc(var(--spacing) * 2); padding-left: calc(50px + var(--spacing)); + + @media (max-width: 870px) { + padding-left: 0; + flex-direction: column; + } box-shadow: none; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } @@ -25,6 +29,7 @@ display: flex; gap: 8px; justify-content: center; + align-items: center; } .custom-toolbar .navigation-controls button { @@ -77,6 +82,8 @@ border-radius: 11px; justify-items: center; align-items: center; + display: flex; + gap: 8px; } .custom-toolbar .navigation-controls .handleWeek button { @@ -93,6 +100,9 @@ padding: 0 8px; border-radius: 11px; justify-items: center; + display: flex; + justify-content: center; + align-items: center; } .right-section .datepicker-box { @@ -100,8 +110,12 @@ background-color: #c6c6c6; height: 36px; border-radius: 11px; - font-size: 12px; + font-size: 14px; align-self: center; + font-family: 'Varela Round', sans-serif; + display: flex; + align-items: center; + justify-content: center; } .datepicker { diff --git a/src/components/custom-toolbar.tsx b/src/components/custom-toolbar.tsx index 36c8fff..76e59ee 100644 --- a/src/components/custom-toolbar.tsx +++ b/src/components/custom-toolbar.tsx @@ -171,12 +171,9 @@ const CustomToolbar: React.FC = ({ }; return ( -
+
-
+
+ Permanently delete your account and all associated data. From f769b400c623c99aa15a2dbaedfb3a8101c7cc08 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 27 Jun 2025 21:02:50 +0000 Subject: [PATCH 15/38] chore(deps): update dependency eslint to v9.30.0 --- package.json | 2 +- yarn.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 9a33a39..0a8caa5 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@types/webpack-env": "1.18.8", "cypress": "14.5.0", "dotenv-cli": "8.0.0", - "eslint": "9.29.0", + "eslint": "9.30.0", "eslint-config-next": "15.3.4", "eslint-config-prettier": "10.1.5", "orval": "7.10.0", diff --git a/yarn.lock b/yarn.lock index 9553a33..2cfdded 100644 --- a/yarn.lock +++ b/yarn.lock @@ -436,21 +436,21 @@ __metadata: languageName: node linkType: hard -"@eslint/config-array@npm:^0.20.1": - version: 0.20.1 - resolution: "@eslint/config-array@npm:0.20.1" +"@eslint/config-array@npm:^0.21.0": + version: 0.21.0 + resolution: "@eslint/config-array@npm:0.21.0" dependencies: "@eslint/object-schema": "npm:^2.1.6" debug: "npm:^4.3.1" minimatch: "npm:^3.1.2" - checksum: 10c0/709108c3925d83c2166024646829ab61ba5fa85c6568daefd32508899f46ed8dc36d7153042df6dcc7e58ad543bc93298b646575daecb5eb4e39a43d838dab42 + checksum: 10c0/0ea801139166c4aa56465b309af512ef9b2d3c68f9198751bbc3e21894fe70f25fbf26e1b0e9fffff41857bc21bfddeee58649ae6d79aadcd747db0c5dca771f languageName: node linkType: hard -"@eslint/config-helpers@npm:^0.2.1": - version: 0.2.3 - resolution: "@eslint/config-helpers@npm:0.2.3" - checksum: 10c0/8fd36d7f33013628787947c81894807c7498b31eacf6648efa6d7c7a99aac6bf0d59a8a4d14f968ec2aeebefb76a1a7e4fd4cd556a296323d4711b3d7a7cda22 +"@eslint/config-helpers@npm:^0.3.0": + version: 0.3.0 + resolution: "@eslint/config-helpers@npm:0.3.0" + checksum: 10c0/013ae7b189eeae8b30cc2ee87bc5c9c091a9cd615579003290eb28bebad5d78806a478e74ba10b3fe08ed66975b52af7d2cd4b4b43990376412b14e5664878c8 languageName: node linkType: hard @@ -489,10 +489,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.29.0": - version: 9.29.0 - resolution: "@eslint/js@npm:9.29.0" - checksum: 10c0/d0ccf37063fa27a3fae9347cb044f84ca10b5a2fa19ffb2b3fedf3b96843ac1ff359ea9f0ab0e80f2f16fda4cb0dc61ea0fed0375090f050fe0a029e7d6de3a3 +"@eslint/js@npm:9.30.0": + version: 9.30.0 + resolution: "@eslint/js@npm:9.30.0" + checksum: 10c0/aec2df7f4e4e884d693dc27dbf4713c1a48afa327bfadac25ebd0e61a2797ce906f2f2a9be0d7d922acb68ccd68cc88779737811f9769eb4933d1f5e574c469e languageName: node linkType: hard @@ -5667,17 +5667,17 @@ __metadata: languageName: node linkType: hard -"eslint@npm:9.29.0": - version: 9.29.0 - resolution: "eslint@npm:9.29.0" +"eslint@npm:9.30.0": + version: 9.30.0 + resolution: "eslint@npm:9.30.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.20.1" - "@eslint/config-helpers": "npm:^0.2.1" + "@eslint/config-array": "npm:^0.21.0" + "@eslint/config-helpers": "npm:^0.3.0" "@eslint/core": "npm:^0.14.0" "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.29.0" + "@eslint/js": "npm:9.30.0" "@eslint/plugin-kit": "npm:^0.3.1" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" @@ -5713,7 +5713,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/75e3f841e0f8b0fa93dbb2ba6ae538bd8b611c3654117bc3dadf90bb009923dfd2c15ec2948dc6e6b8b571317cc125c5cceb9255da8cd644ee740020df645dd8 + checksum: 10c0/ebc4b17cfd96f308ebaeb12dfab133a551eb03200c80109ecf663fbeb9af83c4eb3c143407c1b04522d23b5f5844fe9a629b00d409adfc460c1aadf5108da86a languageName: node linkType: hard @@ -7713,7 +7713,7 @@ __metadata: cypress: "npm:14.5.0" date-fns: "npm:^4.1.0" dotenv-cli: "npm:8.0.0" - eslint: "npm:9.29.0" + eslint: "npm:9.30.0" eslint-config-next: "npm:15.3.4" eslint-config-prettier: "npm:10.1.5" lucide-react: "npm:^0.523.0" From 6b46177dc0cf9ea59accada947d3e9783c17dceb Mon Sep 17 00:00:00 2001 From: Maximilian Liebmann Date: Fri, 27 Jun 2025 23:06:19 +0200 Subject: [PATCH 16/38] feat: tempcommit --- .../misc/profile-picture-upload.tsx | 7 +- src/components/misc/settings-page.tsx | 142 ++++++++++-------- 2 files changed, 82 insertions(+), 67 deletions(-) diff --git a/src/components/misc/profile-picture-upload.tsx b/src/components/misc/profile-picture-upload.tsx index 41b64e0..cdd1673 100644 --- a/src/components/misc/profile-picture-upload.tsx +++ b/src/components/misc/profile-picture-upload.tsx @@ -15,12 +15,7 @@ export default function ProfilePictureUpload({ <>
- + {data?.data.user.image ? (
- +
@@ -127,7 +124,7 @@ export default function SettingsPage() { type='text' label='Timezone' placeholder='Europe/Berlin' - defaultValue={data?.data.user.timezone} + defaultValue={data?.data.user.timezone ?? ''} >
@@ -147,7 +144,12 @@ export default function SettingsPage() {
- + Permanently delete your account and all associated data. @@ -165,67 +167,85 @@ export default function SettingsPage() { Notification Preferences -
- - -
-
-
- - -
-
+ +
- +
-
- - + + + +
+
+ + +
+
+ + +
+
+
+ +
+
+ + +
+
+ +
+
-
- - + + +
+
+ + +
+
+ + +
-
- - -
-
- - -
-
+
From 232ce7bafea7dc8145cefa099616d916461da418 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 28 Jun 2025 02:01:24 +0000 Subject: [PATCH 17/38] fix(deps): update dependency react-hook-form to v7.59.0 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2cfdded..4d7c34d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9018,11 +9018,11 @@ __metadata: linkType: hard "react-hook-form@npm:^7.56.4": - version: 7.58.1 - resolution: "react-hook-form@npm:7.58.1" + version: 7.59.0 + resolution: "react-hook-form@npm:7.59.0" peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - checksum: 10c0/981b9982b7eb497c3afff86c219175fdd4d92a24e3533518035239545dda23f5ebc6ba647fb29a250f768bdf811ba413094ce0103fbf36b436737fc18aa1bb49 + checksum: 10c0/6be30ce65121f4be0f491c2929142e2d9a390a8802f58fb7a41ab978ab8daa6fcd2c442258dbd1b053e6864a83c8f4b1d83de9c95f0efdf5c2120d3c21bd838e languageName: node linkType: hard From d2ba0bfc16d0b1d721f48eb1c622a1cb190e1934 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 28 Jun 2025 08:06:22 +0000 Subject: [PATCH 18/38] chore(deps): update dependency @types/node to v22.15.34 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0a8caa5..8dc1030 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "devDependencies": { "@eslint/eslintrc": "3.3.1", "@tailwindcss/postcss": "4.1.11", - "@types/node": "22.15.33", + "@types/node": "22.15.34", "@types/react": "19.1.8", "@types/react-big-calendar": "1.16.2", "@types/react-dom": "19.1.6", diff --git a/yarn.lock b/yarn.lock index 4d7c34d..d257835 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3378,12 +3378,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:22.15.33": - version: 22.15.33 - resolution: "@types/node@npm:22.15.33" +"@types/node@npm:22.15.34": + version: 22.15.34 + resolution: "@types/node@npm:22.15.34" dependencies: undici-types: "npm:~6.21.0" - checksum: 10c0/ee040c29c891aa37fffc27d04a8529318c391356346933646b7692eaf62236831ad532f6ebaf43ebd6a2ef1f0f091860d8a0a83a4e3c5a4f66d37aa1b2c99f31 + checksum: 10c0/fb6a6b36daaa1b484aaba3d33b4d1e7b37ea993e29f20b7a676affa76ed6ff6acd2ded4d5003469bc8dbc815b3d224533b4560896037ef6d5b5d552721ab7d57 languageName: node linkType: hard @@ -7700,7 +7700,7 @@ __metadata: "@radix-ui/react-tooltip": "npm:^1.2.7" "@tailwindcss/postcss": "npm:4.1.11" "@tanstack/react-query": "npm:^5.80.7" - "@types/node": "npm:22.15.33" + "@types/node": "npm:22.15.34" "@types/react": "npm:19.1.8" "@types/react-big-calendar": "npm:1.16.2" "@types/react-dom": "npm:19.1.6" From 5dd0179ac2279d329ad3e7256fddd27042524806 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 28 Jun 2025 09:02:08 +0000 Subject: [PATCH 19/38] fix(deps): update dependency @tanstack/react-query to v5.81.5 --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index d257835..0a4c257 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3243,21 +3243,21 @@ __metadata: languageName: node linkType: hard -"@tanstack/query-core@npm:5.81.4": - version: 5.81.4 - resolution: "@tanstack/query-core@npm:5.81.4" - checksum: 10c0/9280a6918c230ec8d2fbb09c3113aa9682874592bce22019806747d306ab8d86c7a351b1ff743e7c841c7c3ebd0efaa4e3dbf0d23fa73d6385c19a5c926ecf3b +"@tanstack/query-core@npm:5.81.5": + version: 5.81.5 + resolution: "@tanstack/query-core@npm:5.81.5" + checksum: 10c0/8d3af841f0e2f3fba1dfd100a8bbc2d07e7b1d5b8570d3d0d8bba5127601c2788e5f7c84839f93f3b0cbe54f14866c1fef8e3f9e745f02d27afaaeea33b7b755 languageName: node linkType: hard "@tanstack/react-query@npm:^5.80.7": - version: 5.81.4 - resolution: "@tanstack/react-query@npm:5.81.4" + version: 5.81.5 + resolution: "@tanstack/react-query@npm:5.81.5" dependencies: - "@tanstack/query-core": "npm:5.81.4" + "@tanstack/query-core": "npm:5.81.5" peerDependencies: react: ^18 || ^19 - checksum: 10c0/f381656c1e61da875c03b6578ca1c44a456614c25a0bfaef0811a3fafcdb6d67243aae777d0a0e60298d22c71ccccaaa3b0c61195de8e1b0aed3de24c42dc831 + checksum: 10c0/04803dd7d7d6ac73ba8c55bb25d76aa2f74d173d1574a113c35559bf6a8ac7d04a75db5e470e9a2e4132fee5e0ce1ab23bd8c27d17f70a461aca0e7ae740e76b languageName: node linkType: hard From 6231d6cd45037f7611374f9c197d06391741488f Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Sat, 28 Jun 2025 17:11:56 +0200 Subject: [PATCH 20/38] feat(event): display user calendars on event creation and edit pages --- package.json | 1 + src/app/(main)/events/[eventID]/page.tsx | 308 +++++++++-------- src/app/(main)/events/edit/[eventID]/page.tsx | 18 +- .../api/{user/[user] => }/calendar/route.ts | 65 +++- .../api/{user/[user] => }/calendar/swagger.ts | 9 +- .../{user/[user] => }/calendar/validation.ts | 9 +- src/app/api/search/user/route.ts | 2 +- src/components/calendar.tsx | 199 ++++++++++- src/components/custom-toolbar.css | 5 + src/components/custom-ui/app-sidebar.tsx | 2 +- src/components/forms/event-form.tsx | 317 ++++++++++-------- yarn.lock | 17 + 12 files changed, 615 insertions(+), 337 deletions(-) rename src/app/api/{user/[user] => }/calendar/route.ts (73%) rename src/app/api/{user/[user] => }/calendar/swagger.ts (77%) rename src/app/api/{user/[user] => }/calendar/validation.ts (91%) diff --git a/package.json b/package.json index 7ec859d..e5e8c9e 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", "@hookform/resolvers": "^5.0.1", + "@marko19907/string-to-color": "^1.0.0", "@prisma/client": "^6.9.0", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-collapsible": "^1.1.11", diff --git a/src/app/(main)/events/[eventID]/page.tsx b/src/app/(main)/events/[eventID]/page.tsx index bfc390d..81b98cf 100644 --- a/src/app/(main)/events/[eventID]/page.tsx +++ b/src/app/(main)/events/[eventID]/page.tsx @@ -70,169 +70,167 @@ export default function ShowEvent() { }; return ( -
- - + + - -
-
-
-
- -
-
-

- {event.title || 'Untitled Event'} -

-
-
+ +
+
+
+
+
-
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- - -
-
+
+

+ {event.title || 'Untitled Event'} +

-
-
-
-
- - -
-
-
- - -
-
-
- {' '} -
- {event.participants?.map((user) => ( - - ))} -
-
+
+
+
+
+ +
- -
-
- {session.data?.user?.id === event.organizer.id ? ( - - - - - - - Delete Event - - Are you sure you want to delete the event “ - {event.title}”? This action cannot be undone. - - - - - - - - - ) : null} +
+ + +
+
+ + +
+
+
+ +
-
- {session.data?.user?.id === event.organizer.id ? ( - - ) : null} +
+ +
+
+
+
+
+ + +
+
+
+ + +
+
+
+ {' '} +
+ {event.participants?.map((user) => ( + + ))} +
+
+
+ +
+
+ {session.data?.user?.id === event.organizer.id ? ( + + + + + + + Delete Event + + Are you sure you want to delete the event “ + {event.title}”? This action cannot be undone. + + + + + + + + + ) : null} +
+
+ {session.data?.user?.id === event.organizer.id ? ( + + ) : null} +
+
- - -
+
+ + ); } diff --git a/src/app/(main)/events/edit/[eventID]/page.tsx b/src/app/(main)/events/edit/[eventID]/page.tsx index 42c6e8b..b099f10 100644 --- a/src/app/(main)/events/edit/[eventID]/page.tsx +++ b/src/app/(main)/events/edit/[eventID]/page.tsx @@ -9,16 +9,14 @@ export default async function Page({ }) { const eventID = (await params).eventID; return ( -
- - + + - - - - - - -
+ + + + + + ); } diff --git a/src/app/api/user/[user]/calendar/route.ts b/src/app/api/calendar/route.ts similarity index 73% rename from src/app/api/user/[user]/calendar/route.ts rename to src/app/api/calendar/route.ts index 62142e9..fa6dd47 100644 --- a/src/app/api/user/[user]/calendar/route.ts +++ b/src/app/api/calendar/route.ts @@ -15,7 +15,7 @@ import { } from '@/app/api/validation'; import { z } from 'zod/v4'; -export const GET = auth(async function GET(req, { params }) { +export const GET = auth(async function GET(req) { const authCheck = userAuthenticated(req); if (!authCheck.continue) return returnZodTypeCheckedResponse( @@ -24,7 +24,22 @@ export const GET = auth(async function GET(req, { params }) { authCheck.metadata, ); - const dataRaw = Object.fromEntries(new URL(req.url).searchParams); + const dataRaw: Record = {}; + for (const [key, value] of req.nextUrl.searchParams.entries()) { + if (key.endsWith('[]')) { + const cleanKey = key.slice(0, -2); + if (!dataRaw[cleanKey]) { + dataRaw[cleanKey] = []; + } + if (Array.isArray(dataRaw[cleanKey])) { + (dataRaw[cleanKey] as string[]).push(value); + } else { + dataRaw[cleanKey] = [dataRaw[cleanKey] as string, value]; + } + } else { + dataRaw[key] = value; + } + } const data = await userCalendarQuerySchema.safeParseAsync(dataRaw); if (!data.success) return returnZodTypeCheckedResponse( @@ -36,15 +51,15 @@ export const GET = auth(async function GET(req, { params }) { }, { status: 400 }, ); - const { end, start } = data.data; + const { end, start, userIds } = data.data; const requestUserId = authCheck.user.id; - const requestedUserId = (await params).user; - - const requestedUser = await prisma.user.findFirst({ + const requestedUser = await prisma.user.findMany({ where: { - id: requestedUserId, + id: { + in: userIds, + }, }, select: { meetingParts: { @@ -64,6 +79,7 @@ export const GET = auth(async function GET(req, { params }) { }, }, select: { + user_id: true, meeting: { select: { id: true, @@ -136,6 +152,7 @@ export const GET = auth(async function GET(req, { params }) { start_time: 'asc', }, select: { + user_id: true, id: true, reason: true, start_time: true, @@ -153,46 +170,64 @@ export const GET = auth(async function GET(req, { params }) { if (!requestedUser) return returnZodTypeCheckedResponse( ErrorResponseSchema, - { success: false, message: 'User not found' }, + { success: false, message: 'User/s not found' }, { status: 404 }, ); const calendar: z.input = []; - for (const event of requestedUser.meetingParts) { + for (const event of requestedUser.map((r) => r.meetingParts).flat()) { if ( event.meeting.participants.some((p) => p.user.id === requestUserId) || event.meeting.organizer_id === requestUserId ) { - calendar.push({ ...event.meeting, type: 'event' }); + calendar.push({ + ...event.meeting, + type: 'event', + users: event.meeting.participants + .map((p) => p.user.id) + .filter((id) => userIds.includes(id)), + }); } else { calendar.push({ id: event.meeting.id, start_time: event.meeting.start_time, end_time: event.meeting.end_time, type: 'blocked_private', + users: event.meeting.participants + .map((p) => p.user.id) + .filter((id) => userIds.includes(id)), }); } } - for (const event of requestedUser.meetingsOrg) { + for (const event of requestedUser.map((r) => r.meetingsOrg).flat()) { if ( event.participants.some((p) => p.user.id === requestUserId) || event.organizer_id === requestUserId ) { - calendar.push({ ...event, type: 'event' }); + calendar.push({ + ...event, + type: 'event', + users: event.participants + .map((p) => p.user.id) + .filter((id) => userIds.includes(id)), + }); } else { calendar.push({ id: event.id, start_time: event.start_time, end_time: event.end_time, type: 'blocked_private', + users: event.participants + .map((p) => p.user.id) + .filter((id) => userIds.includes(id)), }); } } - for (const slot of requestedUser.blockedSlots) { - if (requestUserId === requestedUserId) { + for (const slot of requestedUser.map((r) => r.blockedSlots).flat()) { + if (requestUserId === userIds[0] && userIds.length === 1) { calendar.push({ start_time: slot.start_time, end_time: slot.end_time, @@ -204,6 +239,7 @@ export const GET = auth(async function GET(req, { params }) { created_at: slot.created_at, updated_at: slot.updated_at, type: 'blocked_owned', + users: [requestUserId], }); } else { calendar.push({ @@ -211,6 +247,7 @@ export const GET = auth(async function GET(req, { params }) { end_time: slot.end_time, id: slot.id, type: 'blocked_private', + users: [slot.user_id], }); } } diff --git a/src/app/api/user/[user]/calendar/swagger.ts b/src/app/api/calendar/swagger.ts similarity index 77% rename from src/app/api/user/[user]/calendar/swagger.ts rename to src/app/api/calendar/swagger.ts index fb48629..b4f5898 100644 --- a/src/app/api/user/[user]/calendar/swagger.ts +++ b/src/app/api/calendar/swagger.ts @@ -7,17 +7,12 @@ import { userNotFoundResponse, } from '@/lib/defaultApiResponses'; import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; -import zod from 'zod/v4'; -import { UserIdParamSchema } from '@/app/api/validation'; export default function registerSwaggerPaths(registry: OpenAPIRegistry) { registry.registerPath({ method: 'get', - path: '/api/user/{user}/calendar', + path: '/api/calendar', request: { - params: zod.object({ - user: UserIdParamSchema, - }), query: userCalendarQuerySchema, }, responses: { @@ -32,6 +27,6 @@ export default function registerSwaggerPaths(registry: OpenAPIRegistry) { ...notAuthenticatedResponse, ...userNotFoundResponse, }, - tags: ['User'], + tags: ['Calendar'], }); } diff --git a/src/app/api/user/[user]/calendar/validation.ts b/src/app/api/calendar/validation.ts similarity index 91% rename from src/app/api/user/[user]/calendar/validation.ts rename to src/app/api/calendar/validation.ts index 1572793..5bf45a6 100644 --- a/src/app/api/user/[user]/calendar/validation.ts +++ b/src/app/api/calendar/validation.ts @@ -14,6 +14,8 @@ export const BlockedSlotSchema = zod end_time: eventEndTimeSchema, type: zod.literal('blocked_private'), id: zod.string(), + users: zod.string().array(), + user_id: zod.string().optional(), }) .openapi('BlockedSlotSchema', { description: 'Blocked time slot in the user calendar', @@ -31,17 +33,21 @@ export const OwnedBlockedSlotSchema = zod created_at: zod.date().nullish(), updated_at: zod.date().nullish(), type: zod.literal('blocked_owned'), + users: zod.string().array(), + user_id: zod.string().optional(), }) .openapi('OwnedBlockedSlotSchema', { description: 'Blocked slot owned by the user', }); export const VisibleSlotSchema = EventSchema.omit({ - organizer: true, participants: true, + organizer: true, }) .extend({ type: zod.literal('event'), + users: zod.string().array(), + user_id: zod.string().optional(), }) .openapi('VisibleSlotSchema', { description: 'Visible time slot in the user calendar', @@ -86,6 +92,7 @@ export const userCalendarQuerySchema = zod ); return endOfWeek; }), + userIds: zod.string().array(), }) .openapi('UserCalendarQuerySchema', { description: 'Query parameters for filtering the user calendar', diff --git a/src/app/api/search/user/route.ts b/src/app/api/search/user/route.ts index a8b6414..0bcb6cf 100644 --- a/src/app/api/search/user/route.ts +++ b/src/app/api/search/user/route.ts @@ -19,7 +19,7 @@ export const GET = auth(async function GET(req) { authCheck.metadata, ); - const dataRaw = Object.fromEntries(new URL(req.url).searchParams); + const dataRaw = Object.fromEntries(req.nextUrl.searchParams); const data = await searchUserSchema.safeParseAsync(dataRaw); if (!data.success) return returnZodTypeCheckedResponse( diff --git a/src/components/calendar.tsx b/src/components/calendar.tsx index a8d6005..ba9e730 100644 --- a/src/components/calendar.tsx +++ b/src/components/calendar.tsx @@ -7,7 +7,6 @@ import '@/components/react-big-calendar.css'; import 'react-big-calendar/lib/addons/dragAndDrop/styles.css'; import CustomToolbar from '@/components/custom-toolbar'; import React from 'react'; -import { useGetApiUserUserCalendar } from '@/generated/api/user/user'; import { useRouter } from 'next/navigation'; import { usePatchApiEventEventID } from '@/generated/api/event/event'; import { useSession } from 'next-auth/react'; @@ -17,6 +16,11 @@ import { ErrorBoundary } from 'react-error-boundary'; import { Button } from '@/components/ui/button'; import { fromZodIssue } from 'zod-validation-error/v4'; import type { $ZodIssue } from 'zod/v4/core'; +import { useGetApiCalendar } from '@/generated/api/calendar/calendar'; +//import { +// generateColor, +// generateSecondaryColor, +//} from '@marko19907/string-to-color'; moment.updateLocale('en', { week: { @@ -25,12 +29,31 @@ moment.updateLocale('en', { }, }); +function eventPropGetter() { + // event: { + // id: string; + // start: Date; + // end: Date; + // type: UserCalendarSchemaItem['type']; + // userId?: string; + // } + return { + // style: { + // backgroundColor: generateColor(event.userId || 'defaultColor', { + // saturation: 0.7, + // lightness: 0.5, + // }), + // }, + }; +} + const DaDRBCalendar = withDragAndDrop< { id: string; start: Date; end: Date; type: UserCalendarSchemaItem['type']; + userId?: string; }, { id: string; @@ -44,9 +67,20 @@ const localizer = momentLocalizer(moment); export default function Calendar({ userId, height, + additionalEvents = [], + className, }: { - userId?: string; + userId?: string | string[]; height: string; + additionalEvents?: { + id: string; + title: string; + start: Date; + end: Date; + type: UserCalendarSchemaItem['type']; + userId?: string; + }[]; + className?: string; }) { return ( @@ -67,10 +101,26 @@ export default function Calendar({
)} > - {userId ? ( - + {typeof userId === 'string' ? ( + + ) : Array.isArray(userId) && userId.length > 0 ? ( + ) : ( - + )} )} @@ -81,9 +131,20 @@ export default function Calendar({ function CalendarWithUserEvents({ userId, height, + additionalEvents, + className, }: { userId: string; height: string; + additionalEvents?: { + id: string; + title: string; + start: Date; + end: Date; + type: UserCalendarSchemaItem['type']; + userId?: string; + }[]; + className?: string; }) { const sesstion = useSession(); const [currentView, setCurrentView] = React.useState< @@ -92,9 +153,9 @@ function CalendarWithUserEvents({ const [currentDate, setCurrentDate] = React.useState(new Date()); const router = useRouter(); - const { data, refetch, error, isError } = useGetApiUserUserCalendar( - userId, + const { data, refetch, error, isError } = useGetApiCalendar( { + userIds: [userId, userId + '_blocked'], start: moment(currentDate) .startOf( currentView === 'agenda' @@ -137,6 +198,8 @@ function CalendarWithUserEvents({ return ( { setCurrentDate(date); }} - events={ - data?.data.calendar.map((event) => ({ + events={[ + ...(data?.data.calendar.map((event) => ({ id: event.id, title: event.type === 'event' ? event.title : 'Blocker', start: new Date(event.start_time), end: new Date(event.end_time), type: event.type, - })) ?? [] - } + userId: event.users[0], + })) ?? []), + ...(additionalEvents ?? []), + ]} onSelectEvent={(event) => { router.push(`/events/${event.id}`); }} @@ -228,7 +293,114 @@ function CalendarWithUserEvents({ ); } -function CalendarWithoutUserEvents({ height }: { height: string }) { +function CalendarWithMultiUserEvents({ + userIds, + height, + additionalEvents, + className, +}: { + userIds: string[]; + height: string; + additionalEvents?: { + id: string; + title: string; + start: Date; + end: Date; + type: UserCalendarSchemaItem['type']; + userId?: string; + }[]; + className?: string; +}) { + const [currentView, setCurrentView] = React.useState< + 'month' | 'week' | 'day' | 'agenda' | 'work_week' + >('week'); + const [currentDate, setCurrentDate] = React.useState(new Date()); + + const { data, error, isError } = useGetApiCalendar( + { + userIds: userIds, + start: moment(currentDate) + .startOf( + currentView === 'agenda' + ? 'month' + : currentView === 'work_week' + ? 'week' + : currentView, + ) + .toISOString(), + end: moment(currentDate) + .endOf( + currentView === 'agenda' + ? 'month' + : currentView === 'work_week' + ? 'week' + : currentView, + ) + .toISOString(), + }, + { + query: { + refetchOnWindowFocus: true, + refetchOnReconnect: true, + refetchOnMount: true, + }, + }, + ); + + if (isError) { + throw error.response?.data || 'Failed to fetch calendar data'; + } + + return ( + { + setCurrentDate(date); + }} + events={[ + ...(data?.data.calendar.map((event) => ({ + id: event.id, + title: event.type === 'event' ? event.title : 'Blocker', + start: new Date(event.start_time), + end: new Date(event.end_time), + type: event.type, + userId: event.users[0], + })) ?? []), + ...(additionalEvents ?? []), + ]} + /> + ); +} + +function CalendarWithoutUserEvents({ + height, + additionalEvents, + className, +}: { + height: string; + additionalEvents?: { + id: string; + title: string; + start: Date; + end: Date; + type: UserCalendarSchemaItem['type']; + userId?: string; + }[]; + className?: string; +}) { const [currentView, setCurrentView] = React.useState< 'month' | 'week' | 'day' | 'agenda' | 'work_week' >('week'); @@ -236,6 +408,8 @@ function CalendarWithoutUserEvents({ height }: { height: string }) { return ( { setCurrentDate(date); }} + events={additionalEvents} /> ); } diff --git a/src/components/custom-toolbar.css b/src/components/custom-toolbar.css index 37a2a4d..1230384 100644 --- a/src/components/custom-toolbar.css +++ b/src/components/custom-toolbar.css @@ -32,6 +32,11 @@ align-items: center; } +.custom-toolbar .navigation-controls .handleWeek { + display: grid; + grid-template-columns: 1fr 1fr; +} + .custom-toolbar .navigation-controls button { padding: 8px 12px; color: #ffffff; diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx index f823970..4188141 100644 --- a/src/components/custom-ui/app-sidebar.tsx +++ b/src/components/custom-ui/app-sidebar.tsx @@ -75,7 +75,7 @@ export function AppSidebar() { className='group-data-[collapsible=]:hidden group-data-[mobile=true]/mobile:hidden' > - + diff --git a/src/components/forms/event-form.tsx b/src/components/forms/event-form.tsx index a445080..00322cc 100644 --- a/src/components/forms/event-form.tsx +++ b/src/components/forms/event-form.tsx @@ -21,6 +21,16 @@ import { useSearchParams } from 'next/navigation'; import zod from 'zod/v4'; import { PublicUserSchema } from '@/app/api/user/validation'; +import Calendar from '@/components/calendar'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '../ui/dialog'; type User = zod.output; @@ -68,6 +78,8 @@ const EventForm: React.FC = (props) => { const [location, setLocation] = React.useState(''); const [description, setDescription] = React.useState(''); + const [calendarOpen, setCalendarOpen] = React.useState(false); + // Update state when event data loads React.useEffect(() => { if (props.type === 'edit' && event) { @@ -194,149 +206,182 @@ const EventForm: React.FC = (props) => { return
Error loading event.
; return ( -
-
-
-
- -
-
- setTitle(e.target.value)} - /> -
-
-
-
-
- -
-
- -
-
- setLocation(e.target.value)} - /> -
-
-
- - + <> + + +
+
+
+ +
+
+ setTitle(e.target.value)} + /> +
+
-
- -

- {updatedAtDisplay} -

-
-
-
-
-
-
-
- - +
+
+ +
+
+ +
+
+ setLocation(e.target.value)} + /> +
+
+
+ + +
+
+ +

+ {updatedAtDisplay} +

+
-
- setDescription(e.target.value)} - > -
-
-
- - { - setSelectedParticipants((current) => - current.find((u) => u.id === user.id) - ? current - : [...current, user], - ); - }} - removeUserAction={(user) => { - setSelectedParticipants((current) => - current.filter((u) => u.id !== user.id), - ); - }} - /> -
- {selectedParticipants.map((user) => ( - +
+
+
+ + +
+
+
+ setDescription(e.target.value)} + > +
+
+
+ + { + setSelectedParticipants((current) => + current.find((u) => u.id === user.id) + ? current + : [...current, user], + ); + }} + removeUserAction={(user) => { + setSelectedParticipants((current) => + current.filter((u) => u.id !== user.id), + ); + }} /> - ))} + + + +
+ {selectedParticipants.map((user) => ( + + ))} +
+
-
-
-
-
- +
+
+ +
+
+ +
+
+ {isSuccess &&

Event created!

} + {error &&

Error: {error.message}

}
-
- -
-
- {isSuccess &&

Event created!

} - {error &&

Error: {error.message}

} -
- + + + + Calendar + + Calendar for selected participants + + + + u.id)} + additionalEvents={[ + { + id: 'temp-event', + title: title || 'New Event', + start: startDate ? new Date(startDate) : new Date(), + end: endDate ? new Date(endDate) : new Date(), + type: 'event', + userId: 'create-event', + }, + ]} + height='600px' + /> + + + + ); }; diff --git a/yarn.lock b/yarn.lock index 816d896..b241026 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1013,6 +1013,15 @@ __metadata: languageName: node linkType: hard +"@marko19907/string-to-color@npm:^1.0.0": + version: 1.0.0 + resolution: "@marko19907/string-to-color@npm:1.0.0" + dependencies: + esm-seedrandom: "npm:^3.0.5" + checksum: 10c0/fd297937fb5a8a0c3372bc614f9a4e21842e3392ef54e0d323f0fe730ec5b5a441f921e63b3b151bd39ca64a120ddfe905d1a67ca62ed637ade69c96c55fe641 + languageName: node + linkType: hard + "@napi-rs/wasm-runtime@npm:^0.2.11": version: 0.2.11 resolution: "@napi-rs/wasm-runtime@npm:0.2.11" @@ -5717,6 +5726,13 @@ __metadata: languageName: node linkType: hard +"esm-seedrandom@npm:^3.0.5": + version: 3.0.5 + resolution: "esm-seedrandom@npm:3.0.5" + checksum: 10c0/6fe5a33f31bce0e733814df884cdfc27812e4b04ce973f7cf008b7b8500ecab6706f54591841bd12dd3bdd9bbb153abf04fb8efd4934a5b0ab273bff114cdb3b + languageName: node + linkType: hard + "espree@npm:^10.0.1, espree@npm:^10.4.0": version: 10.4.0 resolution: "espree@npm:10.4.0" @@ -7683,6 +7699,7 @@ __metadata: "@fortawesome/free-solid-svg-icons": "npm:^6.7.2" "@fortawesome/react-fontawesome": "npm:^0.2.2" "@hookform/resolvers": "npm:^5.0.1" + "@marko19907/string-to-color": "npm:^1.0.0" "@prisma/client": "npm:^6.9.0" "@radix-ui/react-avatar": "npm:^1.1.10" "@radix-ui/react-collapsible": "npm:^1.1.11" From 7d2d5c55e8ffcb22c11bf6cda8d2a978ffbd5d0f Mon Sep 17 00:00:00 2001 From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com> Date: Sun, 29 Jun 2025 19:03:26 +0200 Subject: [PATCH 21/38] feat: tempcommit --- src/app/api/logout/route.ts | 8 + src/components/custom-ui/labeled-input.tsx | 8 +- src/components/misc/settings-page.tsx | 174 +++++++++++---------- 3 files changed, 105 insertions(+), 85 deletions(-) create mode 100644 src/app/api/logout/route.ts diff --git a/src/app/api/logout/route.ts b/src/app/api/logout/route.ts new file mode 100644 index 0000000..ba89440 --- /dev/null +++ b/src/app/api/logout/route.ts @@ -0,0 +1,8 @@ +import { signOut } from '@/auth'; +import { NextResponse } from 'next/server'; + +export const GET = async () => { + await signOut(); + + return NextResponse.redirect('/login'); +}; diff --git a/src/components/custom-ui/labeled-input.tsx b/src/components/custom-ui/labeled-input.tsx index 23601d9..5ea9caf 100644 --- a/src/components/custom-ui/labeled-input.tsx +++ b/src/components/custom-ui/labeled-input.tsx @@ -8,6 +8,7 @@ import { cn } from '@/lib/utils'; export default function LabeledInput({ type, label, + subtext, placeholder, value, name, @@ -16,8 +17,8 @@ export default function LabeledInput({ error, ...rest }: { - type: 'text' | 'email' | 'password' | 'file'; label: string; + subtext?: string; placeholder?: string; value?: string; name?: string; @@ -30,6 +31,11 @@ export default function LabeledInput({ return (
+ {subtext && ( + + )} {variantSize === 'textarea' ? (