diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
deleted file mode 100644
index 6a3c299..0000000
--- a/src/app/not-found.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import Link from 'next/link';
-import { Button } from '@/components/ui/button';
-
-export default function NotFound() {
- return (
-
-
-
-
404
-
Page Not Found
-
- Sorry, we couldn't find the page you're looking for. It
- might have been moved, deleted, or doesn't exist.
-
-
-
-
-
- Go Home
-
-
- Browse Events
-
-
-
-
- );
-}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index a86e576..9e7e5a6 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,6 +1,7 @@
-import { auth } from '@/auth';
import { redirect } from 'next/navigation';
+import { auth } from '@/auth';
+
export default async function Home() {
const session = await auth();
diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx
index a2c5b35..5381a3b 100644
--- a/src/app/settings/page.tsx
+++ b/src/app/settings/page.tsx
@@ -1,5 +1,482 @@
-import SettingsPage from '@/components/settings/settings-page';
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { Switch } from '@/components/ui/switch';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
-export default function Page() {
- return
;
+export default function SettingsPage() {
+ return (
+
+
+
+
+ Account
+ Notifications
+ Calendar
+ Privacy
+ Appearance
+
+
+
+
+
+
+ Account Settings
+
+ Manage your account details and preferences.
+
+
+
+
+ Display Name
+
+
+
+
Email Address
+
+
+ Email is managed by your SSO provider.
+
+
+
+
Profile Picture
+
+
+ Upload a new profile picture.
+
+
+
+ Timezone
+
+
+
+
+ Language
+
+
+
+
+
+ English
+ German
+
+
+
+
+
Delete Account
+
+ Permanently delete your account and all associated data.
+
+
+
+
+
+ Exit
+ Save Changes
+
+
+
+
+
+
+
+
+ Notification Preferences
+
+ Choose how you want to be notified.
+
+
+
+
+
+ Enable All Email Notifications
+
+
+
+
+
+
+ New Meeting Bookings
+
+
+
+
+
+ Meeting Confirmations/Cancellations
+
+
+
+
+
+ Meeting Reminders
+
+
+
+
+ Remind me before
+
+
+
+
+
+ 15 minutes
+ 30 minutes
+ 1 hour
+ 1 day
+
+
+
+
+
+ Friend Requests
+
+
+
+
+
+ Group Invitations/Updates
+
+
+
+
+
+
+
+ Exit
+ Save Changes
+
+
+
+
+
+
+
+
+ Calendar & Availability
+
+ Manage your calendar display, default availability, and iCal
+ integrations.
+
+
+
+
+
+ Display
+
+
+
+ Default Calendar View
+
+
+
+
+
+
+ Day
+ Week
+ Month
+
+
+
+
+ Week Starts On
+
+
+
+
+
+ Sunday
+ Monday
+
+
+
+
+
+ Show Weekends
+
+
+
+
+
+
+
+ Availability
+
+
+
Working Hours
+
+ Define your typical available hours (e.g.,
+ Monday-Friday, 9 AM - 5 PM).
+
+
+ Set Working Hours
+
+
+
+
+ Minimum Notice for Bookings
+
+
+ Min time before a booking can be made.
+
+
+
+
+
+
+
+ Booking Window (days in advance)
+
+
+ Max time in advance a booking can be made.
+
+
+
+
+
+
+
+ iCalendar Integration
+
+
+ Import iCal Feed URL
+
+
+ Add Feed
+
+
+
+ Export Your Calendar
+
+ Get iCal Export URL
+
+
+ Download .ics File
+
+
+
+
+
+
+ Exit
+ Save Changes
+
+
+
+
+
+
+
+
+ Sharing & Privacy
+
+ Control who can see your calendar and book time with you.
+
+
+
+
+
+ Default Calendar Visibility
+
+
+
+
+
+
+
+ Private (Only You)
+
+
+ Free/Busy for Friends
+
+
+ Full Details for Friends
+
+
+
+
+
+
+ Who Can See Your Full Calendar Details?
+
+
+ (Override for Default Visibility)
+
+
+ This setting will override the default visibility for
+ your calendar. You can set specific friends or groups to
+ see your full calendar details.
+
+
+
+
+
+
+
+ Only Me
+ My Friends
+
+ Specific Friends/Groups (manage separately)
+
+
+
+
+
+
+ Who Can Book Time With You?
+
+
+
+
+
+
+ No One
+ My Friends
+
+ Specific Friends/Groups (manage separately)
+
+
+
+
+
+
Blocked Users
+
+ Manage Blocked Users
+
+
+ Prevent specific users from seeing your calendar or
+ booking time.
+
+
+
+
+
+ Exit
+ Save Changes
+
+
+
+
+
+
+
+
+ Appearance
+
+ Customize the look and feel of the application.
+
+
+
+
+ Theme
+
+
+
+
+
+ Light
+ Dark
+ System Default
+
+
+
+
+ Date Format
+
+
+
+
+
+ DD/MM/YYYY
+ MM/DD/YYYY
+ YYYY-MM-DD
+
+
+
+
+ Time Format
+
+
+
+
+
+ 24-hour
+ 12-hour
+
+
+
+
+
+
+ Exit
+ Save Changes
+
+
+
+
+
+
+ );
}
diff --git a/src/auth.ts b/src/auth.ts
index 18b3b2d..4fa8b23 100644
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -1,22 +1,21 @@
+import { PrismaAdapter } from '@auth/prisma-adapter';
import NextAuth, { CredentialsSignin } from 'next-auth';
-
-import { Prisma } from '@/generated/prisma';
import type { Provider } from 'next-auth/providers';
-
-import Credentials from 'next-auth/providers/credentials';
import AuthentikProvider from 'next-auth/providers/authentik';
+import Credentials from 'next-auth/providers/credentials';
import DiscordProvider from 'next-auth/providers/discord';
import FacebookProvider from 'next-auth/providers/facebook';
import GithubProvider from 'next-auth/providers/github';
import GitlabProvider from 'next-auth/providers/gitlab';
import GoogleProvider from 'next-auth/providers/google';
import KeycloakProvider from 'next-auth/providers/keycloak';
-
-import { PrismaAdapter } from '@auth/prisma-adapter';
-import { prisma } from '@/prisma';
+import { ZodError } from 'zod/v4';
import { loginSchema } from '@/lib/auth/validation';
-import { ZodError } from 'zod/v4';
+
+import { Prisma } from '@/generated/prisma';
+
+import { prisma } from '@/prisma';
class InvalidLoginError extends CredentialsSignin {
constructor(code: string) {
@@ -95,27 +94,13 @@ const providers: Provider[] = [
}
},
}),
-
+ process.env.AUTH_AUTHENTIK_ID && AuthentikProvider,
process.env.AUTH_DISCORD_ID && DiscordProvider,
process.env.AUTH_FACEBOOK_ID && FacebookProvider,
process.env.AUTH_GITHUB_ID && GithubProvider,
process.env.AUTH_GITLAB_ID && GitlabProvider,
process.env.AUTH_GOOGLE_ID && GoogleProvider,
process.env.AUTH_KEYCLOAK_ID && KeycloakProvider,
-
- process.env.AUTH_AUTHENTIK_ID &&
- AuthentikProvider({
- profile(profile) {
- return {
- id: profile.sub,
- name: profile.preferred_username,
- first_name: profile.given_name.split(' ')[0] || '',
- last_name: profile.given_name.split(' ')[1] || '',
- email: profile.email,
- image: profile.picture,
- };
- },
- }),
].filter(Boolean) as Provider[];
export const providerMap = providers
diff --git a/src/components/buttons/icon-button.tsx b/src/components/buttons/icon-button.tsx
index 4b50e90..bc6b51e 100644
--- a/src/components/buttons/icon-button.tsx
+++ b/src/components/buttons/icon-button.tsx
@@ -1,20 +1,19 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+
import { Button } from '@/components/ui/button';
-import { LucideProps } from 'lucide-react';
-import React, { ForwardRefExoticComponent, RefAttributes } from 'react';
export function IconButton({
icon,
children,
...props
}: {
- icon?: ForwardRefExoticComponent<
- Omit
& RefAttributes
- >;
- children?: React.ReactNode;
+ icon: IconProp;
+ children: React.ReactNode;
} & React.ComponentProps) {
return (
- {icon && React.createElement(icon, { className: 'mr-2' })}
+
{children}
);
diff --git a/src/components/buttons/notification-button.tsx b/src/components/buttons/notification-button.tsx
index f41f325..f0d9fe4 100644
--- a/src/components/buttons/notification-button.tsx
+++ b/src/components/buttons/notification-button.tsx
@@ -1,10 +1,10 @@
+import { NDot, NotificationDot } from '@/components/misc/notification-dot';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import { NDot, NotificationDot } from '@/components/misc/notification-dot';
export function NotificationButton({
dotVariant,
diff --git a/src/components/buttons/redirect-button.tsx b/src/components/buttons/redirect-button.tsx
index e67acc1..e605640 100644
--- a/src/components/buttons/redirect-button.tsx
+++ b/src/components/buttons/redirect-button.tsx
@@ -1,6 +1,7 @@
-import { Button } from '@/components/ui/button';
import Link from 'next/link';
+import { Button } from '@/components/ui/button';
+
export function RedirectButton({
redirectUrl,
buttonText,
diff --git a/src/components/buttons/sso-login-button.tsx b/src/components/buttons/sso-login-button.tsx
index b5cde0f..e06fe34 100644
--- a/src/components/buttons/sso-login-button.tsx
+++ b/src/components/buttons/sso-login-button.tsx
@@ -1,6 +1,8 @@
-import { signIn } from '@/auth';
+import { faOpenid } from '@fortawesome/free-brands-svg-icons';
+
import { IconButton } from '@/components/buttons/icon-button';
-import { Fingerprint } from 'lucide-react';
+
+import { signIn } from '@/auth';
export default function SSOLogin({
provider,
@@ -22,7 +24,7 @@ export default function SSOLogin({
className='w-full'
type='submit'
variant='secondary'
- icon={Fingerprint}
+ icon={faOpenid}
{...props}
>
Login with {providerDisplayName}
diff --git a/src/components/calendar.tsx b/src/components/calendar.tsx
index d77b00a..a1ea3fc 100644
--- a/src/components/calendar.tsx
+++ b/src/components/calendar.tsx
@@ -1,23 +1,24 @@
'use client';
+import { QueryErrorResetBoundary } from '@tanstack/react-query';
+import moment from 'moment';
+import { useSession } from 'next-auth/react';
+import { useRouter } from 'next/navigation';
+import React from 'react';
import { Calendar as RBCalendar, momentLocalizer } from 'react-big-calendar';
import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop';
-import moment from 'moment';
-import '@/components/react-big-calendar.css';
import 'react-big-calendar/lib/addons/dragAndDrop/styles.css';
-import CustomToolbar from '@/components/custom-toolbar';
-import React from 'react';
-import { useRouter } from 'next/navigation';
-import { usePatchApiEventEventID } from '@/generated/api/event/event';
-import { useSession } from 'next-auth/react';
-import { UserCalendarSchemaItem } from '@/generated/api/meetup.schemas';
-import { QueryErrorResetBoundary } from '@tanstack/react-query';
import { ErrorBoundary } from 'react-error-boundary';
-import { Button } from '@/components/ui/button';
import { fromZodIssue } from 'zod-validation-error/v4';
import type { $ZodIssue } from 'zod/v4/core';
+
+import CustomToolbar from '@/components/custom-toolbar';
+import '@/components/react-big-calendar.css';
+import { Button } from '@/components/ui/button';
+
import { useGetApiCalendar } from '@/generated/api/calendar/calendar';
-import { usePatchApiBlockedSlotsSlotID } from '@/generated/api/blocked-slots/blocked-slots';
+import { usePatchApiEventEventID } from '@/generated/api/event/event';
+import { UserCalendarSchemaItem } from '@/generated/api/meetup.schemas';
moment.updateLocale('en', {
week: {
@@ -48,7 +49,6 @@ const DaDRBCalendar = withDragAndDrop<
end: Date;
type: UserCalendarSchemaItem['type'];
userId?: string;
- organizer?: string;
},
{
id: string;
@@ -192,13 +192,6 @@ function CalendarWithUserEvents({
},
},
});
- const { mutate: patchBlockedSlot } = usePatchApiBlockedSlotsSlotID({
- mutation: {
- throwOnError(error) {
- throw error.response?.data || 'Failed to update blocked slot';
- },
- },
- });
return (
{
- if (event.type === 'blocked_private') return;
- if (event.type === 'blocked_owned') {
- router.push(`/blocker/${event.id}`);
- return;
- }
- if (event.type === 'event') {
- router.push(`/events/${event.id}`);
- }
+ router.push(`/events/${event.id}`);
}}
onSelectSlot={(slotInfo) => {
router.push(
@@ -250,108 +235,56 @@ function CalendarWithUserEvents({
resourceTitleAccessor={(event) => event.title}
startAccessor={(event) => event.start}
endAccessor={(event) => event.end}
- selectable={sesstion.data?.user?.id === userId}
+ selectable={sesstion.data?.user?.id === userId && !additionalEvents}
onEventDrop={(event) => {
const { start, end, event: droppedEvent } = event;
- if (
- droppedEvent.type === 'blocked_private' ||
- (droppedEvent.organizer &&
- droppedEvent.organizer !== sesstion.data?.user?.id)
- )
- return;
+ if (droppedEvent.type === 'blocked_private') return;
const startISO = new Date(start).toISOString();
const endISO = new Date(end).toISOString();
- if (droppedEvent.type === 'blocked_owned') {
- patchBlockedSlot(
- {
- slotID: droppedEvent.id,
- data: {
- start_time: startISO,
- end_time: endISO,
- },
+ patchEvent(
+ {
+ eventID: droppedEvent.id,
+ data: {
+ start_time: startISO,
+ end_time: endISO,
},
- {
- onSuccess: () => {
- refetch();
- },
- onError: (error) => {
- console.error('Error updating blocked slot:', error);
- },
+ },
+ {
+ onSuccess: () => {
+ refetch();
},
- );
- return;
- } else if (droppedEvent.type === 'event') {
- patchEvent(
- {
- eventID: droppedEvent.id,
- data: {
- start_time: startISO,
- end_time: endISO,
- },
+ onError: (error) => {
+ console.error('Error updating event:', error);
},
- {
- onSuccess: () => {
- refetch();
- },
- onError: (error) => {
- console.error('Error updating event:', error);
- },
- },
- );
- }
+ },
+ );
}}
onEventResize={(event) => {
const { start, end, event: resizedEvent } = event;
- if (
- resizedEvent.type === 'blocked_private' ||
- (resizedEvent.organizer &&
- resizedEvent.organizer !== sesstion.data?.user?.id)
- )
- return;
+ if (resizedEvent.type === 'blocked_private') return;
const startISO = new Date(start).toISOString();
const endISO = new Date(end).toISOString();
if (startISO === endISO) {
console.warn('Start and end times are the same, skipping resize.');
return;
}
- if (resizedEvent.type === 'blocked_owned') {
- patchBlockedSlot(
- {
- slotID: resizedEvent.id,
- data: {
- start_time: startISO,
- end_time: endISO,
- },
+ patchEvent(
+ {
+ eventID: resizedEvent.id,
+ data: {
+ start_time: startISO,
+ end_time: endISO,
},
- {
- onSuccess: () => {
- refetch();
- },
- onError: (error) => {
- console.error('Error resizing blocked slot:', error);
- },
+ },
+ {
+ onSuccess: () => {
+ refetch();
},
- );
- return;
- } else if (resizedEvent.type === 'event') {
- patchEvent(
- {
- eventID: resizedEvent.id,
- data: {
- start_time: startISO,
- end_time: endISO,
- },
+ onError: (error) => {
+ console.error('Error resizing event:', error);
},
- {
- onSuccess: () => {
- refetch();
- },
- onError: (error) => {
- console.error('Error resizing event:', error);
- },
- },
- );
- }
+ },
+ );
}}
/>
);
diff --git a/src/components/custom-toolbar.tsx b/src/components/custom-toolbar.tsx
index 76e59ee..b4549b1 100644
--- a/src/components/custom-toolbar.tsx
+++ b/src/components/custom-toolbar.tsx
@@ -1,9 +1,11 @@
-import React, { useState, useEffect } from 'react';
-import './custom-toolbar.css';
-import { Button } from '@/components/ui/button';
+import React, { useEffect, useState } from 'react';
+import { NavigateAction } from 'react-big-calendar';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
-import { NavigateAction } from 'react-big-calendar';
+
+import { Button } from '@/components/ui/button';
+
+import './custom-toolbar.css';
interface CustomToolbarProps {
//Aktuell angezeigtes Datum
diff --git a/src/components/custom-ui/app-sidebar.tsx b/src/components/custom-ui/app-sidebar.tsx
index cbef9d6..c95d8af 100644
--- a/src/components/custom-ui/app-sidebar.tsx
+++ b/src/components/custom-ui/app-sidebar.tsx
@@ -1,6 +1,17 @@
'use client';
+import { ChevronDown } from 'lucide-react';
+import {
+ CalendarClock,
+ CalendarDays,
+ CalendarPlus,
+ Star,
+ User,
+ Users,
+} from 'lucide-react';
+import Link from 'next/link';
import React from 'react';
+
import {
Sidebar,
SidebarContent,
@@ -13,34 +24,20 @@ import {
SidebarMenuButton,
SidebarMenuItem,
} from '@/components/custom-ui/sidebar';
-
-import { CalendarMinus, CalendarMinus2, ChevronDown } from 'lucide-react';
+import Logo from '@/components/misc/logo';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
-import Logo from '@/components/misc/logo';
-
-import Link from 'next/link';
-
-import {
- Star,
- CalendarDays,
- //User,
- //Users,
- CalendarClock,
- CalendarPlus,
-} from 'lucide-react';
-
const items = [
{
title: 'Calendar',
url: '/home',
icon: CalendarDays,
},
- /*{
+ {
title: 'Friends',
url: '#',
icon: User,
@@ -49,17 +46,12 @@ const items = [
title: 'Groups',
url: '#',
icon: Users,
- },*/
+ },
{
title: 'Events',
url: '/events',
icon: CalendarClock,
},
- {
- title: 'Blockers',
- url: '/blocker',
- icon: CalendarMinus,
- },
];
export function AppSidebar() {
@@ -67,27 +59,25 @@ export function AppSidebar() {
<>
-
-
-
-
+
+
-
-
+
+
{' '}
Favorites
@@ -130,17 +120,6 @@ export function AppSidebar() {
-
-
-
-
- New Blocker
-
-
-
diff --git a/src/components/custom-ui/blocked-slot-list-entry.tsx b/src/components/custom-ui/blocked-slot-list-entry.tsx
deleted file mode 100644
index 9d1acdf..0000000
--- a/src/components/custom-ui/blocked-slot-list-entry.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-'use client';
-
-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 { BlockedSlotsSchema } from '@/app/api/blocked_slots/validation';
-
-type BlockedSlotListEntryProps = zod.output;
-
-export default function BlockedSlotListEntry(slot: BlockedSlotListEntryProps) {
- 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 (
-
-
-
-
-
-
-
-
{slot.reason}
-
-
-
-
- start
-
-
- {formatDate(slot.start_time)} {formatTime(slot.start_time)}
-
-
-
-
- end
-
-
- {formatDate(slot.end_time)} {formatTime(slot.end_time)}
-
-
-
-
-
-
- );
-}
diff --git a/src/components/custom-ui/event-list-entry.tsx b/src/components/custom-ui/event-list-entry.tsx
index b52d438..fbb94a9 100644
--- a/src/components/custom-ui/event-list-entry.tsx
+++ b/src/components/custom-ui/event-list-entry.tsx
@@ -1,20 +1,24 @@
'use client';
-import { Card } from '@/components/ui/card';
-import Logo from '@/components/misc/logo';
-import { Label } from '@/components/ui/label';
+import { useSession } from 'next-auth/react';
import Link from 'next/link';
import zod from 'zod/v4';
+
+import Logo from '@/components/misc/logo';
+import { Card } from '@/components/ui/card';
+import { Label } from '@/components/ui/label';
+
import { EventSchema } from '@/app/api/event/validation';
-import { useSession } from 'next-auth/react';
+
+import { usePatchApiEventEventIDParticipantUser } from '@/generated/api/event-participant/event-participant';
+
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
-} from '../ui/select';
-import { usePatchApiEventEventIDParticipantUser } from '@/generated/api/event-participant/event-participant';
+} from '@/components/ui/select';
type EventListEntryProps = zod.output;
@@ -43,10 +47,7 @@ export default function EventListEntry({
return (
-
+
diff --git a/src/components/custom-ui/labeled-input.tsx b/src/components/custom-ui/labeled-input.tsx
index 38a6c56..4746a31 100644
--- a/src/components/custom-ui/labeled-input.tsx
+++ b/src/components/custom-ui/labeled-input.tsx
@@ -1,64 +1,29 @@
import { Input, Textarea } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
-import React, { ForwardRefExoticComponent, RefAttributes } from 'react';
-import { Button } from '../ui/button';
-import { Eye, EyeOff, LucideProps } from 'lucide-react';
-import { cn } from '@/lib/utils';
export default function LabeledInput({
type,
label,
- subtext,
placeholder,
value,
- defaultValue,
name,
- icon,
variantSize = 'default',
autocomplete,
error,
- 'data-cy': dataCy,
...rest
}: {
+ type: 'text' | 'email' | 'password';
label: string;
- subtext?: string;
placeholder?: string;
value?: string;
name?: string;
- icon?: ForwardRefExoticComponent<
- Omit
& RefAttributes
- >;
variantSize?: 'default' | 'big' | 'textarea';
autocomplete?: string;
error?: string;
- 'data-cy'?: string;
} & React.InputHTMLAttributes) {
- const [passwordVisible, setPasswordVisible] = React.useState(false);
- const [inputValue, setInputValue] = React.useState(
- value || defaultValue || '',
- );
-
- React.useEffect(() => {
- if (value !== undefined) {
- setInputValue(value);
- }
- }, [value]);
-
- const handleInputChange = (e: React.ChangeEvent) => {
- setInputValue(e.target.value);
- if (rest.onChange) {
- rest.onChange(e);
- }
- };
-
return (
{label}
- {subtext && (
-
- {subtext}
-
- )}
{variantSize === 'textarea' ? (
) : (
-
-
- {icon && (
-
- {React.createElement(icon)}
-
- )}
- {type === 'password' && (
- setPasswordVisible((visible) => !visible)}
- >
- {passwordVisible ? : }
-
- )}
-
+
)}
{error &&
{error}
}
diff --git a/src/components/custom-ui/participant-list-entry.tsx b/src/components/custom-ui/participant-list-entry.tsx
index 6f21ee2..5b2c8bb 100644
--- a/src/components/custom-ui/participant-list-entry.tsx
+++ b/src/components/custom-ui/participant-list-entry.tsx
@@ -1,32 +1,21 @@
-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 Image from 'next/image';
+import React from 'react';
import zod from 'zod/v4';
+
import { ParticipantSchema } from '@/app/api/event/[eventID]/participant/validation';
-import { usePatchApiEventEventIDParticipantUser } from '@/generated/api/event-participant/event-participant';
-import { useSession } from 'next-auth/react';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '../ui/select';
type ParticipantListEntryProps = zod.output;
export default function ParticipantListEntry({
user,
status,
- eventID,
-}: ParticipantListEntryProps & { eventID?: string }) {
- const session = useSession();
+}: ParticipantListEntryProps) {
const { resolvedTheme } = useTheme();
const defaultImage =
resolvedTheme === 'dark' ? user_default_dark : user_default_light;
- const updateAttendance = usePatchApiEventEventIDParticipantUser();
const finalImageSrc = user.image ?? defaultImage;
@@ -34,38 +23,7 @@ export default function ParticipantListEntry({
{user.name}
- {user.id === session.data?.user?.id && eventID ? (
- {
- updateAttendance.mutate({
- eventID: eventID,
- user: session.data?.user?.id || '',
- data: {
- status: value as
- | 'ACCEPTED'
- | 'TENTATIVE'
- | 'DECLINED'
- | 'PENDING',
- },
- });
- }}
- >
-
-
-
-
- Attending
- Maybe Attending
- Not Attending
-
- Pending Response
-
-
-
- ) : (
- {status}
- )}
+ {status}
);
}
diff --git a/src/components/custom-ui/sidebar.tsx b/src/components/custom-ui/sidebar.tsx
index 11228cb..1e3cc82 100644
--- a/src/components/custom-ui/sidebar.tsx
+++ b/src/components/custom-ui/sidebar.tsx
@@ -1,12 +1,11 @@
'use client';
-import * as React from 'react';
-import { Slot } from '@radix-ui/react-slot';
-import { cva, VariantProps } from 'class-variance-authority';
-import { PanelLeftIcon } from 'lucide-react';
-
import { useIsMobile } from '@/hooks/use-mobile';
-import { cn } from '@/lib/utils';
+import { Slot } from '@radix-ui/react-slot';
+import { VariantProps, cva } from 'class-variance-authority';
+import { PanelLeftIcon } from 'lucide-react';
+import * as React from 'react';
+
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
@@ -25,6 +24,8 @@ import {
TooltipTrigger,
} from '@/components/ui/tooltip';
+import { cn } from '@/lib/utils';
+
const SIDEBAR_COOKIE_NAME = 'sidebar_state';
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = '16rem';
diff --git a/src/components/forms/blocked-slot-form.tsx b/src/components/forms/blocked-slot-form.tsx
deleted file mode 100644
index 52a119d..0000000
--- a/src/components/forms/blocked-slot-form.tsx
+++ /dev/null
@@ -1,285 +0,0 @@
-'use client';
-
-import useZodForm from '@/lib/hooks/useZodForm';
-import {
- updateBlockedSlotSchema,
- createBlockedSlotClientSchema,
-} from '@/app/api/blocked_slots/validation';
-import {
- useGetApiBlockedSlotsSlotID,
- usePatchApiBlockedSlotsSlotID,
- useDeleteApiBlockedSlotsSlotID,
- usePostApiBlockedSlots,
-} from '@/generated/api/blocked-slots/blocked-slots';
-import { useRouter } from 'next/navigation';
-import React from 'react';
-import LabeledInput from '../custom-ui/labeled-input';
-import { Button } from '../ui/button';
-import { Card, CardContent, CardHeader } from '../ui/card';
-import Logo from '../misc/logo';
-import { eventStartTimeSchema } from '@/app/api/event/validation';
-import zod from 'zod/v4';
-
-const dateForDateTimeInputValue = (date: Date) =>
- new Date(date.getTime() + new Date().getTimezoneOffset() * -60 * 1000)
- .toISOString()
- .slice(0, 19);
-
-export default function BlockedSlotForm({
- existingBlockedSlotId,
-}: {
- existingBlockedSlotId?: string;
-}) {
- const router = useRouter();
-
- const { data: existingBlockedSlot, isLoading: isLoadingExisting } =
- useGetApiBlockedSlotsSlotID(existingBlockedSlotId || '');
-
- const {
- register: registerCreate,
- handleSubmit: handleCreateSubmit,
- formState: formStateCreate,
- reset: resetCreate,
- } = useZodForm(createBlockedSlotClientSchema);
-
- const {
- register: registerUpdate,
- handleSubmit: handleUpdateSubmit,
- formState: formStateUpdate,
- reset: resetUpdate,
- setValue: setValueUpdate,
- } = useZodForm(
- updateBlockedSlotSchema.extend({
- start_time: eventStartTimeSchema.or(zod.iso.datetime({ local: true })),
- end_time: eventStartTimeSchema.or(zod.iso.datetime({ local: true })),
- }),
- );
-
- const { mutateAsync: updateBlockedSlot } = usePatchApiBlockedSlotsSlotID({
- mutation: {
- onSuccess: () => {
- resetUpdate();
- },
- },
- });
-
- const { mutateAsync: deleteBlockedSlot } = useDeleteApiBlockedSlotsSlotID({
- mutation: {
- onSuccess: () => {
- router.push('/blocker');
- },
- },
- });
-
- const { mutateAsync: createBlockedSlot } = usePostApiBlockedSlots({
- mutation: {
- onSuccess: () => {
- resetCreate();
- router.push('/blocker');
- },
- },
- });
-
- React.useEffect(() => {
- if (existingBlockedSlot?.data) {
- setValueUpdate(
- 'start_time',
- dateForDateTimeInputValue(
- new Date(existingBlockedSlot?.data.blocked_slot.start_time),
- ),
- );
- setValueUpdate(
- 'end_time',
- dateForDateTimeInputValue(
- new Date(existingBlockedSlot?.data.blocked_slot.end_time),
- ),
- );
- setValueUpdate(
- 'reason',
- existingBlockedSlot?.data.blocked_slot.reason || '',
- );
- }
- }, [
- existingBlockedSlot?.data,
- resetUpdate,
- setValueUpdate,
- isLoadingExisting,
- ]);
-
- const onUpdateSubmit = handleUpdateSubmit(async (data) => {
- await updateBlockedSlot(
- {
- data: {
- ...data,
- start_time: new Date(data.start_time).toISOString(),
- end_time: new Date(data.end_time).toISOString(),
- },
- slotID: existingBlockedSlotId || '',
- },
- {
- onSuccess: () => {
- router.back();
- },
- },
- );
- });
-
- const onDeleteSubmit = async () => {
- if (existingBlockedSlotId) {
- await deleteBlockedSlot({ slotID: existingBlockedSlotId });
- }
- };
-
- const onCreateSubmit = handleCreateSubmit(async (data) => {
- await createBlockedSlot({
- data: {
- ...data,
- start_time: new Date(data.start_time).toISOString(),
- end_time: new Date(data.end_time).toISOString(),
- },
- });
- });
-
- if (existingBlockedSlotId)
- return (
-
-
-
-
-
-
-
-
-
{'Update Blocker'}
-
-
-
-
-
-
-
-
-
- );
- else
- return (
-
-
-
-
-
-
-
-
-
{'Create Blocker'}
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/forms/event-form.tsx b/src/components/forms/event-form.tsx
index 788629c..5915ae8 100644
--- a/src/components/forms/event-form.tsx
+++ b/src/components/forms/event-form.tsx
@@ -1,26 +1,30 @@
'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 {
- usePostApiEvent,
- useGetApiEventEventID,
- usePatchApiEventEventID,
-} from '@/generated/api/event/event';
+
import { useRouter } from 'next/navigation';
+import { useSearchParams } from 'next/navigation';
+import React from 'react';
import { toast } from 'sonner';
+import zod from 'zod/v4';
+
+import Calendar from '@/components/calendar';
+import LabeledInput from '@/components/custom-ui/labeled-input';
+import Logo from '@/components/misc/logo';
import { ToastInner } from '@/components/misc/toast-inner';
import { UserSearchInput } from '@/components/misc/user-search';
-import ParticipantListEntry from '../custom-ui/participant-list-entry';
+import TimePicker from '@/components/time-picker';
+import { Button } from '@/components/ui/button';
+import { Label } from '@/components/ui/label';
-import { useSearchParams } from 'next/navigation';
-
-import zod from 'zod/v4';
import { PublicUserSchema } from '@/app/api/user/validation';
-import Calendar from '@/components/calendar';
+
+import {
+ useGetApiEventEventID,
+ usePatchApiEventEventID,
+ usePostApiEvent,
+} from '@/generated/api/event/event';
+import { useGetApiUserMe } from '@/generated/api/user/user';
+
+import ParticipantListEntry from '@/components/custom-ui/participant-list-entry';
import {
Dialog,
DialogContent,
@@ -29,8 +33,7 @@ import {
DialogHeader,
DialogTitle,
DialogTrigger,
-} from '../ui/dialog';
-import { useGetApiUserMe } from '@/generated/api/user/user';
+} from '@/components/ui/dialog';
type User = zod.output;
@@ -51,19 +54,17 @@ const EventForm: React.FC = (props) => {
const startFromUrl = searchParams.get('start');
const endFromUrl = searchParams.get('end');
- const {
- mutateAsync: createEvent,
- status,
- isSuccess,
- error,
- } = usePostApiEvent();
+ 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 { data, isLoading, isError } = useGetApiUserMe();
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(undefined);
const [startTime, setStartTime] = React.useState('');
@@ -84,24 +85,22 @@ const EventForm: React.FC = (props) => {
// Update state when event data loads
React.useEffect(() => {
- if (props.type === 'edit' && eventData?.data?.event) {
- setTitle(eventData?.data?.event.title || '');
+ if (props.type === 'edit' && event) {
+ setTitle(event.title || '');
// Parse start_time and end_time
- if (eventData?.data?.event.start_time) {
- const start = new Date(eventData?.data?.event.start_time);
+ if (event.start_time) {
+ const start = new Date(event.start_time);
setStartDate(start);
setStartTime(start.toTimeString().slice(0, 5)); // "HH:mm"
}
- if (eventData?.data?.event.end_time) {
- const end = new Date(eventData?.data?.event.end_time);
+ if (event.end_time) {
+ const end = new Date(event.end_time);
setEndDate(end);
setEndTime(end.toTimeString().slice(0, 5)); // "HH:mm"
}
- setLocation(eventData?.data?.event.location || '');
- setDescription(eventData?.data?.event.description || '');
- setSelectedParticipants(
- eventData?.data?.event.participants?.map((u) => u.user) || [],
- );
+ 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('');
@@ -112,7 +111,7 @@ const EventForm: React.FC = (props) => {
setEndDate(end);
setEndTime(end.toTimeString().slice(0, 5)); // "HH:mm"
}
- }, [eventData?.data?.event, props.type, startFromUrl, endFromUrl]);
+ }, [event, props.type, startFromUrl, endFromUrl]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
@@ -154,10 +153,8 @@ const EventForm: React.FC = (props) => {
participants: selectedParticipants.map((u) => u.id),
};
- let eventID: string | undefined;
-
if (props.type === 'edit' && props.eventId) {
- const mutationResult = await patchEvent.mutateAsync({
+ await patchEvent.mutateAsync({
eventID: props.eventId,
data: {
title: data.title,
@@ -168,20 +165,18 @@ const EventForm: React.FC = (props) => {
participants: data.participants,
},
});
- eventID = mutationResult.data.event.id;
console.log('Updating event');
} else {
console.log('Creating event');
- const mutationResult = await createEvent({ data });
- eventID = mutationResult.data.event.id;
+ createEvent({ data });
}
toast.custom((t) => (
router.push(`/events/${eventID}`)}
+ description={event?.title}
+ onAction={() => router.push(`/events/${event?.id}`)}
variant='success'
buttonText='show'
/>
@@ -190,216 +185,207 @@ const EventForm: React.FC = (props) => {
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' && eventData?.data?.event?.created_at
- ? eventData.data.event.created_at
+ props.type === 'edit' && event?.created_at
+ ? event.created_at
: new Date().toISOString();
const updatedAtValue =
- props.type === 'edit' && eventData?.data?.event?.updated_at
- ? eventData.data.event.updated_at
+ 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();
- const [isClient, setIsClient] = React.useState(false);
- React.useEffect(() => {
- setIsClient(true);
- }, []);
-
if (props.type === 'edit' && isLoading) return Loading...
;
- if (props.type === 'edit' && isError) return Error loading event.
;
+ if (props.type === 'edit' && fetchError)
+ return Error loading event.
;
return (
-
-