Modèle de Données : Ajout de la table AnalyticsEvent dans prisma/schema.prisma pour suivre les vues de pages, les clics et le temps de rétention (DWELL_TIME). API Backend : Création de la route app/api/analytics/route.ts pour enregistrer les événements en base de données. Tracking Client : Création du composant AnalyticsTracker.tsx (intégré dans le layout principal) qui capture automatiquement : Le changement de page (Page Views). Les clics sur les boutons et liens importants. Le temps passé sur chaque page. 2. Intégration Base de Données (PostgreSQL/Prisma) Fiches Entreprises : Mise à jour de app/directory/[id]/page.tsx pour récupérer les données réelles depuis PostgreSQL via Prisma au lieu d'utiliser les données de test (mockData). Navigation Dashboard : Correction du bouton "Voir ma fiche" dans le tableau de bord pour qu'il redirige correctement vers le profil public de l'entrepreneur. 3. Administration & Metrics Nouvelle Application Admin : Initialisation d'un dossier admin/ (application Next.js séparée) destinée à la gestion de la plateforme et à la visualisation des statistiques (Metrics & Analytics). 4. Authentification & Inscription Nouvelle Page : Création de app/register/page.tsx pour permettre l'inscription des nouveaux utilisateurs. Gestion de Session : Amélioration de UserProvider.tsx pour une meilleure gestion de l'état utilisateur à travers l'application. 5. Maintenance & Types Mise à jour des types globaux dans types.ts et rafraîchissement du package-lock.json suite à l'ajout de dépendances.
188 lines
7.4 KiB
TypeScript
188 lines
7.4 KiB
TypeScript
"use client";
|
|
|
|
import { useTransition } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { createInterview, updateInterview } from '@/app/actions/interview';
|
|
import { Loader2, ArrowLeft, Save, Video, FileText } from 'lucide-react';
|
|
import Link from 'next/link';
|
|
import { InterviewType } from '@prisma/client';
|
|
|
|
interface Props {
|
|
initialData?: any;
|
|
}
|
|
|
|
export default function InterviewForm({ initialData }: Props) {
|
|
const router = useRouter();
|
|
const [isPending, startTransition] = useTransition();
|
|
|
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
const formData = new FormData(event.currentTarget);
|
|
const data: any = {
|
|
title: formData.get('title') as string,
|
|
guestName: formData.get('guestName') as string,
|
|
companyName: formData.get('companyName') as string,
|
|
role: formData.get('role') as string,
|
|
type: formData.get('type') as InterviewType,
|
|
thumbnailUrl: formData.get('thumbnailUrl') as string,
|
|
videoUrl: formData.get('videoUrl') as string || null,
|
|
excerpt: formData.get('excerpt') as string,
|
|
content: formData.get('content') as string || null,
|
|
duration: formData.get('duration') as string || null,
|
|
};
|
|
|
|
startTransition(async () => {
|
|
const result = initialData
|
|
? await updateInterview(initialData.id, data)
|
|
: await createInterview(data);
|
|
|
|
if (result.success) {
|
|
router.push('/blog');
|
|
router.refresh();
|
|
} else {
|
|
alert(result.error);
|
|
}
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="max-w-4xl mx-auto">
|
|
<div className="mb-8 flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Link href="/blog" className="p-2 bg-slate-800 rounded-lg text-slate-400 hover:text-white transition-colors">
|
|
<ArrowLeft className="w-5 h-5" />
|
|
</Link>
|
|
<h1 className="text-3xl font-bold text-white">
|
|
{initialData ? 'Modifier l\'interview' : 'Nouvelle Interview'}
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="card space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">Titre de l'interview</label>
|
|
<input
|
|
name="title"
|
|
defaultValue={initialData?.title}
|
|
required
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
placeholder="Ex: Entreprise X : Une success story..."
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">Nom de l'invité</label>
|
|
<input
|
|
name="guestName"
|
|
defaultValue={initialData?.guestName}
|
|
required
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
placeholder="Ex: Sarah Koné"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">Entreprise</label>
|
|
<input
|
|
name="companyName"
|
|
defaultValue={initialData?.companyName}
|
|
required
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
placeholder="Ex: TechAfrica"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">Rôle / Poste</label>
|
|
<input
|
|
name="role"
|
|
defaultValue={initialData?.role}
|
|
required
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
placeholder="Ex: Fondatrice & CEO"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">Type d'interview</label>
|
|
<select
|
|
name="type"
|
|
defaultValue={initialData?.type || 'VIDEO'}
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
>
|
|
<option value="VIDEO">Vidéo</option>
|
|
<option value="ARTICLE">Article (Texte)</option>
|
|
</select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">Durée (Optionnel)</label>
|
|
<input
|
|
name="duration"
|
|
defaultValue={initialData?.duration}
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
placeholder="Ex: 12 min"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">URL de la miniature (Thumbnail)</label>
|
|
<input
|
|
name="thumbnailUrl"
|
|
defaultValue={initialData?.thumbnailUrl}
|
|
required
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">URL Vidéo (Si type Vidéo)</label>
|
|
<input
|
|
name="videoUrl"
|
|
defaultValue={initialData?.videoUrl}
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
placeholder="https://youtube.com/..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">Extrait (Court résumé)</label>
|
|
<textarea
|
|
name="excerpt"
|
|
defaultValue={initialData?.excerpt}
|
|
required
|
|
rows={2}
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-slate-400">Contenu / Transcription (Optionnel)</label>
|
|
<textarea
|
|
name="content"
|
|
defaultValue={initialData?.content}
|
|
rows={8}
|
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg p-3 text-white focus:outline-none focus:border-indigo-500"
|
|
/>
|
|
</div>
|
|
|
|
<div className="pt-4">
|
|
<button
|
|
type="submit"
|
|
disabled={isPending}
|
|
className="w-full btn-verify flex items-center justify-center gap-2 py-4 text-lg bg-emerald-600"
|
|
>
|
|
{isPending ? (
|
|
<Loader2 className="w-6 h-6 animate-spin" />
|
|
) : (
|
|
<Save className="w-6 h-6" />
|
|
)}
|
|
{initialData ? 'Enregistrer les modifications' : 'Créer l\'interview'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|