184 lines
7.9 KiB
TypeScript
184 lines
7.9 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useTransition } from 'react';
|
|
import { Save, Loader2, Zap, Calendar, Newspaper, Layout } from 'lucide-react';
|
|
import { updateOneShotPlan } from '@/app/actions/one-shot';
|
|
import { toast } from 'react-hot-toast';
|
|
import { OneShotType } from '@prisma/client';
|
|
|
|
interface OneShotPlan {
|
|
id: string;
|
|
type: OneShotType;
|
|
name: string;
|
|
priceXOF: number;
|
|
priceEUR: number;
|
|
description: string | null;
|
|
}
|
|
|
|
const TYPE_ICONS = {
|
|
BUMP: Zap,
|
|
POST_EVENT: Calendar,
|
|
POST_NEWS: Newspaper,
|
|
AD_DIRECTORY: Layout,
|
|
};
|
|
|
|
const TYPE_LABELS = {
|
|
BUMP: "Remonter dans l'annuaire",
|
|
POST_EVENT: "Poster un événement",
|
|
POST_NEWS: "Poster une actualité",
|
|
AD_DIRECTORY: "Publicité dans l'annuaire",
|
|
};
|
|
|
|
export default function OneShotPricingSettings({ initialPlans }: { initialPlans: OneShotPlan[] }) {
|
|
const [plans, setPlans] = useState<OneShotPlan[]>(initialPlans);
|
|
const [isPending, startTransition] = useTransition();
|
|
|
|
// Ensure all types are present even if not in DB yet
|
|
const allTypes: OneShotType[] = ['BUMP', 'POST_EVENT', 'POST_NEWS', 'AD_DIRECTORY'];
|
|
|
|
const getPlanByType = (type: OneShotType) => {
|
|
return plans.find(p => p.type === type) || {
|
|
id: '',
|
|
type,
|
|
name: TYPE_LABELS[type],
|
|
priceXOF: 0,
|
|
priceEUR: 0,
|
|
description: ''
|
|
};
|
|
};
|
|
|
|
const handleUpdate = async (type: OneShotType, data: any) => {
|
|
startTransition(async () => {
|
|
const result = await updateOneShotPlan(type, data);
|
|
if (result.success) {
|
|
toast.success(`Plan ${TYPE_LABELS[type]} mis à jour`);
|
|
} else {
|
|
toast.error(result.error || "Erreur lors de la mise à jour");
|
|
}
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
|
<div className="bg-slate-900/50 border border-slate-700 rounded-2xl overflow-hidden">
|
|
<div className="p-4 bg-slate-800/50 border-b border-slate-700 flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<Zap className="w-5 h-5 text-yellow-400" />
|
|
<h2 className="font-semibold text-white">Tarifs des Services Ponctuels (One-Shot)</h2>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={async () => {
|
|
if (confirm("Initialiser tous les plans avec les valeurs par défaut ?")) {
|
|
startTransition(async () => {
|
|
for (const type of allTypes) {
|
|
await updateOneShotPlan(type, {
|
|
name: TYPE_LABELS[type],
|
|
priceXOF: 5000,
|
|
priceEUR: 8,
|
|
description: `Service de ${TYPE_LABELS[type].toLowerCase()}`
|
|
});
|
|
}
|
|
toast.success("Plans initialisés. Rechargement...");
|
|
window.location.reload();
|
|
});
|
|
}
|
|
}}
|
|
className="text-xs bg-slate-800 hover:bg-slate-700 text-slate-300 px-3 py-1 rounded-lg border border-slate-700 transition-all"
|
|
>
|
|
Initialiser par défaut
|
|
</button>
|
|
</div>
|
|
<div className="p-6 space-y-8">
|
|
<p className="text-sm text-slate-400">
|
|
Configurez les prix des services à l'acte. Ces tarifs seront affichés sur la page de tarification.
|
|
</p>
|
|
|
|
<div className="grid grid-cols-1 gap-6">
|
|
{allTypes.map((type) => {
|
|
const plan = getPlanByType(type);
|
|
const Icon = TYPE_ICONS[type];
|
|
|
|
return (
|
|
<div key={type} className="bg-slate-950/50 border border-slate-800 rounded-xl p-6 hover:border-slate-700 transition-colors">
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center">
|
|
<Icon className="w-6 h-6 text-indigo-400" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-bold text-white text-lg">{TYPE_LABELS[type]}</h3>
|
|
<p className="text-xs text-slate-500 font-mono">{type}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.currentTarget);
|
|
handleUpdate(type, {
|
|
name: formData.get('name'),
|
|
priceXOF: parseInt(formData.get('priceXOF') as string || '0'),
|
|
priceEUR: parseInt(formData.get('priceEUR') as string || '0'),
|
|
description: formData.get('description'),
|
|
});
|
|
}}
|
|
className="flex-1 grid grid-cols-1 md:grid-cols-4 gap-4"
|
|
>
|
|
<div className="space-y-1">
|
|
<label className="text-[10px] font-bold text-slate-500 uppercase">Nom affiché</label>
|
|
<input
|
|
name="name"
|
|
defaultValue={plan.name}
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-2 text-sm text-white focus:outline-none focus:border-indigo-500"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-[10px] font-bold text-slate-500 uppercase">Prix (XOF)</label>
|
|
<input
|
|
type="number"
|
|
name="priceXOF"
|
|
defaultValue={plan.priceXOF}
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-2 text-sm text-white focus:outline-none focus:border-indigo-500"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-[10px] font-bold text-slate-500 uppercase">Prix (EUR)</label>
|
|
<input
|
|
type="number"
|
|
name="priceEUR"
|
|
defaultValue={plan.priceEUR}
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-2 text-sm text-white focus:outline-none focus:border-indigo-500"
|
|
/>
|
|
</div>
|
|
<div className="flex items-end">
|
|
<button
|
|
type="submit"
|
|
disabled={isPending}
|
|
className="w-full bg-indigo-600 hover:bg-indigo-700 disabled:bg-slate-800 text-white p-2 rounded-lg font-bold text-sm transition-all flex items-center justify-center gap-2"
|
|
>
|
|
{isPending ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
|
Enregistrer
|
|
</button>
|
|
</div>
|
|
<div className="md:col-span-4 space-y-1">
|
|
<label className="text-[10px] font-bold text-slate-500 uppercase">Description courte</label>
|
|
<input
|
|
name="description"
|
|
defaultValue={plan.description || ''}
|
|
placeholder="Ex: Remontez votre entreprise en tête de liste pour 7 jours"
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-2 text-sm text-white focus:outline-none focus:border-indigo-500"
|
|
/>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|