feat: event creation functionality #99
30 changed files with 2310 additions and 67 deletions
10
package.json
10
package.json
|
@ -37,7 +37,8 @@
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||||
"@radix-ui/react-hover-card": "^1.1.13",
|
"@radix-ui/react-hover-card": "^1.1.13",
|
||||||
"@radix-ui/react-label": "^2.1.6",
|
"@radix-ui/react-label": "^2.1.6",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.8",
|
"@radix-ui/react-popover": "^1.1.14",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.9",
|
||||||
"@radix-ui/react-select": "^2.2.4",
|
"@radix-ui/react-select": "^2.2.4",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
@ -48,13 +49,18 @@
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.1.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.515.0",
|
"lucide-react": "^0.515.0",
|
||||||
"next": "15.3.4",
|
"next": "15.3.4",
|
||||||
"next-auth": "^5.0.0-beta.25",
|
"next-auth": "^5.0.0-beta.25",
|
||||||
|
"next-swagger-doc": "^0.4.1",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-day-picker": "^9.7.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.56.4",
|
"react-hook-form": "^7.56.4",
|
||||||
|
"sonner": "^2.0.5",
|
||||||
"swagger-ui-react": "^5.24.1",
|
"swagger-ui-react": "^5.24.1",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"zod": "^3.25.60"
|
"zod": "^3.25.60"
|
||||||
|
@ -80,7 +86,7 @@
|
||||||
"ts-node": "10.9.2",
|
"ts-node": "10.9.2",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"tw-animate-css": "1.3.4",
|
"tw-animate-css": "1.3.4",
|
||||||
"typescript": "5.8.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.9.2"
|
"packageManager": "yarn@4.9.2"
|
||||||
}
|
}
|
||||||
|
|
238
src/app/(main)/events/[eventID]/page.tsx
Normal file
238
src/app/(main)/events/[eventID]/page.tsx
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import Logo from '@/components/misc/logo';
|
||||||
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import {
|
||||||
|
useDeleteApiEventEventID,
|
||||||
|
useGetApiEventEventID,
|
||||||
|
} from '@/generated/api/event/event';
|
||||||
|
import { useGetApiUserMe } from '@/generated/api/user/user';
|
||||||
|
import { RedirectButton } from '@/components/buttons/redirect-button';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import ParticipantListEntry from '@/components/custom-ui/participant-list-entry';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { ToastInner } from '@/components/misc/toast-inner';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
|
||||||
|
export default function ShowEvent() {
|
||||||
|
const session = useSession();
|
||||||
|
const router = useRouter();
|
||||||
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const { eventID: eventID } = useParams<{ eventID: string }>();
|
||||||
|
|
||||||
|
// Fetch event data
|
||||||
|
const { data: eventData, isLoading, error } = useGetApiEventEventID(eventID);
|
||||||
|
const { data: userData, isLoading: userLoading } = useGetApiUserMe();
|
||||||
|
const deleteEvent = useDeleteApiEventEventID();
|
||||||
|
|
||||||
|
if (isLoading || userLoading) {
|
||||||
|
return (
|
||||||
|
<div className='flex justify-center items-center h-screen'>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (error || !eventData?.data?.event) {
|
||||||
|
return (
|
||||||
|
<div className='flex justify-center items-center h-screen'>
|
||||||
|
Error loading event.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = eventData.data.event;
|
||||||
|
const organiserName = userData?.data.user?.name || 'Unknown User';
|
||||||
|
|
||||||
|
// Format dates & times for display
|
||||||
|
const formatDate = (isoString?: string) => {
|
||||||
|
if (!isoString) return '-';
|
||||||
|
return new Date(isoString).toLocaleDateString();
|
||||||
|
};
|
||||||
|
const formatTime = (isoString?: string) => {
|
||||||
|
if (!isoString) return '-';
|
||||||
|
return new Date(isoString).toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col items-center justify-center h-screen'>
|
||||||
|
<Card className='w-[80%] max-w-screen p-0 gap-0 max-xl:w-[95%] max-h-[90vh] overflow-auto'>
|
||||||
|
<CardHeader className='p-0 m-0 gap-0' />
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<div className='flex flex-col gap-5 w-full'>
|
||||||
|
<div className='grid grid-row-start:auto gap-4 sm:gap-8'>
|
||||||
|
<div className='h-full mt-0 ml-2 mb-16 flex items-center justify-between max-sm:grid max-sm:grid-row-start:auto max-sm:mb-6 max-sm:mt-10 max-sm:ml-0'>
|
||||||
|
<div className='w-[100px] max-sm:w-full max-sm:flex max-sm:justify-center'>
|
||||||
|
<Logo colorType='monochrome' logoType='submark' width={50} />
|
||||||
|
</div>
|
||||||
|
<div className='items-center ml-auto mr-auto max-sm:mb-6 max-sm:w-full max-sm:flex max-sm:justify-center'>
|
||||||
|
<h1 className='text-center'>
|
||||||
|
{event.title || 'Untitled Event'}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div className='w-0 sm:w-[100px]'></div>
|
||||||
|
</div>
|
||||||
|
<div className='grid grid-cols-4 gap-4 h-full w-full max-lg:grid-cols-2 max-sm:grid-cols-1'>
|
||||||
|
<div>
|
||||||
|
<Label className='text-[var(--color-neutral-300)] mb-2'>
|
||||||
|
start Time
|
||||||
|
</Label>
|
||||||
|
<Label size='large'>
|
||||||
|
{event.start_time
|
||||||
|
? `${formatDate(event.start_time)} ${formatTime(event.start_time)}`
|
||||||
|
: '-'}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className='text-[var(--color-neutral-300)] mb-2'>
|
||||||
|
end Time
|
||||||
|
</Label>
|
||||||
|
<Label size='large'>
|
||||||
|
{event.end_time
|
||||||
|
? `${formatDate(event.end_time)} ${formatTime(event.end_time)}`
|
||||||
|
: '-'}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className='w-54'>
|
||||||
|
<Label className='text-[var(--color-neutral-300)] mb-2'>
|
||||||
|
Location
|
||||||
|
</Label>
|
||||||
|
<Label size='large'>{event.location || '-'}</Label>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-4'>
|
||||||
|
<div className='flex flex-row gap-2'>
|
||||||
|
<Label className='w-[70px] text-[var(--color-neutral-300)]'>
|
||||||
|
created:
|
||||||
|
</Label>
|
||||||
|
<Label>
|
||||||
|
{event.created_at ? formatDate(event.created_at) : '-'}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row gap-2'>
|
||||||
|
<Label className='w-[70px] text-[var(--color-neutral-300)]'>
|
||||||
|
updated:
|
||||||
|
</Label>
|
||||||
|
<Label>
|
||||||
|
{event.updated_at ? formatDate(event.updated_at) : '-'}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='h-full w-full grid grid-cols-2 gap-4 max-sm:grid-cols-1'>
|
||||||
|
<div className='h-full w-full grid grid-flow-row gap-4 sm:gap-8'>
|
||||||
|
<div className='h-full w-full'>
|
||||||
|
<div className='flex flex-row gap-2'>
|
||||||
|
<Label className='text-[var(--color-neutral-300)]'>
|
||||||
|
Organiser:
|
||||||
|
</Label>
|
||||||
|
<Label size='large'>{organiserName}</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='h-full w-full'>
|
||||||
|
<Label className='text-[var(--color-neutral-300)] mb-2'>
|
||||||
|
Description
|
||||||
|
</Label>
|
||||||
|
<Label size='large'>{event.description || '-'}</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='h-full w-full mt-2'>
|
||||||
|
<Label className='text-[var(--color-neutral-300)] mb-2'>
|
||||||
|
Participants
|
||||||
|
</Label>{' '}
|
||||||
|
<div className='grid grid-cols-1 mt-3 sm:max-h-60 sm:grid-cols-2 sm:overflow-y-auto sm:mb-0'>
|
||||||
|
{event.participants?.map((user) => (
|
||||||
|
<ParticipantListEntry key={user.user.id} {...user} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-2 justify-end mt-4 mb-6'>
|
||||||
|
<div className='w-[20%] grid max-sm:w-full'>
|
||||||
|
{session.data?.user?.id === event.organizer.id ? (
|
||||||
|
<Dialog
|
||||||
|
open={deleteDialogOpen}
|
||||||
|
onOpenChange={setDeleteDialogOpen}
|
||||||
|
>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant='destructive' className='w-full'>
|
||||||
|
delete
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Delete Event</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Are you sure you want to delete the event “
|
||||||
|
{event.title}”? This action cannot be undone.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant='secondary'
|
||||||
|
onClick={() => setDeleteDialogOpen(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='muted'
|
||||||
|
onClick={() => {
|
||||||
|
deleteEvent.mutate(
|
||||||
|
{ eventID: event.id },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
router.push('/home');
|
||||||
|
toast.custom((t) => (
|
||||||
|
<ToastInner
|
||||||
|
toastId={t}
|
||||||
|
title='Event deleted'
|
||||||
|
description={event?.title}
|
||||||
|
variant='success'
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
setDeleteDialogOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className='w-[20%] grid max-sm:w-full'>
|
||||||
|
{session.data?.user?.id === event.organizer.id ? (
|
||||||
|
<RedirectButton
|
||||||
|
redirectUrl={`/events/edit/${eventID}`}
|
||||||
|
buttonText='edit'
|
||||||
|
className='w-full'
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
24
src/app/(main)/events/edit/[eventID]/page.tsx
Normal file
24
src/app/(main)/events/edit/[eventID]/page.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
|
import EventForm from '@/components/forms/event-form';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
|
export default async function Page({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ eventID: string }>;
|
||||||
|
}) {
|
||||||
|
const eventID = (await params).eventID;
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col items-center justify-center h-screen'>
|
||||||
|
<Card className='w-[80%] max-w-screen p-0 gap-0 max-xl:w-[95%] max-h-[90vh] overflow-auto'>
|
||||||
|
<CardHeader className='p-0 m-0 gap-0' />
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<Suspense>
|
||||||
|
<EventForm type='edit' eventId={eventID} />
|
||||||
|
</Suspense>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
21
src/app/(main)/events/new/page.tsx
Normal file
21
src/app/(main)/events/new/page.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { ThemePicker } from '@/components/misc/theme-picker';
|
||||||
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
|
import EventForm from '@/components/forms/event-form';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
|
export default function NewEvent() {
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col items-center justify-center h-screen'>
|
||||||
|
<div className='absolute top-4 right-4'>{<ThemePicker />}</div>
|
||||||
|
<Card className='w-[80%] max-w-screen p-0 gap-0 max-xl:w-[95%] max-h-[90vh] overflow-auto'>
|
||||||
|
<CardHeader className='p-0 m-0 gap-0' />
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<Suspense>
|
||||||
|
<EventForm type='create' />
|
||||||
|
</Suspense>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
54
src/app/(main)/events/page.tsx
Normal file
54
src/app/(main)/events/page.tsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { RedirectButton } from '@/components/buttons/redirect-button';
|
||||||
|
import EventListEntry from '@/components/custom-ui/event-list-entry';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { useGetApiEvent } from '@/generated/api/event/event';
|
||||||
|
|
||||||
|
export default function Events() {
|
||||||
|
const { data: eventsData, isLoading, error } = useGetApiEvent();
|
||||||
|
|
||||||
|
if (isLoading) return <div className='text-center mt-10'>Loading...</div>;
|
||||||
|
if (error)
|
||||||
|
return (
|
||||||
|
<div className='text-center mt-10 text-red-500'>Error loading events</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const events = eventsData?.data?.events || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative h-screen flex flex-col items-center'>
|
||||||
|
{/* Heading */}
|
||||||
|
<h1 className='text-3xl font-bold mt-8 mb-4 text-center z-10'>
|
||||||
|
My Events
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Scrollable event list */}
|
||||||
|
<div className='w-full flex justify-center overflow-hidden'>
|
||||||
|
<div className='grid gap-8 w-[90%] sm:w-[80%] lg:w-[60%] xl:w-[50%] p-6 overflow-y-auto'>
|
||||||
|
{events.length > 0 ? (
|
||||||
|
events.map((event) => (
|
||||||
|
<EventListEntry
|
||||||
|
key={event.id}
|
||||||
|
{...event}
|
||||||
|
created_at={new Date(event.created_at)}
|
||||||
|
updated_at={new Date(event.updated_at)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className='flex flex-1 flex-col items-center justify-center min-h-[300px]'>
|
||||||
|
<Label size='large' className='justify-center text-center'>
|
||||||
|
You don't have any events right now
|
||||||
|
</Label>
|
||||||
|
<RedirectButton
|
||||||
|
redirectUrl='/events/new'
|
||||||
|
buttonText='create Event'
|
||||||
|
className='mt-4'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ export default function Home() {
|
||||||
</h1>
|
</h1>
|
||||||
<RedirectButton redirectUrl='/logout' buttonText='Logout' />
|
<RedirectButton redirectUrl='/logout' buttonText='Logout' />
|
||||||
<RedirectButton redirectUrl='/settings' buttonText='Settings' />
|
<RedirectButton redirectUrl='/settings' buttonText='Settings' />
|
||||||
|
<RedirectButton redirectUrl='/events/new' buttonText='New Event' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
--font-heading: 'Comfortaa', sans-serif;
|
--font-heading: 'Comfortaa', sans-serif;
|
||||||
--font-label: 'Varela Round', sans-serif;
|
--font-label: 'Varela Round', sans-serif;
|
||||||
--font-button: 'Varela Round', sans-serif;
|
--font-button: 'Varela Round', sans-serif;
|
||||||
|
--font-sans: var(--font-label);
|
||||||
|
|
||||||
--transparent: transparent;
|
--transparent: transparent;
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@
|
||||||
|
|
||||||
--background: var(--neutral-800);
|
--background: var(--neutral-800);
|
||||||
--background-reversed: var(--neutral-000);
|
--background-reversed: var(--neutral-000);
|
||||||
--base: var(--neutral-800);
|
--basecl: var(--neutral-800);
|
||||||
dominik marked this conversation as resolved
Outdated
|
|||||||
--text: var(--neutral-000);
|
--text: var(--neutral-000);
|
||||||
--text-alt: var(--neutral-900);
|
--text-alt: var(--neutral-900);
|
||||||
--text-input: var(--text);
|
--text-input: var(--text);
|
||||||
|
@ -49,11 +50,23 @@
|
||||||
--active-secondary: oklch(0.4254 0.133 272.15);
|
--active-secondary: oklch(0.4254 0.133 272.15);
|
||||||
--disabled-secondary: oklch(0.4937 0.1697 271.26 / 0.5);
|
--disabled-secondary: oklch(0.4937 0.1697 271.26 / 0.5);
|
||||||
|
|
||||||
|
--destructive: oklch(60.699% 0.20755 25.945);
|
||||||
|
--hover-destructive: oklch(60.699% 0.20755 25.945 / 0.8);
|
||||||
|
--active-destructive: oklch(50.329% 0.17084 25.842);
|
||||||
|
--disabled-destructive: oklch(60.699% 0.20755 25.945 / 0.4);
|
||||||
|
|
||||||
--muted: var(--color-neutral-700);
|
--muted: var(--color-neutral-700);
|
||||||
--hover-muted: var(--color-neutral-600);
|
--hover-muted: var(--color-neutral-600);
|
||||||
--active-muted: var(--color-neutral-400);
|
--active-muted: var(--color-neutral-400);
|
||||||
--disabled-muted: var(--color-neutral-400);
|
--disabled-muted: var(--color-neutral-400);
|
||||||
|
|
||||||
|
--toaster-default-bg: var(--color-neutral-150);
|
||||||
|
--toaster-success-bg: oklch(54.147% 0.09184 144.208);
|
||||||
|
--toaster-error-bg: oklch(52.841% 0.10236 27.274);
|
||||||
|
--toaster-info-bg: oklch(44.298% 0.05515 259.369);
|
||||||
|
--toaster-warning-bg: oklch(61.891% 0.07539 102.943);
|
||||||
|
--toaster-notification-bg: var(--color-neutral-150);
|
||||||
|
|
||||||
--card: var(--neutral-800);
|
--card: var(--neutral-800);
|
||||||
|
|
||||||
--sidebar-width-icon: 32px;
|
--sidebar-width-icon: 32px;
|
||||||
|
@ -80,8 +93,6 @@
|
||||||
|
|
||||||
--accent-foreground: oklch(0.21 0.034 264.665);
|
--accent-foreground: oklch(0.21 0.034 264.665);
|
||||||
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
|
|
||||||
--border: oklch(0.928 0.006 264.531);
|
--border: oklch(0.928 0.006 264.531);
|
||||||
|
|
||||||
--input: oklch(0.928 0.006 264.531);
|
--input: oklch(0.928 0.006 264.531);
|
||||||
|
@ -115,6 +126,62 @@
|
||||||
--sidebar-ring: oklch(0.707 0.022 261.325);
|
--sidebar-ring: oklch(0.707 0.022 261.325);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 40px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 36px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 32px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 28px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 26px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: 20px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: var(--font-label);
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Comfortaa';
|
font-family: 'Comfortaa';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@ -153,7 +220,7 @@
|
||||||
|
|
||||||
--color-background: var(--neutral-750);
|
--color-background: var(--neutral-750);
|
||||||
--color-background-reversed: var(--background-reversed);
|
--color-background-reversed: var(--background-reversed);
|
||||||
--color-base: var(--neutral-800);
|
--color-basecl: var(--neutral-800);
|
||||||
--color-text: var(--text);
|
--color-text: var(--text);
|
||||||
--color-text-alt: var(--text-alt);
|
--color-text-alt: var(--text-alt);
|
||||||
--color-text-input: var(--text-input);
|
--color-text-input: var(--text-input);
|
||||||
|
@ -175,11 +242,23 @@
|
||||||
--color-active-secondary: var(--active-secondary);
|
--color-active-secondary: var(--active-secondary);
|
||||||
--color-disabled-secondary: var(--disabled-secondary);
|
--color-disabled-secondary: var(--disabled-secondary);
|
||||||
|
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-hover-destructive: var(--hover-destructive);
|
||||||
|
--color-active-destructive: var(--active-destructive);
|
||||||
|
--color-disabled-destructive: var(--disabled-destructive);
|
||||||
|
|
||||||
--color-muted: var(--muted);
|
--color-muted: var(--muted);
|
||||||
--color-hover-muted: var(--hover-muted);
|
--color-hover-muted: var(--hover-muted);
|
||||||
--color-active-muted: var(--active-muted);
|
--color-active-muted: var(--active-muted);
|
||||||
--color-disabled-muted: var(--disabled-muted);
|
--color-disabled-muted: var(--disabled-muted);
|
||||||
|
|
||||||
|
--color-toaster-default-bg: var(--toaster-default-bg);
|
||||||
|
--color-toaster-success-bg: var(--toaster-success-bg);
|
||||||
|
--color-toaster-error-bg: var(--toaster-error-bg);
|
||||||
|
--color-toaster-info-bg: var(--toaster-info-bg);
|
||||||
|
--color-toaster-warning-bg: var(--toaster-warning-bg);
|
||||||
|
--color-toaster-notification-bg: var(--toaster-notification-bg);
|
||||||
|
|
||||||
/* Custom values */
|
/* Custom values */
|
||||||
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
@ -220,8 +299,6 @@
|
||||||
|
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
|
||||||
--color-destructive: var(--destructive);
|
|
||||||
|
|
||||||
--color-border: var(--border);
|
--color-border: var(--border);
|
||||||
|
|
||||||
--color-input: var(--input);
|
--color-input: var(--input);
|
||||||
|
@ -277,7 +354,7 @@
|
||||||
|
|
||||||
--background: var(--neutral-750);
|
--background: var(--neutral-750);
|
||||||
--background-reversed: var(--neutral-000);
|
--background-reversed: var(--neutral-000);
|
||||||
--base: var(--neutral-750);
|
--basecl: var(--neutral-750);
|
||||||
--text: var(--neutral-000);
|
--text: var(--neutral-000);
|
||||||
--text-alt: var(--neutral-900);
|
--text-alt: var(--neutral-900);
|
||||||
--text-input: var(--text);
|
--text-input: var(--text);
|
||||||
|
@ -297,11 +374,23 @@
|
||||||
--active-secondary: oklch(0.4471 0.15 271.61);
|
--active-secondary: oklch(0.4471 0.15 271.61);
|
||||||
--disabled-secondary: oklch(0.6065 0.213 271.11 / 0.4);
|
--disabled-secondary: oklch(0.6065 0.213 271.11 / 0.4);
|
||||||
|
|
||||||
|
--destructive: oklch(0.58 0.2149 27.13);
|
||||||
|
--hover-destructive: oklch(0.58 0.2149 27.13 / 0.8);
|
||||||
|
--active-destructive: oklch(45.872% 0.16648 26.855);
|
||||||
|
--disabled-destructive: oklch(0.58 0.2149 27.13 / 0.4);
|
||||||
|
|
||||||
--muted: var(--color-neutral-650);
|
--muted: var(--color-neutral-650);
|
||||||
--hover-muted: var(--color-neutral-500);
|
--hover-muted: var(--color-neutral-500);
|
||||||
--active-muted: var(--color-neutral-400);
|
--active-muted: var(--color-neutral-400);
|
||||||
--disabled-muted: var(--color-neutral-400);
|
--disabled-muted: var(--color-neutral-400);
|
||||||
|
|
||||||
|
--toaster-default-bg: var(--color-neutral-150);
|
||||||
|
--toaster-success-bg: var(--color-green-200);
|
||||||
|
--toaster-error-bg: var(--color-red-200);
|
||||||
|
--toaster-info-bg: var(--color-blue-200);
|
||||||
|
--toaster-warning-bg: var(--color-yellow-200);
|
||||||
|
--toaster-notification-bg: var(--color-neutral-150);
|
||||||
|
|
||||||
--card: var(--neutral-750);
|
--card: var(--neutral-750);
|
||||||
|
|
||||||
/* ------------------- */
|
/* ------------------- */
|
||||||
|
@ -326,8 +415,6 @@
|
||||||
|
|
||||||
--accent-foreground: oklch(0.985 0.002 247.839);
|
--accent-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
--input: oklch(1 0 0 / 15%);
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { ThemeProvider } from '@/components/wrappers/theme-provider';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
import { QueryProvider } from '@/components/wrappers/query-provider';
|
import { QueryProvider } from '@/components/wrappers/query-provider';
|
||||||
|
import { Toaster } from '@/components/ui/sonner';
|
||||||
|
import { SessionProvider } from 'next-auth/react';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'MeetUp',
|
title: 'MeetUp',
|
||||||
|
@ -50,6 +52,7 @@ export default function RootLayout({
|
||||||
<link rel='manifest' href='/site.webmanifest' />
|
<link rel='manifest' href='/site.webmanifest' />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<SessionProvider>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute='class'
|
attribute='class'
|
||||||
defaultTheme='system'
|
defaultTheme='system'
|
||||||
|
@ -58,6 +61,8 @@ export default function RootLayout({
|
||||||
>
|
>
|
||||||
<QueryProvider>{children}</QueryProvider>
|
<QueryProvider>{children}</QueryProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
</SessionProvider>
|
||||||
|
<Toaster />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
5
src/assets/usericon/default/default-user-icon_dark.svg
Normal file
5
src/assets/usericon/default/default-user-icon_dark.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 11C0 4.92487 4.92487 0 11 0H29C35.0751 0 40 4.92487 40 11V29C40 35.0751 35.0751 40 29 40H11C4.92487 40 0 35.0751 0 29V11Z" fill="#5770FF"/>
|
||||||
|
<path d="M31.6663 35V31.6667C31.6663 29.8986 30.964 28.2029 29.7137 26.9526C28.4635 25.7024 26.7678 25 24.9997 25H14.9997C13.2316 25 11.5359 25.7024 10.2856 26.9526C9.03539 28.2029 8.33301 29.8986 8.33301 31.6667V35" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19.9997 18.3333C23.6816 18.3333 26.6663 15.3486 26.6663 11.6667C26.6663 7.98477 23.6816 5 19.9997 5C16.3178 5 13.333 7.98477 13.333 11.6667C13.333 15.3486 16.3178 18.3333 19.9997 18.3333Z" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 833 B |
5
src/assets/usericon/default/default-user-icon_light.svg
Normal file
5
src/assets/usericon/default/default-user-icon_light.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 11C0 4.92487 4.92487 0 11 0H29C35.0751 0 40 4.92487 40 11V29C40 35.0751 35.0751 40 29 40H11C4.92487 40 0 35.0751 0 29V11Z" fill="#4154C0"/>
|
||||||
|
<path d="M31.6663 35V31.6667C31.6663 29.8986 30.964 28.2029 29.7137 26.9526C28.4635 25.7024 26.7678 25 24.9997 25H14.9997C13.2316 25 11.5359 25.7024 10.2856 26.9526C9.03539 28.2029 8.33301 29.8986 8.33301 31.6667V35" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19.9997 18.3333C23.6816 18.3333 26.6663 15.3486 26.6663 11.6667C26.6663 7.98477 23.6816 5 19.9997 5C16.3178 5 13.333 7.98477 13.333 11.6667C13.333 15.3486 16.3178 18.3333 19.9997 18.3333Z" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 833 B |
2
src/assets/usericon/default/defaultusericon-export.tsx
Normal file
2
src/assets/usericon/default/defaultusericon-export.tsx
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as user_default_dark } from '@/assets/usericon/default/default-user-icon_dark.svg';
|
||||||
|
export { default as user_default_light } from '@/assets/usericon/default/default-user-icon_light.svg';
|
|
@ -1,16 +1,18 @@
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export function RedirectButton({
|
export function RedirectButton({
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
buttonText,
|
buttonText,
|
||||||
|
className,
|
||||||
}: {
|
}: {
|
||||||
redirectUrl: string;
|
redirectUrl: string;
|
||||||
buttonText: string;
|
buttonText: string;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Link href={redirectUrl}>
|
<Link href={redirectUrl}>
|
||||||
<Button>{buttonText}</Button>
|
<Button className={className}>{buttonText}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
68
src/components/custom-ui/event-list-entry.tsx
Normal file
68
src/components/custom-ui/event-list-entry.tsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { Card } from '@/components/ui/card';
|
||||||
|
import Logo from '@/components/misc/logo';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import zod from 'zod/v4';
|
||||||
|
import { EventSchema } from '@/app/api/event/validation';
|
||||||
micha.bok marked this conversation as resolved
Outdated
dominik
commented
Könntest dir hierfür auch einfach den rückgabewert von der API nehmen. Dann passt das auf jeden fall immer.
und dann mit
nutzen. Könntest dir hierfür auch einfach den rückgabewert von der API nehmen. Dann passt das auf jeden fall immer.
```typescript
type EventListEntryProps = zod.output<typeof EventSchema>;
```
und dann mit
```typescript
<EventListEntry
key={event.id}
{...event}
/>
```
nutzen.
|
|||||||
|
|
||||||
|
type EventListEntryProps = zod.output<typeof EventSchema>;
|
||||||
|
|
||||||
|
export default function EventListEntry({
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
location,
|
||||||
|
}: EventListEntryProps) {
|
||||||
|
const formatDate = (isoString?: string) => {
|
||||||
|
if (!isoString) return '-';
|
||||||
|
return new Date(isoString).toLocaleDateString();
|
||||||
|
};
|
||||||
|
const formatTime = (isoString?: string) => {
|
||||||
|
if (!isoString) return '-';
|
||||||
|
return new Date(isoString).toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Link href={`/events/${id}`} className='block'>
|
||||||
|
<Card className='w-full'>
|
||||||
|
<div className='grid grid-cols-1 gap-2 mx-auto md:mx-4 md:grid-cols-[80px_1fr_250px]'>
|
||||||
|
<div className='w-full items-center justify-center grid'>
|
||||||
|
<Logo colorType='monochrome' logoType='submark' width={50} />
|
||||||
|
</div>
|
||||||
|
<div className='w-full items-center justify-center grid my-3 md:my-0'>
|
||||||
|
<h2 className='text-center'>{title}</h2>
|
||||||
|
</div>
|
||||||
|
<div className='grid gap-4'>
|
||||||
|
<div className='grid grid-cols-[80px_auto] gap-2'>
|
||||||
|
<Label className='text-[var(--color-neutral-300)] justify-end'>
|
||||||
|
start
|
||||||
|
</Label>
|
||||||
|
<Label>
|
||||||
|
{formatDate(start_time)} {formatTime(start_time)}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className='grid grid-cols-[80px_auto] gap-2'>
|
||||||
|
<Label className='text-[var(--color-neutral-300)] justify-end'>
|
||||||
|
end
|
||||||
|
</Label>
|
||||||
|
<Label>
|
||||||
|
{formatDate(end_time)} {formatTime(end_time)}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
{location && (
|
||||||
|
<div className='grid grid-cols-[80px_auto] gap-2'>
|
||||||
|
<Label className='text-[var(--color-neutral-300)] justify-end'>
|
||||||
|
location
|
||||||
|
</Label>
|
||||||
|
<Label>{location}</Label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input, Textarea } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
|
|
||||||
export default function LabeledInput({
|
export default function LabeledInput({
|
||||||
|
@ -7,6 +7,7 @@ export default function LabeledInput({
|
||||||
placeholder,
|
placeholder,
|
||||||
value,
|
value,
|
||||||
name,
|
name,
|
||||||
|
variantSize = 'default',
|
||||||
autocomplete,
|
autocomplete,
|
||||||
error,
|
error,
|
||||||
...rest
|
...rest
|
||||||
|
@ -16,22 +17,37 @@ export default function LabeledInput({
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
variantSize?: 'default' | 'big' | 'textarea';
|
||||||
autocomplete?: string;
|
autocomplete?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
} & React.InputHTMLAttributes<HTMLInputElement>) {
|
} & React.InputHTMLAttributes<HTMLInputElement>) {
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-1 gap-1'>
|
<div className='grid grid-cols-1 gap-1'>
|
||||||
<Label htmlFor={name}>{label}</Label>
|
<Label htmlFor={name}>{label}</Label>
|
||||||
|
{variantSize === 'textarea' ? (
|
||||||
|
<Textarea
|
||||||
|
placeholder={placeholder}
|
||||||
|
defaultValue={value}
|
||||||
|
id={name}
|
||||||
|
name={name}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<Input
|
<Input
|
||||||
type={type}
|
type={type}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
id={name}
|
id={name}
|
||||||
name={name}
|
name={name}
|
||||||
|
className={
|
||||||
|
variantSize === 'big'
|
||||||
|
? 'h-12 file:h-10 text-lg gplaceholder:text-lg sm:text-2xl sm:placeholder:text-2xl'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
autoComplete={autocomplete}
|
autoComplete={autocomplete}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
{error && <p className='text-red-500 text-sm mt-1'>{error}</p>}
|
{error && <p className='text-red-500 text-sm mt-1'>{error}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
26
src/components/custom-ui/participant-list-entry.tsx
Normal file
26
src/components/custom-ui/participant-list-entry.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { user_default_dark } from '@/assets/usericon/default/defaultusericon-export';
|
||||||
|
import { user_default_light } from '@/assets/usericon/default/defaultusericon-export';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
import zod from 'zod/v4';
|
||||||
|
import { ParticipantSchema } from '@/app/api/event/[eventID]/participant/validation';
|
||||||
micha.bok marked this conversation as resolved
Outdated
dominik
commented
Hier das gleiche mit
und
bzw.
Hier das gleiche mit
```typescript
import { zod } from 'zod/v4';
import { ParticipantSchema } from '@/app/api/event/[eventID]/participant/validation';
type ParticipantListEntryProps = zod.output<typeof ParticipantSchema>;
```
und
```typescript
<ParticipantListEntry
{...user}
/>
```
bzw.
```typescript
<ParticipantListEntry
user={user}
status={"PENDING"}
/>
```
|
|||||||
|
|
||||||
|
type ParticipantListEntryProps = zod.output<typeof ParticipantSchema>;
|
||||||
|
|
||||||
|
export default function ParticipantListEntry({
|
||||||
|
user,
|
||||||
|
}: ParticipantListEntryProps) {
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
const defaultImage =
|
||||||
|
resolvedTheme === 'dark' ? user_default_dark : user_default_light;
|
||||||
|
|
||||||
|
const finalImageSrc = user.image ?? defaultImage;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex items-center gap-2 py-1 ml-5'>
|
||||||
|
<Image src={finalImageSrc} alt='Avatar' width={30} height={30} />
|
||||||
|
<span>{user.name}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
343
src/components/forms/event-form.tsx
Normal file
343
src/components/forms/event-form.tsx
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
'use client';
|
||||||
|
import React from 'react';
|
||||||
|
import LabeledInput from '@/components/custom-ui/labeled-input';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import Logo from '@/components/misc/logo';
|
||||||
|
import TimePicker from '@/components/time-picker';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { useGetApiUserMe } from '@/generated/api/user/user';
|
||||||
|
import {
|
||||||
|
usePostApiEvent,
|
||||||
|
useGetApiEventEventID,
|
||||||
|
usePatchApiEventEventID,
|
||||||
|
} from '@/generated/api/event/event';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { ToastInner } from '@/components/misc/toast-inner';
|
||||||
|
import { UserSearchInput } from '@/components/misc/user-search';
|
||||||
|
import ParticipantListEntry from '../custom-ui/participant-list-entry';
|
||||||
|
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
|
import zod from 'zod/v4';
|
||||||
micha.bok marked this conversation as resolved
Outdated
dominik
commented
Hier am besten auch den API type direkt nutzen. Hier am besten auch den API type direkt nutzen.
|
|||||||
|
import { PublicUserSchema } from '@/app/api/user/validation';
|
||||||
|
|
||||||
|
type User = zod.output<typeof PublicUserSchema>;
|
||||||
|
|
||||||
|
interface EventFormProps {
|
||||||
|
type: 'create' | 'edit';
|
||||||
|
eventId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EventForm: React.FC<EventFormProps> = (props) => {
|
||||||
|
// Runtime validation
|
||||||
|
if (props.type === 'edit' && !props.eventId) {
|
||||||
|
throw new Error(
|
||||||
|
'Error [event-form]: eventId must be provided when type is "edit".',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const startFromUrl = searchParams.get('start');
|
||||||
|
const endFromUrl = searchParams.get('end');
|
||||||
|
|
||||||
|
const { mutate: createEvent, status, isSuccess, error } = usePostApiEvent();
|
||||||
|
const { data, isLoading, error: fetchError } = useGetApiUserMe();
|
||||||
|
const { data: eventData } = useGetApiEventEventID(props.eventId!, {
|
||||||
|
query: { enabled: props.type === 'edit' },
|
||||||
|
});
|
||||||
|
const patchEvent = usePatchApiEventEventID();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Extract event fields for form defaults
|
||||||
|
const event = eventData?.data?.event;
|
||||||
|
|
||||||
|
// State for date and time fields
|
||||||
|
const [startDate, setStartDate] = React.useState<Date | undefined>(undefined);
|
||||||
|
const [startTime, setStartTime] = React.useState('');
|
||||||
|
const [endDate, setEndDate] = React.useState<Date | undefined>(undefined);
|
||||||
|
const [endTime, setEndTime] = React.useState('');
|
||||||
|
|
||||||
|
// State for participants
|
||||||
|
const [selectedParticipants, setSelectedParticipants] = React.useState<
|
||||||
|
User[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
// State for form fields
|
||||||
|
const [title, setTitle] = React.useState('');
|
||||||
|
const [location, setLocation] = React.useState('');
|
||||||
|
const [description, setDescription] = React.useState('');
|
||||||
|
|
||||||
|
// Update state when event data loads
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (props.type === 'edit' && event) {
|
||||||
|
setTitle(event.title || '');
|
||||||
|
// Parse start_time and end_time
|
||||||
|
if (event.start_time) {
|
||||||
|
const start = new Date(event.start_time);
|
||||||
|
setStartDate(start);
|
||||||
|
setStartTime(start.toTimeString().slice(0, 5)); // "HH:mm"
|
||||||
|
}
|
||||||
|
if (event.end_time) {
|
||||||
|
const end = new Date(event.end_time);
|
||||||
|
setEndDate(end);
|
||||||
|
setEndTime(end.toTimeString().slice(0, 5)); // "HH:mm"
|
||||||
|
}
|
||||||
|
setLocation(event.location || '');
|
||||||
|
setDescription(event.description || '');
|
||||||
|
setSelectedParticipants(event.participants?.map((u) => u.user) || []);
|
||||||
|
} else if (props.type === 'create' && startFromUrl && endFromUrl) {
|
||||||
|
// If creating a new event with URL params, set title and dates
|
||||||
|
setTitle('');
|
||||||
|
const start = new Date(startFromUrl);
|
||||||
|
setStartDate(start);
|
||||||
|
setStartTime(start.toTimeString().slice(0, 5)); // "HH:mm"
|
||||||
|
const end = new Date(endFromUrl);
|
||||||
|
setEndDate(end);
|
||||||
|
setEndTime(end.toTimeString().slice(0, 5)); // "HH:mm"
|
||||||
|
}
|
||||||
|
}, [event, props.type, startFromUrl, endFromUrl]);
|
||||||
|
|
||||||
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.currentTarget);
|
||||||
|
|
||||||
|
function combine(date?: Date, time?: string) {
|
||||||
|
if (!date || !time) return undefined;
|
||||||
|
const [hours, minutes] = time.split(':');
|
||||||
|
const d = new Date(date);
|
||||||
|
d.setHours(Number(hours), Number(minutes), 0, 0);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = combine(startDate, startTime);
|
||||||
|
const end = combine(endDate, endTime);
|
||||||
|
|
||||||
|
//validate form data
|
||||||
|
if (!formData.get('eventName')) {
|
||||||
|
alert('Event name is required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!start || !end) {
|
||||||
|
alert('Please provide both start and end date/time.');
|
||||||
|
return;
|
||||||
|
} else if (start >= end) {
|
||||||
|
alert('End time must be after start time.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
title: formData.get('eventName') as string,
|
||||||
|
description: formData.get('eventDescription') as string,
|
||||||
|
start_time: start.toISOString(),
|
||||||
|
end_time: end.toISOString(),
|
||||||
|
location: formData.get('eventLocation') as string,
|
||||||
|
created_at: formData.get('createdAt') as string,
|
||||||
|
updated_at: formData.get('updatedAt') as string,
|
||||||
|
organiser: formData.get('organiser') as string,
|
||||||
|
participants: selectedParticipants.map((u) => u.id),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.type === 'edit' && props.eventId) {
|
||||||
|
await patchEvent.mutateAsync({
|
||||||
|
eventID: props.eventId,
|
||||||
|
data: {
|
||||||
|
title: data.title,
|
||||||
|
description: data.description,
|
||||||
|
start_time: data.start_time,
|
||||||
|
end_time: data.end_time,
|
||||||
|
location: data.location,
|
||||||
|
participants: data.participants,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log('Updating event');
|
||||||
|
} else {
|
||||||
|
console.log('Creating event');
|
||||||
|
createEvent({ data });
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.custom((t) => (
|
||||||
|
<ToastInner
|
||||||
|
toastId={t}
|
||||||
|
title='Event saved'
|
||||||
|
description={event?.title}
|
||||||
|
onAction={() => router.push(`/events/${event?.id}`)}
|
||||||
|
variant='success'
|
||||||
|
buttonText='show'
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate values for organiser, created, and updated
|
||||||
|
const organiserValue = isLoading
|
||||||
|
? 'Loading...'
|
||||||
|
: data?.data.user?.name || 'Unknown User';
|
||||||
|
|
||||||
|
// Use DB values for created_at/updated_at in edit mode
|
||||||
|
const createdAtValue =
|
||||||
|
props.type === 'edit' && event?.created_at
|
||||||
|
? event.created_at
|
||||||
|
: new Date().toISOString();
|
||||||
|
const updatedAtValue =
|
||||||
|
props.type === 'edit' && event?.updated_at
|
||||||
|
? event.updated_at
|
||||||
|
: new Date().toISOString();
|
||||||
|
|
||||||
|
// Format date for display
|
||||||
|
const createdAtDisplay = new Date(createdAtValue).toLocaleDateString();
|
||||||
|
const updatedAtDisplay = new Date(updatedAtValue).toLocaleDateString();
|
||||||
|
|
||||||
|
if (props.type === 'edit' && isLoading) return <div>Loading...</div>;
|
||||||
|
if (props.type === 'edit' && fetchError)
|
||||||
|
return <div>Error loading event.</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className='flex flex-col gap-5 w-full' onSubmit={handleSubmit}>
|
||||||
|
<div className='grid grid-row-start:auto gap-4 sm:gap-8 w-full'>
|
||||||
|
<div className='h-full w-full mt-0 ml-2 mb-16 flex items-center max-sm:grid max-sm:grid-row-start:auto max-sm:mb-6 max-sm:mt-10 max-sm:ml-0'>
|
||||||
|
<div className='w-[100px] max-sm:w-full max-sm:flex max-sm:justify-center'>
|
||||||
|
<Logo colorType='monochrome' logoType='submark' width={50} />
|
||||||
|
</div>
|
||||||
|
<div className='items-center ml-auto mr-auto max-sm:mb-6 max-sm:w-full'>
|
||||||
|
<LabeledInput
|
||||||
|
type='text'
|
||||||
|
label='Event Name'
|
||||||
|
placeholder={props.type === 'create' ? 'New Event' : 'Event Name'}
|
||||||
|
name='eventName'
|
||||||
|
variantSize='big'
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='w-0 sm:w-[50px]'></div>
|
||||||
|
</div>
|
||||||
|
<div className='grid grid-cols-4 gap-4 h-full w-full max-lg:grid-cols-2 max-sm:grid-cols-1'>
|
||||||
|
<div>
|
||||||
|
<TimePicker
|
||||||
|
dateLabel='start Time'
|
||||||
|
timeLabel=' '
|
||||||
|
date={startDate}
|
||||||
|
setDate={setStartDate}
|
||||||
|
time={startTime}
|
||||||
|
setTime={setStartTime}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TimePicker
|
||||||
|
dateLabel='end Time'
|
||||||
|
timeLabel=' '
|
||||||
|
date={endDate}
|
||||||
|
setDate={setEndDate}
|
||||||
|
time={endTime}
|
||||||
|
setTime={setEndTime}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='w-54'>
|
||||||
|
<LabeledInput
|
||||||
|
type='text'
|
||||||
|
label='Location'
|
||||||
|
placeholder='where is the event?'
|
||||||
|
name='eventLocation'
|
||||||
|
value={location}
|
||||||
|
onChange={(e) => setLocation(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-4'>
|
||||||
|
<div className='flex flex-row gap-2'>
|
||||||
|
<Label className='w-[70px]'>created:</Label>
|
||||||
|
<Label className='text-[var(--color-neutral-300)]'>
|
||||||
|
{createdAtDisplay}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row gap-2'>
|
||||||
|
<Label className='w-[70px]'>updated:</Label>
|
||||||
|
<p className='text-[var(--color-neutral-300)]'>
|
||||||
|
{updatedAtDisplay}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='h-full w-full grid grid-cols-2 gap-4 max-sm:grid-cols-1'>
|
||||||
|
<div className='h-full w-full grid grid-flow-row gap-4'>
|
||||||
|
<div className='h-full w-full'>
|
||||||
|
<div className='flex flex-row gap-2'>
|
||||||
|
<Label>Organiser:</Label>
|
||||||
|
<Label className='text-[var(--color-neutral-300)]'>
|
||||||
|
{organiserValue}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='h-full w-full'>
|
||||||
|
<LabeledInput
|
||||||
|
type='text'
|
||||||
|
label='Event Description'
|
||||||
|
placeholder='What is the event about?'
|
||||||
|
name='eventDescription'
|
||||||
|
variantSize='textarea'
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
></LabeledInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='h-full w-full'>
|
||||||
|
<Label>Participants</Label>
|
||||||
|
<UserSearchInput
|
||||||
|
selectedUsers={selectedParticipants}
|
||||||
|
addUserAction={(user) => {
|
||||||
|
setSelectedParticipants((current) =>
|
||||||
|
current.find((u) => u.id === user.id)
|
||||||
|
? current
|
||||||
|
: [...current, user],
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
removeUserAction={(user) => {
|
||||||
|
setSelectedParticipants((current) =>
|
||||||
|
current.filter((u) => u.id !== user.id),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className='grid grid-cols-1 mt-3 sm:max-h-60 sm:grid-cols-2 sm:overflow-y-auto sm:mb-0'>
|
||||||
|
{selectedParticipants.map((user) => (
|
||||||
|
<ParticipantListEntry
|
||||||
|
key={user.id}
|
||||||
|
user={user}
|
||||||
|
status='PENDING'
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-2 justify-end mt-4 mb-6'>
|
||||||
|
<div className='w-[20%] grid max-sm:w-[40%]'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
variant='secondary'
|
||||||
|
onClick={() => {
|
||||||
|
router.back();
|
||||||
|
console.log('user aborted - no change in database');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className='w-[20%] grid max-sm:w-[40%]'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
variant='primary'
|
||||||
|
disabled={status === 'pending'}
|
||||||
|
>
|
||||||
|
{status === 'pending' ? 'Saving...' : 'save event'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isSuccess && <p>Event created!</p>}
|
||||||
|
{error && <p className='text-red-500'>Error: {error.message}</p>}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventForm;
|
|
@ -63,9 +63,9 @@ export default function Logo({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width === undefined || height === undefined) {
|
if (width === undefined && height === undefined) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Logo: 'width' and 'height' props are required by next/image for ${logoType} logo. Path: ${LOGO_BASE_PATH}logo_${colorType}_${logoType}_${theme}.${IMAGE_EXTENSION}`,
|
`Logo: 'width' or 'height' props are required by next/image for ${logoType} logo. Path: ${LOGO_BASE_PATH}logo_${colorType}_${logoType}_${theme}.${IMAGE_EXTENSION}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
162
src/components/misc/toast-inner.tsx
Normal file
162
src/components/misc/toast-inner.tsx
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
USAGE:
|
||||||
|
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { ToastInner } from '@/components/misc/toast-inner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant='outline_primary'
|
||||||
|
onClick={() =>
|
||||||
|
|
||||||
|
|
||||||
|
toast.custom(
|
||||||
|
(t) => (
|
||||||
|
<ToastInner
|
||||||
|
toastId={t}
|
||||||
|
title=''
|
||||||
|
description=''
|
||||||
|
onAction={() => console.log('on Action')} //No Button shown if this is null
|
||||||
|
variant=''default' | 'success' | 'error' | 'info' | 'warning' | 'notification''
|
||||||
|
buttonText=[No Button shown if this is null]
|
||||||
|
iconName=[Any Icon Name from Lucide in UpperCamelCase or default if null]
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
duration: 5000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Show Toast
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
dominik marked this conversation as resolved
Outdated
dominik
commented
Warum haben wir do einen riesigen USAGE block hier drinne? Warum haben wir do einen riesigen USAGE block hier drinne?
Können wir das auch in eine extra `.md` Datei auslagern?
micha.bok
commented
theoretisch schon, ja, ich wollte das irgendwo hinpacken, wo ich es wieder finde, weiß aber nicht, ob wir das immernoch brauchen, schau ich mir morgen mal an theoretisch schon, ja, ich wollte das irgendwo hinpacken, wo ich es wieder finde, weiß aber nicht, ob wir das immernoch brauchen, schau ich mir morgen mal an
|
|||||||
|
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
import React from 'react';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import * as Icons from 'lucide-react';
|
||||||
|
|
||||||
|
interface ToastInnerProps {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
buttonText?: string;
|
||||||
|
onAction?: () => void;
|
||||||
|
toastId: string | number;
|
||||||
|
variant?:
|
||||||
|
| 'default'
|
||||||
|
| 'success'
|
||||||
|
| 'error'
|
||||||
|
| 'info'
|
||||||
|
| 'warning'
|
||||||
|
| 'notification';
|
||||||
|
iconName?: keyof typeof Icons;
|
||||||
|
closeOnAction?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantConfig = {
|
||||||
|
default: {
|
||||||
|
bgColor: 'bg-toaster-default-bg',
|
||||||
|
defaultIcon: 'Info',
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
bgColor: 'bg-toaster-success-bg',
|
||||||
|
defaultIcon: 'CheckCircle',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
bgColor: 'bg-toaster-error-bg',
|
||||||
|
defaultIcon: 'XCircle',
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
bgColor: 'bg-toaster-info-bg',
|
||||||
|
defaultIcon: 'Info',
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
bgColor: 'bg-toaster-warning-bg',
|
||||||
|
defaultIcon: 'AlertTriangle',
|
||||||
|
},
|
||||||
|
notification: {
|
||||||
|
bgColor: 'bg-toaster-notification-bg',
|
||||||
|
defaultIcon: 'BellRing',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ToastInner: React.FC<ToastInnerProps> = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
buttonText,
|
||||||
|
onAction,
|
||||||
|
toastId,
|
||||||
|
variant = 'default',
|
||||||
|
iconName,
|
||||||
|
closeOnAction = true,
|
||||||
|
}) => {
|
||||||
|
const bgColor = variantConfig[variant].bgColor;
|
||||||
|
|
||||||
|
// fallback to variant's default icon if iconName is not provided
|
||||||
|
const iconKey = (iconName ||
|
||||||
|
variantConfig[variant].defaultIcon) as keyof typeof Icons;
|
||||||
|
const Icon = Icons[iconKey] as React.ComponentType<Icons.LucideProps>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`relative sm:w-120 rounded p-4 ${bgColor} select-none`}>
|
||||||
|
{/* Close Button */}
|
||||||
|
<button
|
||||||
|
onClick={() => toast.dismiss(toastId)}
|
||||||
|
className='absolute top-2 right-2 cursor-pointer'
|
||||||
|
aria-label='Close notification'
|
||||||
|
>
|
||||||
|
<X className='h-4 w-4 text-neutral-600' />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`grid ${
|
||||||
|
variant === 'default'
|
||||||
|
? 'grid-cols-[auto_130px] max-sm:grid-cols-[auto_90px]'
|
||||||
|
: 'grid-cols-[40px_auto_130px] max-sm:grid-cols-[40px_auto_90px]'
|
||||||
|
} gap-4 items-center`}
|
||||||
|
>
|
||||||
|
{variant !== 'default' && (
|
||||||
|
<div className='flex items-center justify-center'>
|
||||||
|
<Icon className='text-text-alt' size={40} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Text Content */}
|
||||||
|
<div className='grid gap-1'>
|
||||||
|
<h6 className='text-text-alt'>{title}</h6>
|
||||||
|
{description && (
|
||||||
|
<Label className='text-text-alt'>{description}</Label>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Button */}
|
||||||
|
<div className='flex justify-center'>
|
||||||
|
{onAction && buttonText && (
|
||||||
|
<Button
|
||||||
|
variant={'secondary'}
|
||||||
|
className='w-full mr-2'
|
||||||
|
onClick={() => {
|
||||||
|
onAction();
|
||||||
|
if (closeOnAction) {
|
||||||
|
toast.dismiss(toastId);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
95
src/components/misc/user-search.tsx
Normal file
95
src/components/misc/user-search.tsx
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
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 { useGetApiSearchUser } from '@/generated/api/search/search';
|
||||||
|
import zod from 'zod/v4';
|
||||||
|
import { PublicUserSchema } from '@/app/api/user/validation';
|
||||||
|
|
||||||
|
type User = zod.output<typeof PublicUserSchema>;
|
||||||
|
|
||||||
|
export function UserSearchInput({
|
||||||
|
addUserAction,
|
||||||
|
removeUserAction,
|
||||||
|
selectedUsers,
|
||||||
|
}: {
|
||||||
|
addUserAction: (user: User) => void;
|
||||||
|
removeUserAction: (user: User) => void;
|
||||||
|
selectedUsers: User[];
|
||||||
|
}) {
|
||||||
|
const [userSearch, setUserSearch] = React.useState('');
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const { data: searchUserData } = useGetApiSearchUser({ query: userSearch });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='outline_muted'
|
||||||
|
role='combobox'
|
||||||
|
aria-expanded={open}
|
||||||
|
className='w-[200px] justify-between'
|
||||||
|
>
|
||||||
|
{'Select user...'}
|
||||||
|
<ChevronsUpDownIcon className='ml-2 h-4 w-4 shrink-0 opacity-50' />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className='w-[200px] p-0'>
|
||||||
|
<Command shouldFilter={false}>
|
||||||
|
<CommandInput
|
||||||
|
placeholder='Search user...'
|
||||||
|
value={userSearch}
|
||||||
|
onValueChange={setUserSearch}
|
||||||
|
/>
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No users found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{searchUserData?.data.users?.map((user) => {
|
||||||
|
const isSelected = selectedUsers.some((u) => u.id === user.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={user.id}
|
||||||
|
value={user.id}
|
||||||
|
onSelect={() => {
|
||||||
|
if (isSelected) {
|
||||||
|
removeUserAction(user);
|
||||||
|
} else {
|
||||||
|
addUserAction(user);
|
||||||
|
}
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
'mr-2 h-4 w-4',
|
||||||
|
isSelected ? 'opacity-100' : 'opacity-0',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{user.name}
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
86
src/components/time-picker.tsx
Normal file
86
src/components/time-picker.tsx
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ChevronDownIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Calendar } from '@/components/ui/calendar';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
|
|
||||||
|
export default function TimePicker({
|
||||||
|
dateLabel = 'Date',
|
||||||
|
timeLabel = 'Time',
|
||||||
|
date,
|
||||||
|
setDate,
|
||||||
|
time,
|
||||||
|
setTime,
|
||||||
|
}: {
|
||||||
|
dateLabel?: string;
|
||||||
|
timeLabel?: string;
|
||||||
|
date?: Date;
|
||||||
|
setDate?: (date: Date | undefined) => void;
|
||||||
|
time?: string;
|
||||||
|
setTime?: (time: string) => void;
|
||||||
|
}) {
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex gap-4'>
|
||||||
|
<div className='flex flex-col gap-3'>
|
||||||
|
<Label htmlFor='date' className='px-1'>
|
||||||
|
{dateLabel}
|
||||||
|
</Label>
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant='calendar' id='date'>
|
||||||
|
{date ? date.toLocaleDateString() : 'Select date'}
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className='w-auto overflow-hidden p-0' align='start'>
|
||||||
|
<Calendar
|
||||||
|
mode='single'
|
||||||
|
selected={date}
|
||||||
|
captionLayout='dropdown'
|
||||||
|
onSelect={(d) => {
|
||||||
|
setDate?.(d);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
modifiers={{
|
||||||
|
today: new Date(),
|
||||||
|
}}
|
||||||
|
modifiersClassNames={{
|
||||||
|
today: 'bg-secondary text-secondary-foreground rounded-full',
|
||||||
|
}}
|
||||||
|
classNames={{
|
||||||
|
day: 'text-center hover:bg-gray-500 hover:rounded-md',
|
||||||
|
}}
|
||||||
|
weekStartsOn={1} // Set Monday as the first day of the week
|
||||||
|
startMonth={new Date(new Date().getFullYear() - 10, 0)}
|
||||||
|
endMonth={new Date(new Date().getFullYear() + 14, 12)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-3'>
|
||||||
|
<Label htmlFor='time' className='px-1'>
|
||||||
|
{timeLabel}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
type='time'
|
||||||
|
id='time'
|
||||||
|
step='60'
|
||||||
|
value={time}
|
||||||
|
onChange={(e) => setTime?.(e.target.value)}
|
||||||
|
className='bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -21,8 +21,13 @@ const buttonVariants = cva(
|
||||||
'bg-background border-2 text-text shadow-xs hover:bg-secondary border-secondary hover:border-background-reversed active:bg-active-secondary disabled:bg-disabled-secondary',
|
'bg-background border-2 text-text shadow-xs hover:bg-secondary border-secondary hover:border-background-reversed active:bg-active-secondary disabled:bg-disabled-secondary',
|
||||||
outline_muted:
|
outline_muted:
|
||||||
'bg-background border-2 text-text shadow-xs hover:bg-muted border-muted hover:border-background-reversed active:bg-active-muted disabled:bg-disabled-muted',
|
'bg-background border-2 text-text shadow-xs hover:bg-muted border-muted hover:border-background-reversed active:bg-active-muted disabled:bg-disabled-muted',
|
||||||
|
|
||||||
link: 'text-text underline-offset-4 hover:underline',
|
link: 'text-text underline-offset-4 hover:underline',
|
||||||
|
calendar:
|
||||||
|
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 w-32 justify-between font-normal',
|
||||||
|
ghost:
|
||||||
|
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||||
|
destructive:
|
||||||
|
'bg-destructive text-text shadow-xs hover:bg-hover-destructive active:bg-active-destructive disabled:bg-disabled-destructive',
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||||
|
|
213
src/components/ui/calendar.tsx
Normal file
213
src/components/ui/calendar.tsx
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
ChevronDownIcon,
|
||||||
|
ChevronLeftIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Button, buttonVariants } from '@/components/ui/button';
|
||||||
|
|
||||||
|
function Calendar({
|
||||||
|
className,
|
||||||
|
classNames,
|
||||||
|
showOutsideDays = true,
|
||||||
|
captionLayout = 'label',
|
||||||
|
buttonVariant = 'ghost',
|
||||||
|
formatters,
|
||||||
|
components,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DayPicker> & {
|
||||||
|
buttonVariant?: React.ComponentProps<typeof Button>['variant'];
|
||||||
|
}) {
|
||||||
|
const defaultClassNames = getDefaultClassNames();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DayPicker
|
||||||
|
showOutsideDays={showOutsideDays}
|
||||||
|
className={cn(
|
||||||
|
'bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
|
||||||
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||||
|
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
captionLayout={captionLayout}
|
||||||
|
formatters={{
|
||||||
|
formatMonthDropdown: (date) =>
|
||||||
|
date.toLocaleString('default', { month: 'short' }),
|
||||||
|
...formatters,
|
||||||
|
}}
|
||||||
|
classNames={{
|
||||||
|
root: cn('w-fit', defaultClassNames.root),
|
||||||
|
months: cn(
|
||||||
|
'flex gap-4 flex-col md:flex-row relative',
|
||||||
|
defaultClassNames.months,
|
||||||
|
),
|
||||||
|
month: cn('flex flex-col w-full gap-4', defaultClassNames.month),
|
||||||
|
nav: cn(
|
||||||
|
'flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between',
|
||||||
|
defaultClassNames.nav,
|
||||||
|
),
|
||||||
|
button_previous: cn(
|
||||||
|
buttonVariants({ variant: buttonVariant }),
|
||||||
|
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
|
||||||
|
defaultClassNames.button_previous,
|
||||||
|
),
|
||||||
|
button_next: cn(
|
||||||
|
buttonVariants({ variant: buttonVariant }),
|
||||||
|
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
|
||||||
|
defaultClassNames.button_next,
|
||||||
|
),
|
||||||
|
month_caption: cn(
|
||||||
|
'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)',
|
||||||
|
defaultClassNames.month_caption,
|
||||||
|
),
|
||||||
|
dropdowns: cn(
|
||||||
|
'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5',
|
||||||
|
defaultClassNames.dropdowns,
|
||||||
|
),
|
||||||
|
dropdown_root: cn(
|
||||||
|
'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md',
|
||||||
|
defaultClassNames.dropdown_root,
|
||||||
|
),
|
||||||
|
dropdown: cn(
|
||||||
|
'bg-[var(--color-basecl)] absolute inset-0 opacity-0',
|
||||||
|
defaultClassNames.dropdown,
|
||||||
|
),
|
||||||
|
caption_label: cn(
|
||||||
|
'select-none font-medium',
|
||||||
|
captionLayout === 'label'
|
||||||
|
? 'text-sm'
|
||||||
|
: 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5',
|
||||||
|
defaultClassNames.caption_label,
|
||||||
|
),
|
||||||
|
table: 'w-full border-collapse',
|
||||||
|
weekdays: cn('flex', defaultClassNames.weekdays),
|
||||||
|
weekday: cn(
|
||||||
|
'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none',
|
||||||
|
defaultClassNames.weekday,
|
||||||
|
),
|
||||||
|
week: cn('flex w-full mt-2', defaultClassNames.week),
|
||||||
|
week_number_header: cn(
|
||||||
|
'select-none w-(--cell-size)',
|
||||||
|
defaultClassNames.week_number_header,
|
||||||
|
),
|
||||||
|
week_number: cn(
|
||||||
|
'text-[0.8rem] select-none text-muted-foreground',
|
||||||
|
defaultClassNames.week_number,
|
||||||
|
),
|
||||||
|
day: cn(
|
||||||
|
'relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',
|
||||||
|
defaultClassNames.day,
|
||||||
|
),
|
||||||
|
range_start: cn(
|
||||||
|
'rounded-l-md bg-accent',
|
||||||
|
defaultClassNames.range_start,
|
||||||
|
),
|
||||||
|
range_middle: cn('rounded-none', defaultClassNames.range_middle),
|
||||||
|
range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end),
|
||||||
|
today: cn(
|
||||||
|
'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
|
||||||
|
defaultClassNames.today,
|
||||||
|
),
|
||||||
|
outside: cn(
|
||||||
|
'text-muted-foreground aria-selected:text-muted-foreground',
|
||||||
|
defaultClassNames.outside,
|
||||||
|
),
|
||||||
|
disabled: cn(
|
||||||
|
'text-muted-foreground opacity-50',
|
||||||
|
defaultClassNames.disabled,
|
||||||
|
),
|
||||||
|
hidden: cn('invisible', defaultClassNames.hidden),
|
||||||
|
...classNames,
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
Root: ({ className, rootRef, ...props }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='calendar'
|
||||||
|
ref={rootRef}
|
||||||
|
className={cn(className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Chevron: ({ className, orientation, ...props }) => {
|
||||||
|
if (orientation === 'left') {
|
||||||
|
return (
|
||||||
|
<ChevronLeftIcon className={cn('size-4', className)} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orientation === 'right') {
|
||||||
|
return (
|
||||||
|
<ChevronRightIcon
|
||||||
|
className={cn('size-4', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChevronDownIcon className={cn('size-4', className)} {...props} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
DayButton: CalendarDayButton,
|
||||||
|
WeekNumber: ({ children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<td {...props}>
|
||||||
|
<div className='flex size-(--cell-size) items-center justify-center text-center'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
...components,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CalendarDayButton({
|
||||||
|
className,
|
||||||
|
day,
|
||||||
|
modifiers,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DayButton>) {
|
||||||
|
const defaultClassNames = getDefaultClassNames();
|
||||||
|
|
||||||
|
const ref = React.useRef<HTMLButtonElement>(null);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (modifiers.focused) ref.current?.focus();
|
||||||
|
}, [modifiers.focused]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
variant='ghost'
|
||||||
|
size='icon'
|
||||||
|
data-day={day.date.toLocaleDateString()}
|
||||||
|
data-selected-single={
|
||||||
|
modifiers.selected &&
|
||||||
|
!modifiers.range_start &&
|
||||||
|
!modifiers.range_end &&
|
||||||
|
!modifiers.range_middle
|
||||||
|
}
|
||||||
|
data-range-start={modifiers.range_start}
|
||||||
|
data-range-end={modifiers.range_end}
|
||||||
|
data-range-middle={modifiers.range_middle}
|
||||||
|
className={cn(
|
||||||
|
'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70',
|
||||||
|
defaultClassNames.day,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Calendar, CalendarDayButton };
|
|
@ -22,7 +22,7 @@ function Card({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
/* Outline */
|
/* Outline */
|
||||||
'',
|
'',
|
||||||
/* Shadow */
|
/* Shadow */
|
||||||
'shadow-sm',
|
'shadow-[4px_4px_9px_9px_rgba(0,0,0,0.25)]',
|
||||||
/* Opacity */
|
/* Opacity */
|
||||||
'',
|
'',
|
||||||
/* Scaling */
|
/* Scaling */
|
||||||
|
|
184
src/components/ui/command.tsx
Normal file
184
src/components/ui/command.tsx
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Command as CommandPrimitive } from 'cmdk';
|
||||||
|
import { SearchIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
|
||||||
|
function Command({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive
|
||||||
|
data-slot='command'
|
||||||
|
className={cn(
|
||||||
|
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandDialog({
|
||||||
|
title = 'Command Palette',
|
||||||
|
description = 'Search for a command to run...',
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof Dialog> & {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
className?: string;
|
||||||
|
showCloseButton?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogHeader className='sr-only'>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogDescription>{description}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogContent
|
||||||
|
className={cn('overflow-hidden p-0', className)}
|
||||||
|
showCloseButton={showCloseButton}
|
||||||
|
>
|
||||||
|
<Command className='[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5'>
|
||||||
|
{children}
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandInput({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='command-input-wrapper'
|
||||||
|
className='flex h-9 items-center gap-2 border-b px-3'
|
||||||
|
>
|
||||||
|
<SearchIcon className='size-4 shrink-0 opacity-50' />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
data-slot='command-input'
|
||||||
|
className={cn(
|
||||||
|
'placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandList({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
data-slot='command-list'
|
||||||
|
className={cn(
|
||||||
|
'max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandEmpty({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Empty
|
||||||
|
data-slot='command-empty'
|
||||||
|
className='py-6 text-center text-sm'
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandGroup({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
data-slot='command-group'
|
||||||
|
className={cn(
|
||||||
|
'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Separator
|
||||||
|
data-slot='command-separator'
|
||||||
|
className={cn('bg-border -mx-1 h-px', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandItem({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
data-slot='command-item'
|
||||||
|
className={cn(
|
||||||
|
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandShortcut({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'span'>) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot='command-shortcut'
|
||||||
|
className={cn(
|
||||||
|
'text-muted-foreground ml-auto text-xs tracking-widest',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandList,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandShortcut,
|
||||||
|
CommandSeparator,
|
||||||
|
};
|
143
src/components/ui/dialog.tsx
Normal file
143
src/components/ui/dialog.tsx
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
|
import { XIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function Dialog({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||||
|
return <DialogPrimitive.Root data-slot='dialog' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||||
|
return <DialogPrimitive.Trigger data-slot='dialog-trigger' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogPortal({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||||
|
return <DialogPrimitive.Portal data-slot='dialog-portal' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogClose({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||||
|
return <DialogPrimitive.Close data-slot='dialog-close' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogOverlay({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
data-slot='dialog-overlay'
|
||||||
|
className={cn(
|
||||||
|
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||||
|
showCloseButton?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPortal data-slot='dialog-portal'>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
data-slot='dialog-content'
|
||||||
|
className={cn(
|
||||||
|
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close
|
||||||
|
data-slot='dialog-close'
|
||||||
|
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||||
|
>
|
||||||
|
<XIcon />
|
||||||
|
<span className='sr-only'>Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='dialog-header'
|
||||||
|
className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='dialog-footer'
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
data-slot='dialog-title'
|
||||||
|
className={cn('text-lg leading-none font-semibold', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
data-slot='dialog-description'
|
||||||
|
className={cn('text-muted-foreground text-sm', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
};
|
|
@ -9,7 +9,7 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
|
||||||
data-slot='input'
|
data-slot='input'
|
||||||
className={cn(
|
className={cn(
|
||||||
/* Text */
|
/* Text */
|
||||||
'text-text-input selection:text-text md:text-sm file:text-destructive file:text-sm placeholder:text-text-muted-input',
|
'text-text-input selection:text-text file:text-destructive file:text-sm placeholder:text-text-muted-input',
|
||||||
/* Background */
|
/* Background */
|
||||||
'bg-transparent selection:bg-muted-input file:bg-transparent',
|
'bg-transparent selection:bg-muted-input file:bg-transparent',
|
||||||
/* Border */
|
/* Border */
|
||||||
|
@ -41,4 +41,45 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Input };
|
function Textarea({
|
||||||
|
className,
|
||||||
|
rows,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'textarea'>) {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
data-slot='input'
|
||||||
|
rows={rows}
|
||||||
|
className={cn(
|
||||||
|
/* Text */
|
||||||
|
'text-text-input selection:text-text placeholder:text-text-muted-input',
|
||||||
|
/* Background */
|
||||||
|
'bg-transparent selection:bg-muted-input',
|
||||||
|
/* Border */
|
||||||
|
'rounded-md border border-input focus-visible:border-ring aria-invalid:border-destructive',
|
||||||
|
/* Font */
|
||||||
|
'',
|
||||||
|
/* Cursor */
|
||||||
|
'disabled:pointer-events-none disabled:cursor-not-allowed',
|
||||||
|
/* Ring */
|
||||||
|
'focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
|
||||||
|
/* Outline */
|
||||||
|
'outline-none',
|
||||||
|
/* Shadow */
|
||||||
|
'shadow-md transition-[color,box-shadow]',
|
||||||
|
/* Opacity */
|
||||||
|
'disabled:opacity-50',
|
||||||
|
/* Scaling */
|
||||||
|
'h-32 w-full min-w-0', // Bigger height for textarea
|
||||||
|
/* Spacing */
|
||||||
|
'px-3 py-2',
|
||||||
|
/* Alignment */
|
||||||
|
'',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Input, Textarea };
|
||||||
|
|
|
@ -5,16 +5,21 @@ import * as LabelPrimitive from '@radix-ui/react-label';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
function Label({
|
type LabelProps = React.ComponentProps<typeof LabelPrimitive.Root> & {
|
||||||
className,
|
size?: 'default' | 'small' | 'large';
|
||||||
...props
|
};
|
||||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
||||||
|
function Label({ className, size = 'default', ...props }: LabelProps) {
|
||||||
return (
|
return (
|
||||||
<LabelPrimitive.Root
|
<LabelPrimitive.Root
|
||||||
data-slot='label'
|
data-slot='label'
|
||||||
className={cn(
|
className={cn(
|
||||||
/* Text */
|
/* Text */
|
||||||
'text-sm',
|
size === 'small'
|
||||||
|
? 'text-sm'
|
||||||
|
: size === 'large'
|
||||||
|
? 'text-xl'
|
||||||
|
: 'text-base',
|
||||||
/* Background */
|
/* Background */
|
||||||
'',
|
'',
|
||||||
/* Border */
|
/* Border */
|
||||||
|
|
48
src/components/ui/popover.tsx
Normal file
48
src/components/ui/popover.tsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function Popover({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||||
|
return <PopoverPrimitive.Root data-slot='popover' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||||
|
return <PopoverPrimitive.Trigger data-slot='popover-trigger' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverContent({
|
||||||
|
className,
|
||||||
|
align = 'center',
|
||||||
|
sideOffset = 4,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
data-slot='popover-content'
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverAnchor({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||||
|
return <PopoverPrimitive.Anchor data-slot='popover-anchor' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
54
src/components/ui/sonner.tsx
Normal file
54
src/components/ui/sonner.tsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
import { Toaster as Sonner, ToasterProps } from 'sonner';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
|
const { theme = 'system' } = useTheme();
|
||||||
|
|
||||||
|
const [shouldExpand, setShouldExpand] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const mediaQuery = window.matchMedia('(min-width: 600px)');
|
||||||
|
|
||||||
|
const handleScreenSizeChange = () => {
|
||||||
|
setShouldExpand(mediaQuery.matches);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleScreenSizeChange(); // set initial value
|
||||||
|
mediaQuery.addEventListener('change', handleScreenSizeChange);
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
mediaQuery.removeEventListener('change', handleScreenSizeChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
theme={theme as ToasterProps['theme']}
|
||||||
|
richColors={true}
|
||||||
|
className='toaster group'
|
||||||
|
toastOptions={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: 'var(--color-neutral-150)',
|
||||||
|
color: 'var(--color-text-alt)',
|
||||||
|
borderRadius: 'var(--radius)',
|
||||||
|
},
|
||||||
|
cancelButtonStyle: {
|
||||||
|
backgroundColor: 'var(--color-secondary)',
|
||||||
|
color: 'var(--color-text-alt)',
|
||||||
|
},
|
||||||
|
actionButtonStyle: {
|
||||||
|
backgroundColor: 'var(--color-secondary)',
|
||||||
|
color: 'var(--color-text-alt)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
swipeDirections={['left', 'right']}
|
||||||
|
closeButton={true}
|
||||||
|
expand={shouldExpand}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Toaster };
|
352
yarn.lock
352
yarn.lock
|
@ -33,7 +33,19 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@apidevtools/openapi-schemas@npm:^2.1.0":
|
"@apidevtools/json-schema-ref-parser@npm:^9.0.6":
|
||||||
|
version: 9.1.2
|
||||||
|
resolution: "@apidevtools/json-schema-ref-parser@npm:9.1.2"
|
||||||
|
dependencies:
|
||||||
|
"@jsdevtools/ono": "npm:^7.1.3"
|
||||||
|
"@types/json-schema": "npm:^7.0.6"
|
||||||
|
call-me-maybe: "npm:^1.0.1"
|
||||||
|
js-yaml: "npm:^4.1.0"
|
||||||
|
checksum: 10c0/ebf952eb2e00bf0919f024e72897e047fd5012f0a9e47ac361873f6de0a733b9334513cdbc73205a6b43ac4a652b8c87f55e489c39b2d60bd0bc1cb2b411e218
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@apidevtools/openapi-schemas@npm:^2.0.4, @apidevtools/openapi-schemas@npm:^2.1.0":
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
resolution: "@apidevtools/openapi-schemas@npm:2.1.0"
|
resolution: "@apidevtools/openapi-schemas@npm:2.1.0"
|
||||||
checksum: 10c0/f4aa0f9df32e474d166c84ef91bceb18fa1c4f44b5593879529154ef340846811ea57dc2921560f157f692262827d28d988dd6e19fb21f00320e9961964176b4
|
checksum: 10c0/f4aa0f9df32e474d166c84ef91bceb18fa1c4f44b5593879529154ef340846811ea57dc2921560f157f692262827d28d988dd6e19fb21f00320e9961964176b4
|
||||||
|
@ -47,6 +59,22 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@apidevtools/swagger-parser@npm:10.0.3":
|
||||||
|
version: 10.0.3
|
||||||
|
resolution: "@apidevtools/swagger-parser@npm:10.0.3"
|
||||||
|
dependencies:
|
||||||
|
"@apidevtools/json-schema-ref-parser": "npm:^9.0.6"
|
||||||
|
"@apidevtools/openapi-schemas": "npm:^2.0.4"
|
||||||
|
"@apidevtools/swagger-methods": "npm:^3.0.2"
|
||||||
|
"@jsdevtools/ono": "npm:^7.1.3"
|
||||||
|
call-me-maybe: "npm:^1.0.1"
|
||||||
|
z-schema: "npm:^5.0.1"
|
||||||
|
peerDependencies:
|
||||||
|
openapi-types: ">=7"
|
||||||
|
checksum: 10c0/3b43f719c2d647ac8dcf30f132834d413ce21cbf7a8d9c3b35ec91149dd25d608c8fd892358fcd61a8edd8c5140a7fb13676f948e2d87067d081a47b8c7107e9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@apidevtools/swagger-parser@npm:^10.1.1":
|
"@apidevtools/swagger-parser@npm:^10.1.1":
|
||||||
version: 10.1.1
|
version: 10.1.1
|
||||||
resolution: "@apidevtools/swagger-parser@npm:10.1.1"
|
resolution: "@apidevtools/swagger-parser@npm:10.1.1"
|
||||||
|
@ -180,6 +208,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@date-fns/tz@npm:1.2.0":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "@date-fns/tz@npm:1.2.0"
|
||||||
|
checksum: 10c0/411e9d4303b10951f6fd0189d18fb845f0d934a575df2176bc10daf664282c765fb6b057a977e446bbb1229151d89e7788978600a019f1fc24b5c75276d496bd
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@emnapi/core@npm:^1.4.3":
|
"@emnapi/core@npm:^1.4.3":
|
||||||
version: 1.4.3
|
version: 1.4.3
|
||||||
resolution: "@emnapi/core@npm:1.4.3"
|
resolution: "@emnapi/core@npm:1.4.3"
|
||||||
|
@ -428,12 +463,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/core@npm:^0.15.0":
|
"@eslint/core@npm:^0.15.1":
|
||||||
version: 0.15.0
|
version: 0.15.1
|
||||||
resolution: "@eslint/core@npm:0.15.0"
|
resolution: "@eslint/core@npm:0.15.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/json-schema": "npm:^7.0.15"
|
"@types/json-schema": "npm:^7.0.15"
|
||||||
checksum: 10c0/9882c69acfe29743ce473a619d5248589c6687561afaabe8ec8d7ffed07592db16edcca3af022f33ea92fe5f6cfbe3545ee53e89292579d22a944ebaeddcf72d
|
checksum: 10c0/abaf641940776638b8c15a38d99ce0dac551a8939310ec81b9acd15836a574cf362588eaab03ab11919bc2a0f9648b19ea8dee33bf12675eb5b6fd38bda6f25e
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -469,12 +504,12 @@ __metadata:
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/plugin-kit@npm:^0.3.1":
|
"@eslint/plugin-kit@npm:^0.3.1":
|
||||||
version: 0.3.2
|
version: 0.3.3
|
||||||
resolution: "@eslint/plugin-kit@npm:0.3.2"
|
resolution: "@eslint/plugin-kit@npm:0.3.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/core": "npm:^0.15.0"
|
"@eslint/core": "npm:^0.15.1"
|
||||||
levn: "npm:^0.4.1"
|
levn: "npm:^0.4.1"
|
||||||
checksum: 10c0/e069b0a46eb9fa595a1ac7dea4540a9daa493afba88875ee054e9117609c1c41555e779303cb4cff36cf88f603ba6eba2556a927e8ced77002828206ee17fc7e
|
checksum: 10c0/c61888eb8757abc0d25a53c1832f85521c2f347126c475eb32d3596be3505e8619e0ceddee7346d195089a2eb1633b61e6127a5772b8965a85eb9f55b8b1cebe
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -1406,7 +1441,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-compose-refs@npm:1.1.2":
|
"@radix-ui/react-compose-refs@npm:1.1.2, @radix-ui/react-compose-refs@npm:^1.1.1":
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
resolution: "@radix-ui/react-compose-refs@npm:1.1.2"
|
resolution: "@radix-ui/react-compose-refs@npm:1.1.2"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1432,7 +1467,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-dialog@npm:^1.1.14":
|
"@radix-ui/react-dialog@npm:^1.1.14, @radix-ui/react-dialog@npm:^1.1.6":
|
||||||
version: 1.1.14
|
version: 1.1.14
|
||||||
resolution: "@radix-ui/react-dialog@npm:1.1.14"
|
resolution: "@radix-ui/react-dialog@npm:1.1.14"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1586,7 +1621,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-id@npm:1.1.1":
|
"@radix-ui/react-id@npm:1.1.1, @radix-ui/react-id@npm:^1.1.0":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "@radix-ui/react-id@npm:1.1.1"
|
resolution: "@radix-ui/react-id@npm:1.1.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1656,6 +1691,39 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-popover@npm:^1.1.14":
|
||||||
|
version: 1.1.14
|
||||||
|
resolution: "@radix-ui/react-popover@npm:1.1.14"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/primitive": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-compose-refs": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-context": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-dismissable-layer": "npm:1.1.10"
|
||||||
|
"@radix-ui/react-focus-guards": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-focus-scope": "npm:1.1.7"
|
||||||
|
"@radix-ui/react-id": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-popper": "npm:1.2.7"
|
||||||
|
"@radix-ui/react-portal": "npm:1.1.9"
|
||||||
|
"@radix-ui/react-presence": "npm:1.1.4"
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.3"
|
||||||
|
"@radix-ui/react-slot": "npm:1.2.3"
|
||||||
|
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
|
||||||
|
aria-hidden: "npm:^1.2.4"
|
||||||
|
react-remove-scroll: "npm:^2.6.3"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/04e557bfcaab4887694d119555b101e16b8a4e99595541ff2cbe805c551be853cb02882a2ada04e6507ffc45bc092bc2b89704b7b79f5025251767d0b4f3230a
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-popper@npm:1.2.7":
|
"@radix-ui/react-popper@npm:1.2.7":
|
||||||
version: 1.2.7
|
version: 1.2.7
|
||||||
resolution: "@radix-ui/react-popper@npm:1.2.7"
|
resolution: "@radix-ui/react-popper@npm:1.2.7"
|
||||||
|
@ -1724,7 +1792,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-primitive@npm:2.1.3":
|
"@radix-ui/react-primitive@npm:2.1.3, @radix-ui/react-primitive@npm:^2.0.2":
|
||||||
version: 2.1.3
|
version: 2.1.3
|
||||||
resolution: "@radix-ui/react-primitive@npm:2.1.3"
|
resolution: "@radix-ui/react-primitive@npm:2.1.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1770,7 +1838,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-scroll-area@npm:^1.2.8":
|
"@radix-ui/react-scroll-area@npm:^1.2.9":
|
||||||
version: 1.2.9
|
version: 1.2.9
|
||||||
resolution: "@radix-ui/react-scroll-area@npm:1.2.9"
|
resolution: "@radix-ui/react-scroll-area@npm:1.2.9"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -3248,7 +3316,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/json-schema@npm:^7.0.11, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.7":
|
"@types/json-schema@npm:^7.0.11, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.7":
|
||||||
version: 7.0.15
|
version: 7.0.15
|
||||||
resolution: "@types/json-schema@npm:7.0.15"
|
resolution: "@types/json-schema@npm:7.0.15"
|
||||||
checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db
|
checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db
|
||||||
|
@ -3321,6 +3389,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/swagger-jsdoc@npm:6.0.4":
|
||||||
|
version: 6.0.4
|
||||||
|
resolution: "@types/swagger-jsdoc@npm:6.0.4"
|
||||||
|
checksum: 10c0/fbe17d91a12e1e60a255b02e6def6877c81b356c75ffcd0e5167fbaf1476e2d6600cd7eea79e6b3e0ff7929dec33ade345147509ed3b98026f63c782b74514f6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/swagger-ui-react@npm:5":
|
"@types/swagger-ui-react@npm:5":
|
||||||
version: 5.18.0
|
version: 5.18.0
|
||||||
resolution: "@types/swagger-ui-react@npm:5.18.0"
|
resolution: "@types/swagger-ui-react@npm:5.18.0"
|
||||||
|
@ -4386,6 +4461,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"cleye@npm:1.3.2":
|
||||||
|
version: 1.3.2
|
||||||
|
resolution: "cleye@npm:1.3.2"
|
||||||
|
dependencies:
|
||||||
|
terminal-columns: "npm:^1.4.1"
|
||||||
|
type-flag: "npm:^3.0.0"
|
||||||
|
checksum: 10c0/2cd63c194d8476230cb9730dae87ae106995ff36b5e43436965caf14ebc6386b7a33e61cc12f90f27e42f4490383fb6032f1f1956bbbc12a3bb13e5cc74cbd78
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"cli-cursor@npm:^3.1.0":
|
"cli-cursor@npm:^3.1.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "cli-cursor@npm:3.1.0"
|
resolution: "cli-cursor@npm:3.1.0"
|
||||||
|
@ -4443,6 +4528,21 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"cmdk@npm:^1.1.1":
|
||||||
|
version: 1.1.1
|
||||||
|
resolution: "cmdk@npm:1.1.1"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-compose-refs": "npm:^1.1.1"
|
||||||
|
"@radix-ui/react-dialog": "npm:^1.1.6"
|
||||||
|
"@radix-ui/react-id": "npm:^1.1.0"
|
||||||
|
"@radix-ui/react-primitive": "npm:^2.0.2"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
react-dom: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
checksum: 10c0/5605ac4396ec9bc65c82f954da19dd89a0636a54026df72780e2470da1381f9d57434a80a53f2d57eaa4e759660a3ebba9232b74258dc09970576591eae03116
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"color-convert@npm:^2.0.1":
|
"color-convert@npm:^2.0.1":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "color-convert@npm:2.0.1"
|
resolution: "color-convert@npm:2.0.1"
|
||||||
|
@ -4509,6 +4609,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"commander@npm:6.2.0":
|
||||||
|
version: 6.2.0
|
||||||
|
resolution: "commander@npm:6.2.0"
|
||||||
|
checksum: 10c0/1b701c6726fc2b6c6a7d9ab017be9465153546a05767cdd0e15e9f9a11c07f88f64d47684b90b07e5fb103d173efb6afdf4a21f6d6c4c25f7376bd027d21062c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"commander@npm:^6.2.1":
|
"commander@npm:^6.2.1":
|
||||||
version: 6.2.1
|
version: 6.2.1
|
||||||
resolution: "commander@npm:6.2.1"
|
resolution: "commander@npm:6.2.1"
|
||||||
|
@ -4516,6 +4623,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"commander@npm:^9.4.1":
|
||||||
|
version: 9.5.0
|
||||||
|
resolution: "commander@npm:9.5.0"
|
||||||
|
checksum: 10c0/5f7784fbda2aaec39e89eb46f06a999e00224b3763dc65976e05929ec486e174fe9aac2655f03ba6a5e83875bd173be5283dc19309b7c65954701c02025b3c1d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"common-tags@npm:^1.8.0":
|
"common-tags@npm:^1.8.0":
|
||||||
version: 1.8.2
|
version: 1.8.2
|
||||||
resolution: "common-tags@npm:1.8.2"
|
resolution: "common-tags@npm:1.8.2"
|
||||||
|
@ -4695,6 +4809,20 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"date-fns-jalali@npm:4.1.0-0":
|
||||||
|
version: 4.1.0-0
|
||||||
|
resolution: "date-fns-jalali@npm:4.1.0-0"
|
||||||
|
checksum: 10c0/f9ad98d9f7e8e5abe0d070dc806b0c8baded2b1208626c42e92cbd2605b5171f5714d6b79b20cc2666267d821699244c9d0b5e93274106cf57d6232da77596ed
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"date-fns@npm:4.1.0, date-fns@npm:^4.1.0":
|
||||||
|
version: 4.1.0
|
||||||
|
resolution: "date-fns@npm:4.1.0"
|
||||||
|
checksum: 10c0/b79ff32830e6b7faa009590af6ae0fb8c3fd9ffad46d930548fbb5acf473773b4712ae887e156ba91a7b3dc30591ce0f517d69fd83bd9c38650fdc03b4e0bac8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"dayjs@npm:^1.10.4":
|
"dayjs@npm:^1.10.4":
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
resolution: "dayjs@npm:1.11.13"
|
resolution: "dayjs@npm:1.11.13"
|
||||||
|
@ -4810,6 +4938,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"doctrine@npm:3.0.0":
|
||||||
|
version: 3.0.0
|
||||||
|
resolution: "doctrine@npm:3.0.0"
|
||||||
|
dependencies:
|
||||||
|
esutils: "npm:^2.0.2"
|
||||||
|
checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"doctrine@npm:^2.1.0":
|
"doctrine@npm:^2.1.0":
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
resolution: "doctrine@npm:2.1.0"
|
resolution: "doctrine@npm:2.1.0"
|
||||||
|
@ -5886,6 +6023,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"fs.realpath@npm:^1.0.0":
|
||||||
|
version: 1.0.0
|
||||||
|
resolution: "fs.realpath@npm:1.0.0"
|
||||||
|
checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"function-bind@npm:^1.1.2":
|
"function-bind@npm:^1.1.2":
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
resolution: "function-bind@npm:1.1.2"
|
resolution: "function-bind@npm:1.1.2"
|
||||||
|
@ -6028,6 +6172,20 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"glob@npm:7.1.6":
|
||||||
|
version: 7.1.6
|
||||||
|
resolution: "glob@npm:7.1.6"
|
||||||
|
dependencies:
|
||||||
|
fs.realpath: "npm:^1.0.0"
|
||||||
|
inflight: "npm:^1.0.4"
|
||||||
|
inherits: "npm:2"
|
||||||
|
minimatch: "npm:^3.0.4"
|
||||||
|
once: "npm:^1.3.0"
|
||||||
|
path-is-absolute: "npm:^1.0.0"
|
||||||
|
checksum: 10c0/2575cce9306ac534388db751f0aa3e78afedb6af8f3b529ac6b2354f66765545145dba8530abf7bff49fb399a047d3f9b6901c38ee4c9503f592960d9af67763
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"glob@npm:^10.2.2":
|
"glob@npm:^10.2.2":
|
||||||
version: 10.4.5
|
version: 10.4.5
|
||||||
resolution: "glob@npm:10.4.5"
|
resolution: "glob@npm:10.4.5"
|
||||||
|
@ -6333,7 +6491,17 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"inherits@npm:^2.0.1":
|
"inflight@npm:^1.0.4":
|
||||||
|
version: 1.0.6
|
||||||
|
resolution: "inflight@npm:1.0.6"
|
||||||
|
dependencies:
|
||||||
|
once: "npm:^1.3.0"
|
||||||
|
wrappy: "npm:1"
|
||||||
|
checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"inherits@npm:2, inherits@npm:^2.0.1":
|
||||||
version: 2.0.4
|
version: 2.0.4
|
||||||
resolution: "inherits@npm:2.0.4"
|
resolution: "inherits@npm:2.0.4"
|
||||||
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
|
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
|
||||||
|
@ -6701,7 +6869,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"isarray@npm:^2.0.5":
|
"isarray@npm:2.0.5, isarray@npm:^2.0.5":
|
||||||
version: 2.0.5
|
version: 2.0.5
|
||||||
resolution: "isarray@npm:2.0.5"
|
resolution: "isarray@npm:2.0.5"
|
||||||
checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd
|
checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd
|
||||||
|
@ -7157,6 +7325,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"lodash.get@npm:^4.4.2":
|
||||||
|
version: 4.4.2
|
||||||
|
resolution: "lodash.get@npm:4.4.2"
|
||||||
|
checksum: 10c0/48f40d471a1654397ed41685495acb31498d5ed696185ac8973daef424a749ca0c7871bf7b665d5c14f5cc479394479e0307e781f61d5573831769593411be6e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"lodash.isempty@npm:^4.4.0":
|
"lodash.isempty@npm:^4.4.0":
|
||||||
version: 4.4.0
|
version: 4.4.0
|
||||||
resolution: "lodash.isempty@npm:4.4.0"
|
resolution: "lodash.isempty@npm:4.4.0"
|
||||||
|
@ -7164,6 +7339,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"lodash.isequal@npm:^4.5.0":
|
||||||
|
version: 4.5.0
|
||||||
|
resolution: "lodash.isequal@npm:4.5.0"
|
||||||
|
checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"lodash.merge@npm:^4.6.2":
|
"lodash.merge@npm:^4.6.2":
|
||||||
version: 4.6.2
|
version: 4.6.2
|
||||||
resolution: "lodash.merge@npm:4.6.2"
|
resolution: "lodash.merge@npm:4.6.2"
|
||||||
|
@ -7171,6 +7353,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"lodash.mergewith@npm:^4.6.2":
|
||||||
|
version: 4.6.2
|
||||||
|
resolution: "lodash.mergewith@npm:4.6.2"
|
||||||
|
checksum: 10c0/4adbed65ff96fd65b0b3861f6899f98304f90fd71e7f1eb36c1270e05d500ee7f5ec44c02ef979b5ddbf75c0a0b9b99c35f0ad58f4011934c4d4e99e5200b3b5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"lodash.omitby@npm:^4.6.0":
|
"lodash.omitby@npm:^4.6.0":
|
||||||
version: 4.6.0
|
version: 4.6.0
|
||||||
resolution: "lodash.omitby@npm:4.6.0"
|
resolution: "lodash.omitby@npm:4.6.0"
|
||||||
|
@ -7385,7 +7574,8 @@ __metadata:
|
||||||
"@radix-ui/react-dropdown-menu": "npm:^2.1.15"
|
"@radix-ui/react-dropdown-menu": "npm:^2.1.15"
|
||||||
"@radix-ui/react-hover-card": "npm:^1.1.13"
|
"@radix-ui/react-hover-card": "npm:^1.1.13"
|
||||||
"@radix-ui/react-label": "npm:^2.1.6"
|
"@radix-ui/react-label": "npm:^2.1.6"
|
||||||
"@radix-ui/react-scroll-area": "npm:^1.2.8"
|
"@radix-ui/react-popover": "npm:^1.1.14"
|
||||||
|
"@radix-ui/react-scroll-area": "npm:^1.2.9"
|
||||||
"@radix-ui/react-select": "npm:^2.2.4"
|
"@radix-ui/react-select": "npm:^2.2.4"
|
||||||
"@radix-ui/react-separator": "npm:^1.1.7"
|
"@radix-ui/react-separator": "npm:^1.1.7"
|
||||||
"@radix-ui/react-slot": "npm:^1.2.3"
|
"@radix-ui/react-slot": "npm:^1.2.3"
|
||||||
|
@ -7402,7 +7592,9 @@ __metadata:
|
||||||
bcryptjs: "npm:^3.0.2"
|
bcryptjs: "npm:^3.0.2"
|
||||||
class-variance-authority: "npm:^0.7.1"
|
class-variance-authority: "npm:^0.7.1"
|
||||||
clsx: "npm:^2.1.1"
|
clsx: "npm:^2.1.1"
|
||||||
|
cmdk: "npm:^1.1.1"
|
||||||
cypress: "npm:14.5.0"
|
cypress: "npm:14.5.0"
|
||||||
|
date-fns: "npm:^4.1.0"
|
||||||
dotenv-cli: "npm:8.0.0"
|
dotenv-cli: "npm:8.0.0"
|
||||||
eslint: "npm:9.29.0"
|
eslint: "npm:9.29.0"
|
||||||
eslint-config-next: "npm:15.3.4"
|
eslint-config-next: "npm:15.3.4"
|
||||||
|
@ -7410,21 +7602,24 @@ __metadata:
|
||||||
lucide-react: "npm:^0.515.0"
|
lucide-react: "npm:^0.515.0"
|
||||||
next: "npm:15.3.4"
|
next: "npm:15.3.4"
|
||||||
next-auth: "npm:^5.0.0-beta.25"
|
next-auth: "npm:^5.0.0-beta.25"
|
||||||
|
next-swagger-doc: "npm:^0.4.1"
|
||||||
next-themes: "npm:^0.4.6"
|
next-themes: "npm:^0.4.6"
|
||||||
orval: "npm:7.10.0"
|
orval: "npm:7.10.0"
|
||||||
postcss: "npm:8.5.6"
|
postcss: "npm:8.5.6"
|
||||||
prettier: "npm:3.5.3"
|
prettier: "npm:3.5.3"
|
||||||
prisma: "npm:6.10.1"
|
prisma: "npm:6.10.1"
|
||||||
react: "npm:^19.0.0"
|
react: "npm:^19.0.0"
|
||||||
|
react-day-picker: "npm:^9.7.0"
|
||||||
react-dom: "npm:^19.0.0"
|
react-dom: "npm:^19.0.0"
|
||||||
react-hook-form: "npm:^7.56.4"
|
react-hook-form: "npm:^7.56.4"
|
||||||
|
sonner: "npm:^2.0.5"
|
||||||
swagger-ui-react: "npm:^5.24.1"
|
swagger-ui-react: "npm:^5.24.1"
|
||||||
tailwind-merge: "npm:^3.2.0"
|
tailwind-merge: "npm:^3.2.0"
|
||||||
tailwindcss: "npm:4.1.10"
|
tailwindcss: "npm:4.1.10"
|
||||||
ts-node: "npm:10.9.2"
|
ts-node: "npm:10.9.2"
|
||||||
tsconfig-paths: "npm:4.2.0"
|
tsconfig-paths: "npm:4.2.0"
|
||||||
tw-animate-css: "npm:1.3.4"
|
tw-animate-css: "npm:1.3.4"
|
||||||
typescript: "npm:5.8.3"
|
typescript: "npm:^5.8.3"
|
||||||
zod: "npm:^3.25.60"
|
zod: "npm:^3.25.60"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
@ -7485,7 +7680,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"minimatch@npm:3.1.2, minimatch@npm:^3.1.2":
|
"minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.2":
|
||||||
version: 3.1.2
|
version: 3.1.2
|
||||||
resolution: "minimatch@npm:3.1.2"
|
resolution: "minimatch@npm:3.1.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -7681,6 +7876,22 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"next-swagger-doc@npm:^0.4.1":
|
||||||
|
version: 0.4.1
|
||||||
|
resolution: "next-swagger-doc@npm:0.4.1"
|
||||||
|
dependencies:
|
||||||
|
"@types/swagger-jsdoc": "npm:6.0.4"
|
||||||
|
cleye: "npm:1.3.2"
|
||||||
|
isarray: "npm:2.0.5"
|
||||||
|
swagger-jsdoc: "npm:6.2.8"
|
||||||
|
peerDependencies:
|
||||||
|
next: ">=9"
|
||||||
|
bin:
|
||||||
|
next-swagger-doc-cli: dist/cli.js
|
||||||
|
checksum: 10c0/8ff0d33aad41296eb88f03243a627f9f0db528ea6889e528bbdbf66e38499069bfb9b7c29d2787c49f90f0b2a86a9c3e3475218d5af7a13dd4b0a37bec959703
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"next-themes@npm:^0.4.6":
|
"next-themes@npm:^0.4.6":
|
||||||
version: 0.4.6
|
version: 0.4.6
|
||||||
resolution: "next-themes@npm:0.4.6"
|
resolution: "next-themes@npm:0.4.6"
|
||||||
|
@ -8034,7 +8245,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"once@npm:^1.3.1, once@npm:^1.4.0":
|
"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0":
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
resolution: "once@npm:1.4.0"
|
resolution: "once@npm:1.4.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -8236,6 +8447,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"path-is-absolute@npm:^1.0.0":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "path-is-absolute@npm:1.0.1"
|
||||||
|
checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"path-key@npm:^3.0.0, path-key@npm:^3.1.0":
|
"path-key@npm:^3.0.0, path-key@npm:^3.1.0":
|
||||||
version: 3.1.1
|
version: 3.1.1
|
||||||
resolution: "path-key@npm:3.1.1"
|
resolution: "path-key@npm:3.1.1"
|
||||||
|
@ -8567,6 +8785,19 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-day-picker@npm:^9.7.0":
|
||||||
|
version: 9.7.0
|
||||||
|
resolution: "react-day-picker@npm:9.7.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
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-debounce-input@npm:=3.3.0":
|
"react-debounce-input@npm:=3.3.0":
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
resolution: "react-debounce-input@npm:3.3.0"
|
resolution: "react-debounce-input@npm:3.3.0"
|
||||||
|
@ -9411,6 +9642,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"sonner@npm:^2.0.5":
|
||||||
|
version: 2.0.5
|
||||||
|
resolution: "sonner@npm:2.0.5"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
checksum: 10c0/38ec98e2f5d7e086825307f737a90bdc8639182d184e002719c2368bf3a9259c340f41afda731716d2b78c40e5e3aa9165058375be42f6a93bda0876b9b433ba
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.1":
|
"source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.1":
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
resolution: "source-map-js@npm:1.2.1"
|
resolution: "source-map-js@npm:1.2.1"
|
||||||
|
@ -9708,6 +9949,31 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"swagger-jsdoc@npm:6.2.8":
|
||||||
|
version: 6.2.8
|
||||||
|
resolution: "swagger-jsdoc@npm:6.2.8"
|
||||||
|
dependencies:
|
||||||
|
commander: "npm:6.2.0"
|
||||||
|
doctrine: "npm:3.0.0"
|
||||||
|
glob: "npm:7.1.6"
|
||||||
|
lodash.mergewith: "npm:^4.6.2"
|
||||||
|
swagger-parser: "npm:^10.0.3"
|
||||||
|
yaml: "npm:2.0.0-1"
|
||||||
|
bin:
|
||||||
|
swagger-jsdoc: bin/swagger-jsdoc.js
|
||||||
|
checksum: 10c0/7e20f08e8d90cc1e787cd82c096291cf12533359f89c70fbe4295a01f7c4734f2e82a03ba94027127bcd3da04b817abfe979f00d00ef0cd8283e449250a66215
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"swagger-parser@npm:^10.0.3":
|
||||||
|
version: 10.0.3
|
||||||
|
resolution: "swagger-parser@npm:10.0.3"
|
||||||
|
dependencies:
|
||||||
|
"@apidevtools/swagger-parser": "npm:10.0.3"
|
||||||
|
checksum: 10c0/d1a5c05f651f21a23508a36416071630b83e91dfffd52a6d44b06ca2cd1b86304c0dd2f4c04526c999b70062fa89bde3f5d54a1436626f4350590b6c6265a098
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"swagger-ui-react@npm:^5.24.1":
|
"swagger-ui-react@npm:^5.24.1":
|
||||||
version: 5.25.2
|
version: 5.25.2
|
||||||
resolution: "swagger-ui-react@npm:5.25.2"
|
resolution: "swagger-ui-react@npm:5.25.2"
|
||||||
|
@ -9810,6 +10076,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"terminal-columns@npm:^1.4.1":
|
||||||
|
version: 1.4.1
|
||||||
|
resolution: "terminal-columns@npm:1.4.1"
|
||||||
|
checksum: 10c0/e79135b0e9605d247ac26addf2afc0a4313ca4b70e6ff5b8571ab95c93703ab60db5b7b8a61a715e0ea5b6067434cfe1ae58f4392e01ad86c514b347a2a34e34
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"throttleit@npm:^1.0.0":
|
"throttleit@npm:^1.0.0":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "throttleit@npm:1.0.1"
|
resolution: "throttleit@npm:1.0.1"
|
||||||
|
@ -10103,6 +10376,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"type-flag@npm:^3.0.0":
|
||||||
|
version: 3.0.0
|
||||||
|
resolution: "type-flag@npm:3.0.0"
|
||||||
|
checksum: 10c0/b1015d4eb18cd85432fa3bcd0228149dd6893a9c1360a2ad619f79b72e19acfb648a932e2a9ccf2e2db0f0c4b86385bd886a3364e221afb116424e0ae6d1b1ba
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"typed-array-buffer@npm:^1.0.3":
|
"typed-array-buffer@npm:^1.0.3":
|
||||||
version: 1.0.3
|
version: 1.0.3
|
||||||
resolution: "typed-array-buffer@npm:1.0.3"
|
resolution: "typed-array-buffer@npm:1.0.3"
|
||||||
|
@ -10191,7 +10471,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"typescript@npm:5.8.3, typescript@npm:^5.6.3":
|
"typescript@npm:^5.6.3, typescript@npm:^5.8.3":
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
resolution: "typescript@npm:5.8.3"
|
resolution: "typescript@npm:5.8.3"
|
||||||
bin:
|
bin:
|
||||||
|
@ -10201,7 +10481,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.6.3#optional!builtin<compat/typescript>":
|
"typescript@patch:typescript@npm%3A^5.6.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.8.3#optional!builtin<compat/typescript>":
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5"
|
resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5"
|
||||||
bin:
|
bin:
|
||||||
|
@ -10439,7 +10719,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"validator@npm:^13.11.0":
|
"validator@npm:^13.11.0, validator@npm:^13.7.0":
|
||||||
version: 13.15.15
|
version: 13.15.15
|
||||||
resolution: "validator@npm:13.15.15"
|
resolution: "validator@npm:13.15.15"
|
||||||
checksum: 10c0/f5349d1fbb9cc36f9f6c5dab1880764ddad1d0d2b084e2a71e5964f7de1635d20e406611559df9a3db24828ce775cbee5e3b6dd52f0d555a61939ed7ea5990bd
|
checksum: 10c0/f5349d1fbb9cc36f9f6c5dab1880764ddad1d0d2b084e2a71e5964f7de1635d20e406611559df9a3db24828ce775cbee5e3b6dd52f0d555a61939ed7ea5990bd
|
||||||
|
@ -10662,6 +10942,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"yaml@npm:2.0.0-1":
|
||||||
|
version: 2.0.0-1
|
||||||
|
resolution: "yaml@npm:2.0.0-1"
|
||||||
|
checksum: 10c0/e76eba2fbae37cd3e5bff057184be7cdca849895149d2f5660386871a501d76d2e1ec5906c48269a9fe798f214df31d342675b37bcd9d09af7c12eb6fb46a740
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"yaml@npm:^1.10.0":
|
"yaml@npm:^1.10.0":
|
||||||
version: 1.10.2
|
version: 1.10.2
|
||||||
resolution: "yaml@npm:1.10.2"
|
resolution: "yaml@npm:1.10.2"
|
||||||
|
@ -10724,6 +11011,23 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"z-schema@npm:^5.0.1":
|
||||||
|
version: 5.0.5
|
||||||
|
resolution: "z-schema@npm:5.0.5"
|
||||||
|
dependencies:
|
||||||
|
commander: "npm:^9.4.1"
|
||||||
|
lodash.get: "npm:^4.4.2"
|
||||||
|
lodash.isequal: "npm:^4.5.0"
|
||||||
|
validator: "npm:^13.7.0"
|
||||||
|
dependenciesMeta:
|
||||||
|
commander:
|
||||||
|
optional: true
|
||||||
|
bin:
|
||||||
|
z-schema: bin/z-schema
|
||||||
|
checksum: 10c0/e4c812cfe6468c19b2a21d07d4ff8fb70359062d33400b45f89017eaa3efe9d51e85963f2b115eaaa99a16b451782249bf9b1fa8b31d35cc473e7becb3e44264
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"zenscroll@npm:^4.0.2":
|
"zenscroll@npm:^4.0.2":
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
resolution: "zenscroll@npm:4.0.2"
|
resolution: "zenscroll@npm:4.0.2"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue
verstehe nicht so ganz, warum das umbenannt wurde.
weil --base blockiert ist von irgendwas, weiß nicht mehr genau von was