feat(web): polish commission flow and default public navigation

This commit is contained in:
2026-02-12 22:04:42 +01:00
parent 958f3ad723
commit 47e59d2926
8 changed files with 128 additions and 18 deletions

View File

@@ -69,6 +69,13 @@ export default async function PublicCommissionRequestPage({
async function submitCommissionRequestAction(formData: FormData) {
"use server"
const budgetMin = readNullableNumber(formData, "budgetMin")
const budgetMax = readNullableNumber(formData, "budgetMax")
if (budgetMin != null && budgetMax != null && budgetMax < budgetMin) {
redirect(buildRedirect(locale, { error: "budget_range_invalid" }))
}
try {
await createPublicCommissionRequest({
customerName: readInputString(formData, "customerName"),
@@ -77,8 +84,8 @@ export default async function PublicCommissionRequestPage({
customerInstagram: readNullableString(formData, "customerInstagram"),
title: readInputString(formData, "title"),
description: readNullableString(formData, "description"),
budgetMin: readNullableNumber(formData, "budgetMin"),
budgetMax: readNullableNumber(formData, "budgetMax"),
budgetMin,
budgetMax,
})
} catch {
redirect(buildRedirect(locale, { error: "submission_failed" }))
@@ -110,6 +117,11 @@ export default async function PublicCommissionRequestPage({
{t("error")}
</section>
) : null}
{error === "budget_range_invalid" ? (
<section className="rounded-xl border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-800">
{t("budgetRangeError")}
</section>
) : null}
<form action={submitCommissionRequestAction} className="space-y-4 rounded-xl border p-6">
<div className="grid gap-4 md:grid-cols-2">
@@ -117,6 +129,7 @@ export default async function PublicCommissionRequestPage({
<span className="text-xs text-neutral-600">{t("fields.customerName")}</span>
<input
name="customerName"
autoComplete="name"
required
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
@@ -126,6 +139,7 @@ export default async function PublicCommissionRequestPage({
<input
name="customerEmail"
type="email"
autoComplete="email"
required
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
@@ -137,6 +151,7 @@ export default async function PublicCommissionRequestPage({
<span className="text-xs text-neutral-600">{t("fields.customerPhone")}</span>
<input
name="customerPhone"
autoComplete="tel"
className="w-full rounded border border-neutral-300 px-3 py-2 text-sm"
/>
</label>

View File

@@ -1,5 +1,5 @@
import { listPublicNavigation } from "@cms/db"
import { getLocale } from "next-intl/server"
import { getLocale, getTranslations } from "next-intl/server"
import { Link } from "@/i18n/navigation"
@@ -7,7 +7,33 @@ import { LanguageSwitcher } from "./language-switcher"
export async function PublicSiteHeader() {
const locale = await getLocale()
const navItems = await listPublicNavigation("header", locale)
const [navItems, t] = await Promise.all([
listPublicNavigation("header", locale),
getTranslations("Layout.nav"),
])
const fallbackNavItems = [
{
id: "fallback-home",
href: "/",
label: t("home"),
},
{
id: "fallback-portfolio",
href: "/portfolio",
label: t("portfolio"),
},
{
id: "fallback-news",
href: "/news",
label: t("news"),
},
{
id: "fallback-commissions",
href: "/commissions",
label: t("commissions"),
},
]
const resolvedNavItems = navItems.length > 0 ? navItems : fallbackNavItems
return (
<header className="border-b border-neutral-200 bg-white/80 backdrop-blur">
@@ -20,24 +46,15 @@ export async function PublicSiteHeader() {
</Link>
<nav className="flex flex-wrap items-center gap-2">
{navItems.length === 0 ? (
{resolvedNavItems.map((item) => (
<Link
href="/"
key={item.id}
href={item.href}
className="rounded-md border border-neutral-300 px-3 py-1.5 text-sm font-medium text-neutral-700 hover:bg-neutral-100"
>
Home
{item.label}
</Link>
) : (
navItems.map((item) => (
<Link
key={item.id}
href={item.href}
className="rounded-md border border-neutral-300 px-3 py-1.5 text-sm font-medium text-neutral-700 hover:bg-neutral-100"
>
{item.label}
</Link>
))
)}
))}
</nav>
<LanguageSwitcher />

View File

@@ -21,6 +21,9 @@
"brand": "CMS Web",
"nav": {
"home": "Start",
"portfolio": "Portfolio",
"news": "News",
"commissions": "Aufträge",
"about": "Über uns",
"contact": "Kontakt"
},
@@ -49,6 +52,7 @@
"description": "Teile deine Idee und Projektdetails. Wir prüfen die Anfrage und melden uns zeitnah.",
"success": "Deine Auftragsanfrage wurde übermittelt.",
"error": "Übermittlung fehlgeschlagen. Bitte prüfe die Eingaben und versuche es erneut.",
"budgetRangeError": "Das maximale Budget muss größer oder gleich dem minimalen Budget sein.",
"submit": "Anfrage senden",
"fields": {
"customerName": "Name",

View File

@@ -21,6 +21,9 @@
"brand": "CMS Web",
"nav": {
"home": "Home",
"portfolio": "Portfolio",
"news": "News",
"commissions": "Commissions",
"about": "About",
"contact": "Contact"
},
@@ -49,6 +52,7 @@
"description": "Share your idea and project details. We will review and reply as soon as possible.",
"success": "Your commission request was submitted.",
"error": "Submission failed. Please review your data and try again.",
"budgetRangeError": "Budget max must be greater than or equal to budget min.",
"submit": "Submit request",
"fields": {
"customerName": "Name",

View File

@@ -21,6 +21,9 @@
"brand": "CMS Web",
"nav": {
"home": "Inicio",
"portfolio": "Portafolio",
"news": "Noticias",
"commissions": "Comisiones",
"about": "Acerca de",
"contact": "Contacto"
},
@@ -49,6 +52,7 @@
"description": "Comparte tu idea y detalles del proyecto. Revisaremos la solicitud y responderemos pronto.",
"success": "Tu solicitud de comisión fue enviada.",
"error": "No se pudo enviar la solicitud. Revisa los datos e inténtalo de nuevo.",
"budgetRangeError": "El presupuesto máximo debe ser mayor o igual al mínimo.",
"submit": "Enviar solicitud",
"fields": {
"customerName": "Nombre",

View File

@@ -21,6 +21,9 @@
"brand": "CMS Web",
"nav": {
"home": "Accueil",
"portfolio": "Portfolio",
"news": "Actualités",
"commissions": "Commissions",
"about": "À propos",
"contact": "Contact"
},
@@ -49,6 +52,7 @@
"description": "Partagez votre idée et les détails du projet. Nous examinerons la demande et répondrons rapidement.",
"success": "Votre demande de commission a été envoyée.",
"error": "Échec de l'envoi. Vérifiez les données et réessayez.",
"budgetRangeError": "Le budget max doit être supérieur ou égal au budget min.",
"submit": "Envoyer la demande",
"fields": {
"customerName": "Nom",