+
+
-
-
-
-
-
+
- {updatedAtDisplay}
+ {!isClient || isLoading
+ ? 'Loading...'
+ : data?.data.user.name || 'Unknown 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) => (
-
- ))}
-
+ setDescription(e.target.value)}
+ data-cy='event-description-input'
+ >
-
-
-
-
-
-
-
+
+
+
{
+ 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}
}
-
-
-
- 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',
- colorOverride: '#ff9800',
- },
- ]}
- height='600px'
- />
-
-
-
- >
+
+
+
+
+
+
+
+
+
+ {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',
+ colorOverride: '#ff9800',
+ },
+ ]}
+ height='600px'
+ />
+
+
+
);
};
diff --git a/src/components/forms/login-form.tsx b/src/components/forms/login-form.tsx
index c1139b4..fdcceac 100644
--- a/src/components/forms/login-form.tsx
+++ b/src/components/forms/login-form.tsx
@@ -4,11 +4,21 @@ import React, { useState, useRef } from 'react';
import { useRouter } from 'next/navigation';
import LabeledInput from '@/components/custom-ui/labeled-input';
-import { Button } from '@/components/ui/button';
import useZodForm from '@/lib/hooks/useZodForm';
import { loginSchema, registerSchema } from '@/lib/auth/validation';
import { loginAction } from '@/lib/auth/login';
import { registerAction } from '@/lib/auth/register';
+import { IconButton } from '../buttons/icon-button';
+import {
+ FileKey,
+ FileKey2,
+ LogIn,
+ MailOpen,
+ RotateCcwKey,
+ UserCheck,
+ UserPen,
+ UserPlus,
+} from 'lucide-react';
function LoginFormElement({
setIsSignUp,
@@ -56,6 +66,7 @@ function LoginFormElement({
-
-
+
{formState.errors.root?.message && (
@@ -156,27 +174,30 @@ function RegisterFormElement({
{...register('lastName')}
data-cy='last-name-input'
/>
-
+
-
-
+
{formState.errors.root?.message && (
diff --git a/src/components/misc/header.tsx b/src/components/misc/header.tsx
index dd9e36d..c7718c2 100644
--- a/src/components/misc/header.tsx
+++ b/src/components/misc/header.tsx
@@ -7,12 +7,12 @@ import UserDropdown from '@/components/misc/user-dropdown';
const items = [
{
- title: 'Calendar',
+ title: 'Inbox',
url: '#',
icon: Inbox,
},
{
- title: 'Friends',
+ title: 'Notifications',
url: '#',
icon: BellRing,
},
@@ -25,17 +25,21 @@ export default function Header({
}>) {
return (
-
+
- Search
+
{items.map((item) => (
diff --git a/src/components/misc/profile-picture-upload.tsx b/src/components/misc/profile-picture-upload.tsx
new file mode 100644
index 0000000..d773873
--- /dev/null
+++ b/src/components/misc/profile-picture-upload.tsx
@@ -0,0 +1,36 @@
+import Image from 'next/image';
+import { Avatar } from '../ui/avatar';
+import { useGetApiUserMe } from '@/generated/api/user/user';
+import { User } from 'lucide-react';
+
+import { Input } from '../ui/input';
+
+export default function ProfilePictureUpload({
+ className,
+ ...props
+}: {
+ className?: string;
+} & React.InputHTMLAttributes) {
+ const { data } = useGetApiUserMe();
+ return (
+ <>
+
+ >
+ );
+}
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..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 (
@@ -41,11 +42,13 @@ export default function UserDropdown() {
- Settings
+
+ Settings
+
-
- Logout
-
+
+ Logout
+
);
diff --git a/src/components/settings/settings-dropdown.tsx b/src/components/settings/settings-dropdown.tsx
new file mode 100644
index 0000000..6c23d07
--- /dev/null
+++ b/src/components/settings/settings-dropdown.tsx
@@ -0,0 +1,165 @@
+'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,
+ Key,
+} 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 account details',
+ icon: User,
+ },
+ {
+ label: 'Password',
+ value: 'password',
+ description: 'Manage your password',
+ icon: Key,
+ },
+ {
+ label: 'Notifications',
+ value: 'notifications',
+ description: 'Choose notification Preferences',
+ icon: Bell,
+ },
+ {
+ label: 'Calendar',
+ value: 'calendarAvailability',
+ description: 'Manage calendar display, availability and iCal integration',
+ icon: Calendar,
+ },
+ {
+ label: 'Privacy',
+ value: 'sharingPrivacy',
+ description: 'Control who can see your calendar and book time with you',
+ icon: Shield,
+ },
+ {
+ label: 'Appearance',
+ value: 'appearance',
+ description: 'Customize the look and feel of the application',
+ 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/settings/settings-page.tsx b/src/components/settings/settings-page.tsx
new file mode 100644
index 0000000..26eced2
--- /dev/null
+++ b/src/components/settings/settings-page.tsx
@@ -0,0 +1,59 @@
+'use client';
+
+import { useState } from 'react';
+
+import { SettingsDropdown } from '@/components/settings/settings-dropdown';
+
+import AccountTab from './tabs/account';
+import NotificationsTab from './tabs/notifications';
+import CalendarTab from './tabs/calendar';
+import PrivacyTab from './tabs/privacy';
+import AppearanceTab from './tabs/appearance';
+import PasswordTab from './tabs/password';
+
+export default function SettingsPage() {
+ const [currentSection, setCurrentSection] = useState('general');
+
+ const renderSettingsContent = () => {
+ switch (currentSection) {
+ case 'general':
+ return ;
+
+ case 'password':
+ return ;
+
+ case 'notifications':
+ return ;
+
+ case 'calendarAvailability':
+ return ;
+
+ case 'sharingPrivacy':
+ return ;
+
+ case 'appearance':
+ return ;
+
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {/* TODO: Fix overflow */}
+
+ {renderSettingsContent()}
+
+
+ );
+}
diff --git a/src/components/settings/tabs/account.tsx b/src/components/settings/tabs/account.tsx
new file mode 100644
index 0000000..db18b0f
--- /dev/null
+++ b/src/components/settings/tabs/account.tsx
@@ -0,0 +1,287 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+
+import { Label } from '@/components/ui/label';
+import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import {
+ useDeleteApiUserMe,
+ useGetApiUserMe,
+ usePatchApiUserMe,
+} from '@/generated/api/user/user';
+import LabeledInput from '@/components/custom-ui/labeled-input';
+import { GroupWrapper } from '@/components/wrappers/group-wrapper';
+
+import ProfilePictureUpload from '@/components/misc/profile-picture-upload';
+import { CalendarClock, MailOpen, UserPen } from 'lucide-react';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog';
+import useZodForm from '@/lib/hooks/useZodForm';
+import { updateUserClientSchema } from '@/app/api/user/me/validation';
+import { useRouter } from 'next/navigation';
+import { toast } from 'sonner';
+import { ToastInner } from '@/components/misc/toast-inner';
+
+export default function AccountTab() {
+ const router = useRouter();
+ const { data, refetch } = useGetApiUserMe();
+ const deleteUser = useDeleteApiUserMe();
+ const updateAccount = usePatchApiUserMe();
+
+ const { handleSubmit, formState, register } = useZodForm(
+ updateUserClientSchema,
+ );
+
+ const onSubmit = handleSubmit(async (submitData) => {
+ await updateAccount.mutateAsync(
+ {
+ data: {
+ first_name:
+ submitData?.first_name !== data?.data.user.first_name
+ ? submitData?.first_name
+ : undefined,
+ last_name:
+ submitData?.last_name !== data?.data.user.last_name
+ ? submitData?.last_name
+ : undefined,
+ name:
+ submitData?.name !== data?.data.user.name
+ ? submitData?.name
+ : undefined,
+ email:
+ submitData?.email !== data?.data.user.email
+ ? submitData?.email
+ : undefined,
+ image:
+ submitData?.image !== data?.data.user.image
+ ? submitData?.image
+ : undefined,
+ timezone:
+ submitData?.timezone !== data?.data.user.timezone
+ ? submitData?.timezone
+ : undefined,
+ },
+ },
+ {
+ onSuccess: () => {
+ refetch();
+ toast.custom((t) => (
+
+ ));
+ },
+ onError: (error) => {
+ toast.custom((t) => (
+
+ ));
+ },
+ },
+ );
+ });
+
+ if (!data) {
+ return (
+
+
+
+
Loading Settings...
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/settings/tabs/appearance.tsx b/src/components/settings/tabs/appearance.tsx
new file mode 100644
index 0000000..46113ab
--- /dev/null
+++ b/src/components/settings/tabs/appearance.tsx
@@ -0,0 +1,55 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+
+import { Label } from '@/components/ui/label';
+import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
+import { GroupWrapper } from '@/components/wrappers/group-wrapper';
+import { useRouter } from 'next/navigation';
+import { ThemePicker } from '@/components/misc/theme-picker';
+
+export default function AppearanceTab() {
+ const router = useRouter();
+ return (
+ <>
+
+
+
+
+ Appearance
+
+
+ {/*-------------------- Change Theme --------------------*/}
+
+
+
+
+
+
+ {/*-------------------- Change Theme --------------------*/}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/settings/tabs/calendar.tsx b/src/components/settings/tabs/calendar.tsx
new file mode 100644
index 0000000..02feb47
--- /dev/null
+++ b/src/components/settings/tabs/calendar.tsx
@@ -0,0 +1,226 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+
+import { Label } from '@/components/ui/label';
+import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { GroupWrapper } from '@/components/wrappers/group-wrapper';
+import { Switch } from '@/components/ui/switch';
+import { useRouter } from 'next/navigation';
+import LabeledInput from '@/components/custom-ui/labeled-input';
+import {
+ CalendarArrowDown,
+ CalendarArrowUp,
+ CalendarCheck,
+ CalendarPlus,
+ ClockAlert,
+ ClockFading,
+} from 'lucide-react';
+import { IconButton } from '@/components/buttons/icon-button';
+
+export default function CalendarTab() {
+ const router = useRouter();
+ return (
+ <>
+
+
+
+
+ Calendar & Availability
+
+
+ {/*-------------------- Date & Time Format --------------------*/}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*-------------------- Date & Time Format --------------------*/}
+ {/*-------------------- Calendar --------------------*/}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*-------------------- Calendar --------------------*/}
+ {/*-------------------- Availability --------------------*/}
+
+
+
+
+
+ Define your typical available hours (e.g., Monday-Friday,
+ 9 AM - 5 PM).
+
+
+
+
+
+
+
+
+
+
+
+ {/*-------------------- Availability --------------------*/}
+ {/*-------------------- iCalendar Integration --------------------*/}
+
+
+
+
+
+
+ Get iCal Export URL
+
+
+ Download .ics File
+
+
+
+
+ {/*-------------------- iCalendar Integration --------------------*/}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/settings/tabs/notifications.tsx b/src/components/settings/tabs/notifications.tsx
new file mode 100644
index 0000000..f2862a1
--- /dev/null
+++ b/src/components/settings/tabs/notifications.tsx
@@ -0,0 +1,134 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+
+import { Label } from '@/components/ui/label';
+import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { GroupWrapper } from '@/components/wrappers/group-wrapper';
+import { Switch } from '@/components/ui/switch';
+import { useRouter } from 'next/navigation';
+
+export default function NotificationsTab() {
+ const router = useRouter();
+ return (
+ <>
+
+
+
+
+ Notification Preferences
+
+
+ {/*-------------------- All --------------------*/}
+
+
+
+
+
+
+ {/*-------------------- All --------------------*/}
+ {/*-------------------- Meetings --------------------*/}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*-------------------- Meetings --------------------*/}
+ {/*-------------------- Social --------------------*/}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*-------------------- Social --------------------*/}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/settings/tabs/password.tsx b/src/components/settings/tabs/password.tsx
new file mode 100644
index 0000000..53203c4
--- /dev/null
+++ b/src/components/settings/tabs/password.tsx
@@ -0,0 +1,151 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+
+import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
+import {
+ useGetApiUserMe,
+ usePatchApiUserMePassword,
+} from '@/generated/api/user/user';
+import LabeledInput from '@/components/custom-ui/labeled-input';
+import { GroupWrapper } from '@/components/wrappers/group-wrapper';
+
+import { BookKey, FileKey, FileKey2, RotateCcwKey } from 'lucide-react';
+import useZodForm from '@/lib/hooks/useZodForm';
+import { updateUserPasswordServerSchema } from '@/app/api/user/me/validation';
+import { useRouter } from 'next/navigation';
+
+export default function PasswordTab() {
+ const router = useRouter();
+ const { data } = useGetApiUserMe();
+ const updatePassword = usePatchApiUserMePassword();
+
+ const { handleSubmit, formState, register, setError } = useZodForm(
+ updateUserPasswordServerSchema,
+ );
+
+ const onSubmit = handleSubmit(async (data) => {
+ await updatePassword.mutateAsync(
+ {
+ data: data,
+ },
+ {
+ onSuccess: () => {
+ router.refresh();
+ },
+ onError: (error) => {
+ if (error instanceof Error) {
+ setError('root', {
+ message: error.response?.data.message,
+ });
+ } else {
+ setError('root', {
+ message: 'An unknown error occurred.',
+ });
+ }
+ },
+ },
+ );
+ });
+
+ if (!data) {
+ return (
+
+
+
+
Loading Settings...
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/settings/tabs/privacy.tsx b/src/components/settings/tabs/privacy.tsx
new file mode 100644
index 0000000..a0c5f2e
--- /dev/null
+++ b/src/components/settings/tabs/privacy.tsx
@@ -0,0 +1,143 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+
+import { Label } from '@/components/ui/label';
+import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { GroupWrapper } from '@/components/wrappers/group-wrapper';
+import { useRouter } from 'next/navigation';
+import { IconButton } from '@/components/buttons/icon-button';
+import { UserLock } from 'lucide-react';
+
+export default function PrivacyTab() {
+ const router = useRouter();
+ return (
+ <>
+
+
+
+
+ Sharing & Privacy
+
+
+ {/*-------------------- Privacy Settigs --------------------*/}
+
+
+
+
+
+ Default setting for new friends.
+
+
+
+
+
+
+ (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.
+
+
+ Manage Blocked Users
+
+
+
+
+
+ {/*-------------------- Privacy Settigs --------------------*/}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/time-picker.tsx b/src/components/time-picker.tsx
index 4402b88..ff4f0a9 100644
--- a/src/components/time-picker.tsx
+++ b/src/components/time-picker.tsx
@@ -20,6 +20,7 @@ export default function TimePicker({
setDate,
time,
setTime,
+ ...props
}: {
dateLabel?: string;
timeLabel?: string;
@@ -27,12 +28,12 @@ export default function TimePicker({
setDate?: (date: Date | undefined) => void;
time?: string;
setTime?: (time: string) => void;
-}) {
+} & React.HTMLAttributes) {
const [open, setOpen] = React.useState(false);
return (
-
-
+
+
@@ -68,7 +69,7 @@ export default function TimePicker({
-
+
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/ui/dialog.tsx b/src/components/ui/dialog.tsx
index e481c76..5f6b7c5 100644
--- a/src/components/ui/dialog.tsx
+++ b/src/components/ui/dialog.tsx
@@ -69,7 +69,7 @@ function DialogContent({
{showCloseButton && (
Close
diff --git a/src/components/wrappers/group-wrapper.tsx b/src/components/wrappers/group-wrapper.tsx
new file mode 100644
index 0000000..3006c9a
--- /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;
+ title?: string;
+ children: React.ReactNode;
+}
+
+export function GroupWrapper({
+ className,
+ title,
+ children,
+}: ScrollableSettingsWrapperProps) {
+ return (
+
+ );
+}
diff --git a/src/components/wrappers/query-provider.tsx b/src/components/wrappers/query-provider.tsx
index 4d05c6c..0120caf 100644
--- a/src/components/wrappers/query-provider.tsx
+++ b/src/components/wrappers/query-provider.tsx
@@ -3,7 +3,7 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import * as React from 'react';
-const queryClient = new QueryClient();
+export const queryClient = new QueryClient();
export function QueryProvider({ children }: { children: React.ReactNode }) {
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}
);
-};
+}
diff --git a/yarn.lock b/yarn.lock
index b241026..e96576f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -489,10 +489,10 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/js@npm:9.30.0":
- version: 9.30.0
- resolution: "@eslint/js@npm:9.30.0"
- checksum: 10c0/aec2df7f4e4e884d693dc27dbf4713c1a48afa327bfadac25ebd0e61a2797ce906f2f2a9be0d7d922acb68ccd68cc88779737811f9769eb4933d1f5e574c469e
+"@eslint/js@npm:9.30.1":
+ version: 9.30.1
+ resolution: "@eslint/js@npm:9.30.1"
+ checksum: 10c0/17fc382a0deafdb1cadac1269d9c2f2464f025bde6e4d12fc4f4775eb9886b41340d4650b72e85a53423644fdc89bf59c987a852f27379ad25feecf2c5bbc1c9
languageName: node
linkType: hard
@@ -2224,9 +2224,9 @@ __metadata:
linkType: hard
"@rushstack/eslint-patch@npm:^1.10.3":
- version: 1.11.0
- resolution: "@rushstack/eslint-patch@npm:1.11.0"
- checksum: 10c0/abea8d8cf2f4f50343f74abd6a8173c521ddd09b102021f5aa379ef373c40af5948b23db0e87eca1682e559e09d97d3f0c48ea71edad682c6bf72b840c8675b3
+ version: 1.12.0
+ resolution: "@rushstack/eslint-patch@npm:1.12.0"
+ checksum: 10c0/1e567656d92632c085a446f40767bc451caffe1131e8d6a7a3e8f3e3f4167f5f29744a84c709f2440f299442d4bc68ff773784462166800b8c09c0e08042415b
languageName: node
linkType: hard
@@ -3387,12 +3387,12 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:22.15.34":
- version: 22.15.34
- resolution: "@types/node@npm:22.15.34"
+"@types/node@npm:22.16.0":
+ version: 22.16.0
+ resolution: "@types/node@npm:22.16.0"
dependencies:
undici-types: "npm:~6.21.0"
- checksum: 10c0/fb6a6b36daaa1b484aaba3d33b4d1e7b37ea993e29f20b7a676affa76ed6ff6acd2ded4d5003469bc8dbc815b3d224533b4560896037ef6d5b5d552721ab7d57
+ checksum: 10c0/6219b521062f6c38d4d85ebd25807bd7f2bc703a5acba24e2c6716938d9d6cefd6fafd7b5156f61580eb58a0d82e8921751b778655675389631d813e5f261c03
languageName: node
linkType: hard
@@ -4786,9 +4786,9 @@ __metadata:
languageName: node
linkType: hard
-"cypress@npm:14.5.0":
- version: 14.5.0
- resolution: "cypress@npm:14.5.0"
+"cypress@npm:14.5.1":
+ version: 14.5.1
+ resolution: "cypress@npm:14.5.1"
dependencies:
"@cypress/request": "npm:^3.0.8"
"@cypress/xvfb": "npm:^1.2.4"
@@ -4836,7 +4836,7 @@ __metadata:
yauzl: "npm:^2.10.0"
bin:
cypress: bin/cypress
- checksum: 10c0/b76b05c029625357fbc34f22b632c55f9f981f86c3a568a88ea3d8982b8299e4bd4275e966b2ec767f9a989c6e9059fb03a4a8086048b4e990079b1cab19ba11
+ checksum: 10c0/23c87cafcd2fe949af1b3297cc4c9c8f8d741f5dfa8119ff54b387227dba8dc0dbcfb2d160c4df5d4f281374524753598f3501f0fdf0b1ea66c5b8047484c0d2
languageName: node
linkType: hard
@@ -5094,9 +5094,9 @@ __metadata:
linkType: hard
"dotenv@npm:^16.3.0":
- version: 16.5.0
- resolution: "dotenv@npm:16.5.0"
- checksum: 10c0/5bc94c919fbd955bf0ba44d33922a1e93d1078e64a1db5c30faeded1d996e7a83c55332cb8ea4fae5a9ca4d0be44cbceb95c5811e70f9f095298df09d1997dd9
+ version: 16.6.0
+ resolution: "dotenv@npm:16.6.0"
+ checksum: 10c0/c1029a8656e03abc53c0522baf733131ffd4e633d8ab66c3f2d470ebdd7c5d143ffab64945afc475e60c5f803f0e734a9f9247949d17cc8ff564fba5c15978b1
languageName: node
linkType: hard
@@ -5676,9 +5676,9 @@ __metadata:
languageName: node
linkType: hard
-"eslint@npm:9.30.0":
- version: 9.30.0
- resolution: "eslint@npm:9.30.0"
+"eslint@npm:9.30.1":
+ version: 9.30.1
+ resolution: "eslint@npm:9.30.1"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.2.0"
"@eslint-community/regexpp": "npm:^4.12.1"
@@ -5686,7 +5686,7 @@ __metadata:
"@eslint/config-helpers": "npm:^0.3.0"
"@eslint/core": "npm:^0.14.0"
"@eslint/eslintrc": "npm:^3.3.1"
- "@eslint/js": "npm:9.30.0"
+ "@eslint/js": "npm:9.30.1"
"@eslint/plugin-kit": "npm:^0.3.1"
"@humanfs/node": "npm:^0.16.6"
"@humanwhocodes/module-importer": "npm:^1.0.1"
@@ -5722,7 +5722,7 @@ __metadata:
optional: true
bin:
eslint: bin/eslint.js
- checksum: 10c0/ebc4b17cfd96f308ebaeb12dfab133a551eb03200c80109ecf663fbeb9af83c4eb3c143407c1b04522d23b5f5844fe9a629b00d409adfc460c1aadf5108da86a
+ checksum: 10c0/5a5867078e03ea56a1b6d1ee1548659abc38a6d5136c7ef94e21c5dbeb28e3ed50b15d2e0da25fce85600f6cf7ea7715eae650c41e8ae826c34490e9ec73d5d6
languageName: node
linkType: hard
@@ -7717,7 +7717,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.34"
+ "@types/node": "npm:22.16.0"
"@types/react": "npm:19.1.8"
"@types/react-big-calendar": "npm:1.16.2"
"@types/react-dom": "npm:19.1.6"
@@ -7727,10 +7727,10 @@ __metadata:
class-variance-authority: "npm:^0.7.1"
clsx: "npm:^2.1.1"
cmdk: "npm:^1.1.1"
- cypress: "npm:14.5.0"
+ cypress: "npm:14.5.1"
date-fns: "npm:^4.1.0"
dotenv-cli: "npm:8.0.0"
- eslint: "npm:9.30.0"
+ eslint: "npm:9.30.1"
eslint-config-next: "npm:15.3.4"
eslint-config-prettier: "npm:10.1.5"
lucide-react: "npm:^0.525.0"
@@ -8988,15 +8988,15 @@ __metadata:
linkType: hard
"react-day-picker@npm:^9.7.0":
- version: 9.7.0
- resolution: "react-day-picker@npm:9.7.0"
+ version: 9.8.0
+ resolution: "react-day-picker@npm:9.8.0"
dependencies:
"@date-fns/tz": "npm:1.2.0"
date-fns: "npm:4.1.0"
date-fns-jalali: "npm:4.1.0-0"
peerDependencies:
react: ">=16.8.0"
- checksum: 10c0/c08c45a53aebceda1c938d2e4c95eb1702dcf149715e3457739f8930dce19a3be5780e5bad12dcc9d244d50b7e0efb226c336d81c1c062f616cf422e6a3804a6
+ checksum: 10c0/910dfbc59e9fece7f5365a2a23ed497e07f227a733289e8141b858b6ce482087df6b01f2ba4f9f7e452ebc3465af0e227f192708a673396221865df07e5ab2ac
languageName: node
linkType: hard
@@ -9035,11 +9035,11 @@ __metadata:
linkType: hard
"react-hook-form@npm:^7.56.4":
- version: 7.59.0
- resolution: "react-hook-form@npm:7.59.0"
+ version: 7.60.0
+ resolution: "react-hook-form@npm:7.60.0"
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
- checksum: 10c0/6be30ce65121f4be0f491c2929142e2d9a390a8802f58fb7a41ab978ab8daa6fcd2c442258dbd1b053e6864a83c8f4b1d83de9c95f0efdf5c2120d3c21bd838e
+ checksum: 10c0/eb8518d42a074d9e115d4b414bac18ae72708b2d047a9453dcc7588b00df300b32cebf6ecb7f2c8aa534808b3dc54bde4124af95c1e432b6691f9aba07c93b11
languageName: node
linkType: hard
@@ -10214,8 +10214,8 @@ __metadata:
linkType: hard
"swagger-ui-react@npm:^5.24.1":
- version: 5.25.3
- resolution: "swagger-ui-react@npm:5.25.3"
+ version: 5.26.0
+ resolution: "swagger-ui-react@npm:5.26.0"
dependencies:
"@babel/runtime-corejs3": "npm:^7.27.1"
"@scarf/scarf": "npm:=1.4.0"
@@ -10253,7 +10253,7 @@ __metadata:
peerDependencies:
react: ">=16.8.0 <19"
react-dom: ">=16.8.0 <19"
- checksum: 10c0/9d6542d0d1bd2533e87853d4deef5507d30b35941c697d50c763428533a88cbd9c2e3abe1af5946e35aa7fa3568dc14b9da4363f09bdf6d8023e0699efceb5cf
+ checksum: 10c0/4ce665f46171d724050435db86ce046d5a7777b5601d4ae6b418245e1fc9792591d6cb54fc583c074855f9890ff5b0e986b0a5601f47b79cfc8377dee8a3e3cc
languageName: node
linkType: hard
@@ -10692,8 +10692,8 @@ __metadata:
linkType: hard
"typedoc@npm:^0.28.0":
- version: 0.28.5
- resolution: "typedoc@npm:0.28.5"
+ version: 0.28.6
+ resolution: "typedoc@npm:0.28.6"
dependencies:
"@gerrit0/mini-shiki": "npm:^3.2.2"
lunr: "npm:^2.3.9"
@@ -10704,7 +10704,7 @@ __metadata:
typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x
bin:
typedoc: bin/typedoc
- checksum: 10c0/fc8235dbe8f14da24fdb088467b01887b3f1375b27d5caf0276ae405f03aa1f523e94aea52fe8ce1a3d477ae9e3f4f69fdc28614275445a828a77db88784e6ce
+ checksum: 10c0/f83f4ceef6e7b55c4547798d069f64a284df2e93052f317ea09d7f1d345caf73e9247bd38b04702be5f5c79848873b1216c3dbaf284bf4657bdbf7b144ecc9bb
languageName: node
linkType: hard
@@ -11314,8 +11314,8 @@ __metadata:
linkType: hard
"zod@npm:^3.25.60":
- version: 3.25.67
- resolution: "zod@npm:3.25.67"
- checksum: 10c0/80a0cab3033272c4ab9312198081f0c4ea88e9673c059aa36dc32024906363729db54bdb78f3dc9d5529bd1601f74974d5a56c0a23e40c6f04a9270c9ff22336
+ version: 3.25.74
+ resolution: "zod@npm:3.25.74"
+ checksum: 10c0/59e38b046ac333b5bd1ba325a83b6798721227cbfb1e69dfc7159bd7824b904241ab923026edb714fafefec3624265ae374a70aee9a5a45b365bd31781ffa105
languageName: node
linkType: hard