From 3eb5b4ff1e9e4fb250f8ee2ed6141bdf554261a3 Mon Sep 17 00:00:00 2001 From: Micha Date: Fri, 20 Jun 2025 00:03:11 +0200 Subject: [PATCH] feat: enhance event management with participant selection and user search functionality --- src/app/event/[eventID]/page.tsx | 51 ++++++++---- src/app/layout.tsx | 19 +++-- src/components/buttons/redirect-button.tsx | 4 +- src/components/forms/event-form.tsx | 88 ++++++++++---------- src/components/misc/user-search.tsx | 96 ++++++++++++++++++++++ 5 files changed, 189 insertions(+), 69 deletions(-) create mode 100644 src/components/misc/user-search.tsx diff --git a/src/app/event/[eventID]/page.tsx b/src/app/event/[eventID]/page.tsx index f3db559..05df3aa 100644 --- a/src/app/event/[eventID]/page.tsx +++ b/src/app/event/[eventID]/page.tsx @@ -9,8 +9,11 @@ import { Label } from '@/components/ui/label'; import { 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'; export default function ShowEvent() { + const session = useSession(); const pathname = usePathname(); // Extract eventId from URL like /event/[eventId] @@ -61,20 +64,17 @@ export default function ShowEvent() {
-
-
-
+
+
+
-
-

{event.title || 'Untitled Event'}

-
-
- +
+

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

+
@@ -123,7 +123,7 @@ export default function ShowEvent() {
-
+
-
- {' '} - {/* TODO: add participants display */} +
+ {' '} +
+ {event.participants?.map((user) => ( + + ))} +
-
+
+
+ {session.data?.user?.id === event.organizer.id ? ( + + ) : null} +
+
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c37fc2e..c3097ba 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,7 @@ import type { Metadata } from 'next'; import './globals.css'; import { QueryProvider } from '@/components/query-provider'; import { Toaster } from '@/components/ui/sonner'; +import { SessionProvider } from 'next-auth/react'; export const metadata: Metadata = { title: 'MeetUp', @@ -51,14 +52,16 @@ export default function RootLayout({ - - {children} - + + + {children} + + diff --git a/src/components/buttons/redirect-button.tsx b/src/components/buttons/redirect-button.tsx index d4e358a..e67acc1 100644 --- a/src/components/buttons/redirect-button.tsx +++ b/src/components/buttons/redirect-button.tsx @@ -4,13 +4,15 @@ import Link from 'next/link'; export function RedirectButton({ redirectUrl, buttonText, + className, }: { redirectUrl: string; buttonText: string; + className?: string; }) { return ( - + ); } diff --git a/src/components/forms/event-form.tsx b/src/components/forms/event-form.tsx index 77fe8da..362196a 100644 --- a/src/components/forms/event-form.tsx +++ b/src/components/forms/event-form.tsx @@ -11,10 +11,16 @@ import { useGetApiEventEventID, usePatchApiEventEventID, } from '@/generated/api/event/event'; -import ParticipantListEntry from '@/components/custom-ui/participantListEntry'; 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'; + +interface User { + id: string; + name: string; +} interface EventFormProps { type: 'create' | 'edit'; @@ -45,13 +51,11 @@ const EventForm: React.FC = (props) => { const [startTime, setStartTime] = React.useState(''); const [endDate, setEndDate] = React.useState(undefined); const [endTime, setEndTime] = React.useState(''); + // State for participants - const [selectedParticipants, setSelectedParticipants] = React.useState<{ - [name: string]: boolean; - }>({ - 'Max Muster': false, - // Add more participants as needed - }); + const [selectedParticipants, setSelectedParticipants] = React.useState< + User[] + >([]); // State for form fields const [title, setTitle] = React.useState(''); @@ -75,6 +79,12 @@ const EventForm: React.FC = (props) => { } setLocation(event.location || ''); setDescription(event.description || ''); + setSelectedParticipants( + event.participants?.map((u) => ({ + id: u.user.id, + name: u.user.name, + })) || [], + ); } }, [event, props.type]); @@ -115,6 +125,7 @@ const EventForm: React.FC = (props) => { 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) { @@ -126,6 +137,7 @@ const EventForm: React.FC = (props) => { start_time: data.start_time, end_time: data.end_time, location: data.location, + participants: data.participants, }, }); console.log('Updating event'); @@ -173,12 +185,12 @@ const EventForm: React.FC = (props) => { return (
-
-
-
+
+
+
-
+
= (props) => {
- {/* TODO: add participants input */} - - setSelectedParticipants((prev) => ({ - ...prev, - ['Max Muster']: checked, - })) - } - > + + { + setSelectedParticipants((current) => + current.find((u) => u.id === user.id) + ? current + : [...current, user], + ); + }} + removeUserAction={(user) => { + setSelectedParticipants((current) => + current.filter((u) => u.id !== user.id), + ); + }} + /> +
+ {selectedParticipants.map((user) => ( + + ))} +
@@ -300,28 +322,6 @@ const EventForm: React.FC = (props) => { {isSuccess &&

Event created!

} {error &&

Error: {error.message}

}
- {/* Hidden inputs for formData */} - - - - - ); }; diff --git a/src/components/misc/user-search.tsx b/src/components/misc/user-search.tsx new file mode 100644 index 0000000..82b7c38 --- /dev/null +++ b/src/components/misc/user-search.tsx @@ -0,0 +1,96 @@ +'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'; + +interface User { + id: string; + name: string; +} + +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 ( + + + + + + + + + No users found. + + {searchUserData?.data.users?.map((user) => { + const isSelected = selectedUsers.some((u) => u.id === user.id); + + return ( + { + if (isSelected) { + removeUserAction(user); + } else { + addUserAction(user); + } + setOpen(false); + }} + > + + {user.name} + + ); + })} + + + + + + ); +}