171 lines
5.7 KiB
TypeScript
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>
|
|
);
|
|
}
|