diff --git a/package.json b/package.json
index 71bc8ea..e18c3c4 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.13",
"@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-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
@@ -48,13 +49,18 @@
"bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
+ "date-fns": "^4.1.0",
"lucide-react": "^0.515.0",
"next": "15.3.4",
"next-auth": "^5.0.0-beta.25",
+ "next-swagger-doc": "^0.4.1",
"next-themes": "^0.4.6",
"react": "^19.0.0",
+ "react-day-picker": "^9.7.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.56.4",
+ "sonner": "^2.0.5",
"swagger-ui-react": "^5.24.1",
"tailwind-merge": "^3.2.0",
"zod": "^3.25.60"
@@ -80,7 +86,7 @@
"ts-node": "10.9.2",
"tsconfig-paths": "4.2.0",
"tw-animate-css": "1.3.4",
- "typescript": "5.8.3"
+ "typescript": "^5.8.3"
},
"packageManager": "yarn@4.9.2"
}
diff --git a/src/app/(main)/events/[eventID]/page.tsx b/src/app/(main)/events/[eventID]/page.tsx
new file mode 100644
index 0000000..bfc390d
--- /dev/null
+++ b/src/app/(main)/events/[eventID]/page.tsx
@@ -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 (
+
+ Loading...
+
+ );
+ }
+ if (error || !eventData?.data?.event) {
+ return (
+
+ Error loading event.
+
+ );
+ }
+
+ 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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {event.title || 'Untitled Event'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{' '}
+
+ {event.participants?.map((user) => (
+
+ ))}
+
+
+
+
+
+
+ {session.data?.user?.id === event.organizer.id ? (
+
+ ) : null}
+
+
+ {session.data?.user?.id === event.organizer.id ? (
+
+ ) : null}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/events/edit/[eventID]/page.tsx b/src/app/(main)/events/edit/[eventID]/page.tsx
new file mode 100644
index 0000000..42c6e8b
--- /dev/null
+++ b/src/app/(main)/events/edit/[eventID]/page.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/events/new/page.tsx b/src/app/(main)/events/new/page.tsx
new file mode 100644
index 0000000..2db7ae2
--- /dev/null
+++ b/src/app/(main)/events/new/page.tsx
@@ -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 (
+
+ );
+}
diff --git a/src/app/(main)/events/page.tsx b/src/app/(main)/events/page.tsx
new file mode 100644
index 0000000..f0391dd
--- /dev/null
+++ b/src/app/(main)/events/page.tsx
@@ -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 Loading...
;
+ if (error)
+ return (
+ Error loading events
+ );
+
+ const events = eventsData?.data?.events || [];
+
+ return (
+
+ {/* Heading */}
+
+ My Events
+
+
+ {/* Scrollable event list */}
+
+
+ {events.length > 0 ? (
+ events.map((event) => (
+
+ ))
+ ) : (
+
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/src/app/(main)/home/page.tsx b/src/app/(main)/home/page.tsx
index c381c03..66a97d8 100644
--- a/src/app/(main)/home/page.tsx
+++ b/src/app/(main)/home/page.tsx
@@ -15,6 +15,7 @@ export default function Home() {
+
);
diff --git a/src/app/globals.css b/src/app/globals.css
index 93a24ce..bc18178 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -9,6 +9,7 @@
--font-heading: 'Comfortaa', sans-serif;
--font-label: 'Varela Round', sans-serif;
--font-button: 'Varela Round', sans-serif;
+ --font-sans: var(--font-label);
--transparent: transparent;
@@ -28,7 +29,7 @@
--background: var(--neutral-800);
--background-reversed: var(--neutral-000);
- --base: var(--neutral-800);
+ --basecl: var(--neutral-800);
--text: var(--neutral-000);
--text-alt: var(--neutral-900);
--text-input: var(--text);
@@ -49,11 +50,23 @@
--active-secondary: oklch(0.4254 0.133 272.15);
--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);
--hover-muted: var(--color-neutral-600);
--active-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);
--sidebar-width-icon: 32px;
@@ -80,8 +93,6 @@
--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);
--input: oklch(0.928 0.006 264.531);
@@ -115,6 +126,62 @@
--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-family: 'Comfortaa';
font-style: normal;
@@ -153,7 +220,7 @@
--color-background: var(--neutral-750);
--color-background-reversed: var(--background-reversed);
- --color-base: var(--neutral-800);
+ --color-basecl: var(--neutral-800);
--color-text: var(--text);
--color-text-alt: var(--text-alt);
--color-text-input: var(--text-input);
@@ -175,11 +242,23 @@
--color-active-secondary: var(--active-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-hover-muted: var(--hover-muted);
--color-active-muted: var(--active-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 */
--radius-sm: calc(var(--radius) - 4px);
@@ -220,8 +299,6 @@
--color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
-
--color-border: var(--border);
--color-input: var(--input);
@@ -277,7 +354,7 @@
--background: var(--neutral-750);
--background-reversed: var(--neutral-000);
- --base: var(--neutral-750);
+ --basecl: var(--neutral-750);
--text: var(--neutral-000);
--text-alt: var(--neutral-900);
--text-input: var(--text);
@@ -297,11 +374,23 @@
--active-secondary: oklch(0.4471 0.15 271.61);
--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);
--hover-muted: var(--color-neutral-500);
--active-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);
/* ------------------- */
@@ -326,8 +415,6 @@
--accent-foreground: oklch(0.985 0.002 247.839);
- --destructive: oklch(0.704 0.191 22.216);
-
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index af40867..47cec2d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -3,6 +3,8 @@ import { ThemeProvider } from '@/components/wrappers/theme-provider';
import type { Metadata } from 'next';
import './globals.css';
import { QueryProvider } from '@/components/wrappers/query-provider';
+import { Toaster } from '@/components/ui/sonner';
+import { SessionProvider } from 'next-auth/react';
export const metadata: Metadata = {
title: 'MeetUp',
@@ -50,14 +52,17 @@ export default function RootLayout({
-
- {children}
-
+
+
+ {children}
+
+
+