karbe/src/app/admin/settings/_components/SettingsForms.tsx

171 lines
5.7 KiB
TypeScript

"use client";
import { useState, useTransition } from "react";
import { FormField, inputCls, selectCls } from "@/components/admin/FormField";
import {
savePlatformSettingsAction,
saveStripeSettingsAction,
saveThemeSettingsAction,
} from "../actions";
type Action = (fd: FormData) => Promise<{ ok: false; error: string } | { ok: true } | undefined>;
function FormWrapper({
action,
children,
submitLabel = "Enregistrer",
}: {
action: Action;
children: React.ReactNode;
submitLabel?: string;
}) {
const [pending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
function onSubmit(fd: FormData) {
setError(null);
setSuccess(null);
startTransition(async () => {
const res = await action(fd);
if (res && res.ok === false) setError(res.error);
else if (res && res.ok === true) setSuccess("Enregistré.");
});
}
return (
<form action={onSubmit} className="space-y-4">
<fieldset disabled={pending} className="space-y-4">
{children}
{error ? (
<div className="rounded border border-rose-200 bg-rose-50 px-3 py-2 text-sm text-rose-700">{error}</div>
) : null}
{success ? (
<div className="rounded border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-800">{success}</div>
) : null}
<div className="flex items-center justify-end">
<button
type="submit"
className="rounded-md bg-zinc-900 px-5 py-2 text-sm font-semibold text-white hover:bg-zinc-800 disabled:opacity-50"
>
{pending ? "Enregistrement…" : submitLabel}
</button>
</div>
</fieldset>
</form>
);
}
export function PlatformForm({
initial,
}: {
initial: { name: string; defaultLang: string; activeLangs: string[]; currency: string; commissionPercent: number };
}) {
return (
<FormWrapper action={savePlatformSettingsAction}>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField label="Nom de la plateforme" required>
<input name="name" defaultValue={initial.name} required maxLength={80} className={inputCls} />
</FormField>
<FormField label="Devise (ISO 4217)" required hint="EUR, USD, BRL…">
<input
name="currency"
defaultValue={initial.currency}
required
pattern="^[A-Z]{3}$"
maxLength={3}
className={inputCls + " uppercase"}
/>
</FormField>
<FormField label="Langue par défaut" required hint="Code ISO 639-1 (fr, en, pt…)">
<input
name="defaultLang"
defaultValue={initial.defaultLang}
required
pattern="^[a-zA-Z]{2}$"
maxLength={2}
className={inputCls + " lowercase"}
/>
</FormField>
<FormField label="Langues actives" required hint="Séparées par virgule (fr, en, pt).">
<input
name="activeLangs"
defaultValue={initial.activeLangs.join(", ")}
required
className={inputCls + " lowercase"}
placeholder="fr, en"
/>
</FormField>
<FormField label="Commission plateforme (%)" hint="Affiché dans les CGV. 0 = pas de commission.">
<input
name="commissionPercent"
type="number"
min={0}
max={100}
step="0.01"
defaultValue={initial.commissionPercent.toString()}
className={inputCls}
/>
</FormField>
</div>
</FormWrapper>
);
}
export function ThemeForm({ initial }: { initial: { active: string } }) {
return (
<FormWrapper action={saveThemeSettingsAction}>
<FormField label="Thème actif" hint="Détermine la skin du site public.">
<select name="active" defaultValue={initial.active} className={selectCls}>
<option value="default">default sobre (admin-like)</option>
<option value="theme-aquarelle">theme-aquarelle carnet naturaliste XIXᵉ</option>
<option value="theme-guyane">theme-guyane palette tropicale</option>
</select>
</FormField>
</FormWrapper>
);
}
export function StripeForm({
initial,
}: {
initial: { currency: string; commissionMode: string; perBookingFeePercent: number };
}) {
return (
<FormWrapper action={saveStripeSettingsAction}>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField label="Devise Stripe" required hint="Doit correspondre à la devise plateforme.">
<input
name="currency"
defaultValue={initial.currency}
required
pattern="^[A-Z]{3}$"
maxLength={3}
className={inputCls + " uppercase"}
/>
</FormField>
<FormField label="Modèle économique" required>
<select name="commissionMode" defaultValue={initial.commissionMode} className={selectCls}>
<option value="none">Aucune monétisation (preview)</option>
<option value="owner-subscription">Abonnement loueur (revenu plateforme)</option>
<option value="per-booking">Commission par réservation</option>
</select>
</FormField>
<FormField
label="Commission par réservation (%)"
hint="Utilisé uniquement si modèle = par réservation."
>
<input
name="perBookingFeePercent"
type="number"
min={0}
max={100}
step="0.01"
defaultValue={initial.perBookingFeePercent.toString()}
className={inputCls}
/>
</FormField>
</div>
</FormWrapper>
);
}