feat(events): add deletion button and style toaster for light mode
All checks were successful
container-scan / Container Scan (pull_request) Successful in 11m20s
docker-build / docker (pull_request) Successful in 11m30s
tests / Tests (pull_request) Successful in 8m39s

This commit is contained in:
micha 2025-06-26 12:56:53 +02:00
parent 8bbb7e4c85
commit 42e1b69720
7 changed files with 128 additions and 32 deletions

View file

@ -1,25 +1,42 @@
'use client'; 'use client';
import React from 'react'; import React, { useState } from 'react';
import Logo from '@/components/misc/logo'; import Logo from '@/components/misc/logo';
import { ThemePicker } from '@/components/misc/theme-picker';
import { Card, CardContent, CardHeader } from '@/components/ui/card'; import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useGetApiEventEventID } from '@/generated/api/event/event'; import {
useDeleteApiEventEventID,
useGetApiEventEventID,
} from '@/generated/api/event/event';
import { useGetApiUserMe } from '@/generated/api/user/user'; import { useGetApiUserMe } from '@/generated/api/user/user';
import { RedirectButton } from '@/components/buttons/redirect-button'; import { RedirectButton } from '@/components/buttons/redirect-button';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import ParticipantListEntry from '@/components/custom-ui/participant-list-entry'; import ParticipantListEntry from '@/components/custom-ui/participant-list-entry';
import { useParams } from 'next/navigation'; 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() { export default function ShowEvent() {
const session = useSession(); const session = useSession();
const router = useRouter();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const { eventId } = useParams<{ eventId: string }>(); const { eventID: eventID } = useParams<{ eventID: string }>();
// Fetch event data // Fetch event data
const { data: eventData, isLoading, error } = useGetApiEventEventID(eventId); const { data: eventData, isLoading, error } = useGetApiEventEventID(eventID);
const { data: userData, isLoading: userLoading } = useGetApiUserMe(); const { data: userData, isLoading: userLoading } = useGetApiUserMe();
const deleteEvent = useDeleteApiEventEventID();
if (isLoading || userLoading) { if (isLoading || userLoading) {
return ( return (
@ -54,9 +71,6 @@ export default function ShowEvent() {
return ( return (
<div className='flex flex-col items-center justify-center h-screen'> <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'> <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' /> <CardHeader className='p-0 m-0 gap-0' />
@ -150,10 +164,65 @@ export default function ShowEvent() {
</div> </div>
<div className='flex flex-row gap-2 justify-end mt-4 mb-6'> <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 &ldquo;
{event.title}&rdquo;? 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'> <div className='w-[20%] grid max-sm:w-full'>
{session.data?.user?.id === event.organizer.id ? ( {session.data?.user?.id === event.organizer.id ? (
<RedirectButton <RedirectButton
redirectUrl={`/events/edit/${eventId}`} redirectUrl={`/events/edit/${eventID}`}
buttonText='edit' buttonText='edit'
className='w-full' className='w-full'
/> />

View file

@ -1,4 +1,3 @@
import { ThemePicker } from '@/components/misc/theme-picker';
import { Card, CardContent, CardHeader } from '@/components/ui/card'; import { Card, CardContent, CardHeader } from '@/components/ui/card';
import EventForm from '@/components/forms/event-form'; import EventForm from '@/components/forms/event-form';
import { Suspense } from 'react'; import { Suspense } from 'react';
@ -11,7 +10,6 @@ export default async function Page({
const eventID = (await params).eventID; const eventID = (await params).eventID;
return ( return (
<div className='flex flex-col items-center justify-center h-screen'> <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'> <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' /> <CardHeader className='p-0 m-0 gap-0' />

View file

@ -2,7 +2,6 @@
import { RedirectButton } from '@/components/buttons/redirect-button'; import { RedirectButton } from '@/components/buttons/redirect-button';
import EventListEntry from '@/components/custom-ui/event-list-entry'; import EventListEntry from '@/components/custom-ui/event-list-entry';
import { ThemePicker } from '@/components/misc/theme-picker';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useGetApiEvent } from '@/generated/api/event/event'; import { useGetApiEvent } from '@/generated/api/event/event';
@ -19,10 +18,6 @@ export default function Events() {
return ( return (
<div className='relative h-screen flex flex-col items-center'> <div className='relative h-screen flex flex-col items-center'>
<div className='absolute top-4 right-4'>
<ThemePicker />
</div>
{/* Heading */} {/* Heading */}
<h1 className='text-3xl font-bold mt-8 mb-4 text-center z-10'> <h1 className='text-3xl font-bold mt-8 mb-4 text-center z-10'>
My Events My Events

View file

@ -50,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;
@ -81,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);
@ -232,11 +242,23 @@ p {
--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);
@ -277,8 +299,6 @@ p {
--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);
@ -354,11 +374,23 @@ p {
--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);
/* ------------------- */ /* ------------------- */
@ -383,8 +415,6 @@ p {
--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%);

View file

@ -65,27 +65,27 @@ interface ToastInnerProps {
const variantConfig = { const variantConfig = {
default: { default: {
bgColor: 'bg-neutral-150', bgColor: 'bg-toaster-default-bg',
defaultIcon: 'Info', defaultIcon: 'Info',
}, },
success: { success: {
bgColor: 'bg-green-200', bgColor: 'bg-toaster-success-bg',
defaultIcon: 'CheckCircle', defaultIcon: 'CheckCircle',
}, },
error: { error: {
bgColor: 'bg-red-200', bgColor: 'bg-toaster-error-bg',
defaultIcon: 'XCircle', defaultIcon: 'XCircle',
}, },
info: { info: {
bgColor: 'bg-blue-200', bgColor: 'bg-toaster-info-bg',
defaultIcon: 'Info', defaultIcon: 'Info',
}, },
warning: { warning: {
bgColor: 'bg-yellow-200', bgColor: 'bg-toaster-warning-bg',
defaultIcon: 'AlertTriangle', defaultIcon: 'AlertTriangle',
}, },
notification: { notification: {
bgColor: 'bg-neutral-150', bgColor: 'bg-toaster-notification-bg',
defaultIcon: 'BellRing', defaultIcon: 'BellRing',
}, },
}; };
@ -127,14 +127,16 @@ export const ToastInner: React.FC<ToastInnerProps> = ({
> >
{variant !== 'default' && ( {variant !== 'default' && (
<div className='flex items-center justify-center'> <div className='flex items-center justify-center'>
<Icon size={40} /> <Icon className='text-text-alt' size={40} />
</div> </div>
)} )}
{/* Text Content */} {/* Text Content */}
<div className='grid gap-1'> <div className='grid gap-1'>
<h6>{title}</h6> <h6 className='text-text-alt'>{title}</h6>
{description && <Label>{description}</Label>} {description && (
<Label className='text-text-alt'>{description}</Label>
)}
</div> </div>
{/* Action Button */} {/* Action Button */}

View file

@ -26,6 +26,8 @@ const buttonVariants = cva(
'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', '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: ghost:
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', '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',