Contact email
{detail.invitation.contact_email ?? "—"}
diff --git a/app/admin/guests/actions.ts b/app/admin/guests/actions.ts
index ead413c..58dc2de 100644
--- a/app/admin/guests/actions.ts
+++ b/app/admin/guests/actions.ts
@@ -4,14 +4,25 @@ import { revalidatePath } from "next/cache"
import { auth } from "@/auth"
import { isAdminEmail } from "@/lib/admin-auth"
-import { updateSaveTheDateSent } from "@/lib/db/queries/invitations"
-import { updateSaveTheDateSentSchema } from "@/lib/validation/invitation"
+import {
+ updateInvitationLanguage,
+ updateSaveTheDateSent,
+} from "@/lib/db/queries/invitations"
+import {
+ updateInvitationLanguageSchema,
+ updateSaveTheDateSentSchema,
+} from "@/lib/validation/invitation"
export interface UpdateSaveTheDateSentResult {
ok: boolean
error: string | null
}
+export interface UpdateInvitationLanguageResult {
+ ok: boolean
+ error: string | null
+}
+
export async function updateSaveTheDateSentAction(input: {
invitationId: string
sent: boolean
@@ -49,3 +60,42 @@ export async function updateSaveTheDateSentAction(input: {
return { ok: true, error: null }
}
+
+export async function updateInvitationLanguageAction(input: {
+ invitationId: string
+ language: string
+}): Promise
{
+ const session = await auth()
+ const adminEmail = session?.user?.email
+
+ if (!isAdminEmail(adminEmail)) {
+ return { ok: false, error: "You are not allowed to update invitations." }
+ }
+
+ const parsed = updateInvitationLanguageSchema.safeParse(input)
+
+ if (!parsed.success) {
+ return { ok: false, error: "Select a supported language." }
+ }
+
+ try {
+ const updated = await updateInvitationLanguage({
+ invitationId: parsed.data.invitationId,
+ language: parsed.data.language,
+ adminEmail: adminEmail ?? "",
+ })
+
+ if (!updated) {
+ return { ok: false, error: "This guest group no longer exists." }
+ }
+ } catch (error) {
+ console.error("Failed to update invitation language", error)
+ return { ok: false, error: "Could not update the invitation language." }
+ }
+
+ revalidatePath(`/admin/guests/${parsed.data.invitationId}`)
+ revalidatePath("/admin/guests")
+ revalidatePath("/admin")
+
+ return { ok: true, error: null }
+}
diff --git a/components/admin/invitation-language-select.tsx b/components/admin/invitation-language-select.tsx
new file mode 100644
index 0000000..b59cf4b
--- /dev/null
+++ b/components/admin/invitation-language-select.tsx
@@ -0,0 +1,90 @@
+"use client"
+
+import { useRouter } from "next/navigation"
+import { useState } from "react"
+
+import { updateInvitationLanguageAction } from "@/app/admin/guests/actions"
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select"
+import { supportedLanguages } from "@/lib/i18n/config"
+
+interface InvitationLanguageSelectProps {
+ invitationId: string
+ currentLanguage: string
+ displayName: string
+}
+
+export function InvitationLanguageSelect({
+ invitationId,
+ currentLanguage,
+ displayName,
+}: InvitationLanguageSelectProps) {
+ const router = useRouter()
+ const [value, setValue] = useState(currentLanguage)
+ const [message, setMessage] = useState(null)
+ const [error, setError] = useState(null)
+ const [isSaving, setIsSaving] = useState(false)
+
+ async function updateLanguage(nextLanguage: string) {
+ if (nextLanguage === value) return
+
+ const previousLanguage = value
+
+ setValue(nextLanguage)
+ setMessage(null)
+ setError(null)
+ setIsSaving(true)
+
+ try {
+ const result = await updateInvitationLanguageAction({
+ invitationId,
+ language: nextLanguage,
+ })
+
+ if (!result.ok) {
+ setValue(previousLanguage)
+ setError(result.error)
+ return
+ }
+
+ setMessage("Language updated.")
+ router.refresh()
+ } catch {
+ setValue(previousLanguage)
+ setError("Could not update the invitation language.")
+ } finally {
+ setIsSaving(false)
+ }
+ }
+
+ return (
+
+
+ {message ?
{message}
: null}
+ {error ?
{error}
: null}
+
+ )
+}
diff --git a/lib/db/queries/invitations.ts b/lib/db/queries/invitations.ts
index 3e7b3db..3bed4c4 100644
--- a/lib/db/queries/invitations.ts
+++ b/lib/db/queries/invitations.ts
@@ -191,6 +191,55 @@ export async function updateSaveTheDateSent({
})
}
+export async function updateInvitationLanguage({
+ invitationId,
+ language,
+ adminEmail,
+}: {
+ invitationId: string
+ language: string
+ adminEmail: string
+}) {
+ return db.transaction().execute(async (trx) => {
+ const invitation = await trx
+ .selectFrom("invitations")
+ .select(["id", "display_name", "language"])
+ .where("id", "=", invitationId)
+ .executeTakeFirst()
+
+ if (!invitation) return null
+
+ const updated = await trx
+ .updateTable("invitations")
+ .set({
+ language,
+ updated_at: new Date(),
+ })
+ .where("id", "=", invitationId)
+ .returning(["id", "language"])
+ .executeTakeFirst()
+
+ if (!updated) return null
+
+ await trx
+ .insertInto("admin_audit_events")
+ .values({
+ admin_email: adminEmail,
+ action: "invitation.language_updated",
+ target_type: "invitation",
+ target_id: invitationId,
+ metadata: {
+ displayName: invitation.display_name,
+ previousLanguage: invitation.language,
+ newLanguage: language,
+ },
+ })
+ .execute()
+
+ return updated
+ })
+}
+
export async function listInvitations(): Promise {
const invitations = await db
.selectFrom("invitations")
diff --git a/lib/validation/invitation.ts b/lib/validation/invitation.ts
index bcc9bc9..0ee5ec2 100644
--- a/lib/validation/invitation.ts
+++ b/lib/validation/invitation.ts
@@ -54,6 +54,14 @@ export const updateSaveTheDateSentSchema = z.object({
sent: z.boolean(),
})
+export const updateInvitationLanguageSchema = z.object({
+ invitationId: z.uuid(),
+ language: z.enum(supportedLanguageCodes, {
+ error: "Select a supported language",
+ }),
+})
+
export type CreateInvitationInput = z.infer
export type DeleteInvitationInput = z.infer
export type UpdateSaveTheDateSentInput = z.infer
+export type UpdateInvitationLanguageInput = z.infer