feat: invitation deletion
Deploy On Server / Prepare Repository Remotely (push) Successful in 10s
Deploy On Server / Docker Compose Up (push) Successful in 18s
Deploy On Server / Prune Images (push) Successful in 3s
Deploy On Server / Remove .env File (push) Successful in 2s

This commit is contained in:
Raul Lugo
2026-08-11 22:18:25 +02:00
parent d4c542b93b
commit aed8684e09
7 changed files with 377 additions and 2 deletions
+64
View File
@@ -0,0 +1,64 @@
"use server"
import { revalidatePath } from "next/cache"
import { redirect } from "next/navigation"
import { auth } from "@/auth"
import { isAdminEmail } from "@/lib/admin-auth"
import { deleteInvitationGroup } from "@/lib/db/queries/invitations"
import { deleteUploadObject } from "@/lib/storage/uploads"
import { deleteInvitationSchema } from "@/lib/validation/invitation"
export interface DeleteInvitationActionState {
error: string | null
}
export async function deleteInvitationAction(
_previousState: DeleteInvitationActionState,
formData: FormData,
): Promise<DeleteInvitationActionState> {
const session = await auth()
const adminEmail = session?.user?.email
if (!isAdminEmail(adminEmail)) {
return { error: "You are not allowed to delete invitations." }
}
const authorizedAdminEmail = adminEmail ?? ""
const parsed = deleteInvitationSchema.safeParse({
invitationId: formData.get("invitationId"),
})
if (!parsed.success) {
return { error: "Could not delete this guest group." }
}
let deleted: Awaited<ReturnType<typeof deleteInvitationGroup>>
try {
deleted = await deleteInvitationGroup(parsed.data.invitationId, authorizedAdminEmail)
} catch (error) {
console.error("Failed to delete invitation group", error)
return { error: "Could not delete this guest group. Please try again." }
}
if (!deleted) {
return { error: "This guest group was already removed." }
}
if (deleted.photoObjectKey) {
try {
await deleteUploadObject(deleted.photoObjectKey)
} catch (error) {
console.error("Failed to delete uploaded photo object", {
invitationId: deleted.id,
objectKey: deleted.photoObjectKey,
error,
})
}
}
revalidatePath("/admin/guests")
redirect("/admin/guests?deleted=1")
}
+6 -1
View File
@@ -2,6 +2,7 @@ import Link from "next/link"
import { notFound } from "next/navigation"
import { CopyInviteButton } from "@/components/admin/copy-invite-button"
import { DeleteInvitationDialog } from "@/components/admin/delete-invitation-dialog"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
@@ -104,11 +105,15 @@ export default async function GuestDetailPage({
</h1>
<p className="text-muted-foreground">Invitation details and member list.</p>
</div>
<div className="flex gap-2">
<div className="flex flex-wrap gap-2">
<Button asChild variant="outline">
<Link href="/admin/guests">Back to list</Link>
</Button>
<CopyInviteButton inviteUrl={inviteUrl} />
<DeleteInvitationDialog
invitationId={detail.invitation.id}
displayName={detail.invitation.display_name}
/>
</div>
</div>