feat: change language
Deploy On Server / Prepare Repository Remotely (push) Successful in 9s
Deploy On Server / Docker Compose Up (push) Successful in 17s
Deploy On Server / Prune Images (push) Successful in 3s
Deploy On Server / Remove .env File (push) Successful in 3s

This commit is contained in:
Raul Lugo
2026-08-11 23:42:47 +02:00
parent d1a352a473
commit 3c359285ad
5 changed files with 208 additions and 2 deletions
+9
View File
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation"
import { CopyInviteButton } from "@/components/admin/copy-invite-button"
import { DeleteInvitationDialog } from "@/components/admin/delete-invitation-dialog"
import { InvitationLanguageSelect } from "@/components/admin/invitation-language-select"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
@@ -146,6 +147,14 @@ export default async function GuestDetailPage({
<CardDescription>Admin-managed invitation fields.</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div className="space-y-2">
<span className="text-muted-foreground">Language</span>
<InvitationLanguageSelect
invitationId={detail.invitation.id}
currentLanguage={detail.invitation.language}
displayName={detail.invitation.display_name}
/>
</div>
<div className="flex justify-between gap-4">
<span className="text-muted-foreground">Contact email</span>
<span className="text-right">{detail.invitation.contact_email ?? "—"}</span>
+52 -2
View File
@@ -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<UpdateInvitationLanguageResult> {
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 }
}
@@ -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<string | null>(null)
const [error, setError] = useState<string | null>(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 (
<div className="space-y-2">
<Select
value={value}
disabled={isSaving}
onValueChange={(next) => void updateLanguage(next)}
>
<SelectTrigger
aria-label={`Invitation language for ${displayName}`}
className="w-full"
>
<SelectValue placeholder="Select language" />
</SelectTrigger>
<SelectContent>
{supportedLanguages.map((language) => (
<SelectItem key={language.code} value={language.code}>
{language.label}
</SelectItem>
))}
</SelectContent>
</Select>
{message ? <p className="text-xs text-emerald-700">{message}</p> : null}
{error ? <p className="text-xs text-destructive">{error}</p> : null}
</div>
)
}
+49
View File
@@ -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<InvitationListItem[]> {
const invitations = await db
.selectFrom("invitations")
+8
View File
@@ -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<typeof createInvitationSchema>
export type DeleteInvitationInput = z.infer<typeof deleteInvitationSchema>
export type UpdateSaveTheDateSentInput = z.infer<typeof updateSaveTheDateSentSchema>
export type UpdateInvitationLanguageInput = z.infer<typeof updateInvitationLanguageSchema>