diff --git a/app/(app)/me/cancel-action.tsx b/app/(app)/me/cancel-action.tsx
new file mode 100644
index 0000000..23ec021
--- /dev/null
+++ b/app/(app)/me/cancel-action.tsx
@@ -0,0 +1,63 @@
+'use client'
+
+import { useState } from 'react'
+import { useRouter } from 'next/navigation'
+import { toast } from 'sonner'
+import { Button } from '@/components/ui/button'
+import {
+ AlertDialog,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+} from '@/components/ui/alert-dialog'
+
+// Confirm-then-run wrapper for a self-serve cancel. The action returns the
+// project-standard { error } | { data/success } shape.
+export function CancelAction({
+ title,
+ description,
+ action,
+ successMessage,
+}: {
+ title: string
+ description: string
+ action: () => Promise<{ error?: string } | Record>
+ successMessage: string
+}) {
+ const router = useRouter()
+ const [busy, setBusy] = useState(false)
+
+ async function run() {
+ setBusy(true)
+ const res = await action()
+ setBusy(false)
+ if (res && 'error' in res && res.error) return toast.error(res.error as string)
+ toast.success(successMessage)
+ router.refresh()
+ }
+
+ return (
+
+
+
+
+
+
+ {title}
+ {description}
+
+
+ Keep it
+ Cancel it
+
+
+
+ )
+}
diff --git a/app/(app)/me/me-portal-client.tsx b/app/(app)/me/me-portal-client.tsx
index 6e6fbea..ae9bff4 100644
--- a/app/(app)/me/me-portal-client.tsx
+++ b/app/(app)/me/me-portal-client.tsx
@@ -12,6 +12,8 @@ import { RESERVATION_STATUS_LABEL } from '@/lib/equipment-logic'
import { presenceStatus } from '@/lib/presence-logic'
import { DuesCard } from '@/components/billing/dues-card'
import { ProfileForm, type ProfileInitial } from './profile-form'
+import { CancelAction } from './cancel-action'
+import { cancelMySignup, cancelReservation } from '@/lib/actions'
const STATUS_VARIANT: Record = {
active: 'default',
@@ -35,8 +37,8 @@ export type ClassSignup = {
attended: boolean
signed_up_at: string
class_sessions:
- | { starts_at: string; ends_at: string | null; location: string | null; status: string; classes: { title: string } | { title: string }[] | null }
- | { starts_at: string; ends_at: string | null; location: string | null; status: string; classes: { title: string } | { title: string }[] | null }[]
+ | { id: string; starts_at: string; ends_at: string | null; location: string | null; status: string; classes: { title: string } | { title: string }[] | null }
+ | { id: string; starts_at: string; ends_at: string | null; location: string | null; status: string; classes: { title: string } | { title: string }[] | null }[]
| null
}
export type Reservation = {
@@ -235,6 +237,14 @@ export function MePortalClient({
)}
+ {sess && (cs.status === 'registered' || cs.status === 'waitlisted') && (
+ cancelMySignup({ sessionId: sess.id })}
+ />
+ )}
)
})}
@@ -268,6 +278,14 @@ export function MePortalClient({
{r.notes && {r.notes}
}
+ {r.status === 'reserved' && (
+ cancelReservation({ reservationId: r.id })}
+ />
+ )}
)
})}