synchronisation du contenu avec la DB et amélioration du CMS
- Migration du blog et des interviews des données mockées vers PostgreSQL (Prisma). - Refonte des pages publiques en Server Components pour de meilleures performances/SEO. - Intégration d'un éditeur de texte riche (React Quill) avec titres H1-H6 et séparateurs. - Correction des erreurs de validation Prisma liées aux paramètres asynchrones (Next.js 15+). - Correction des problèmes d'affichage des modales de suspension via React Portals. - Installation de @tailwindcss/typography et correction du débordement de texte (break-words). - Implémentation des actions de modération (suspension/révocation) pour les utilisateurs et boutiques.
This commit is contained in:
@@ -1,22 +1,24 @@
|
||||
"use client";
|
||||
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { ArrowLeft, Share2, Play, Calendar, User, Building2 } from 'lucide-react';
|
||||
import { MOCK_INTERVIEWS } from '../../../lib/mockData';
|
||||
import { InterviewType } from '../../../types';
|
||||
import { prisma } from '../../../lib/prisma';
|
||||
import { InterviewType } from '@prisma/client';
|
||||
|
||||
const InterviewDetailPage = () => {
|
||||
const { id } = useParams();
|
||||
const interview = MOCK_INTERVIEWS.find(i => i.id === id);
|
||||
interface Props {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [id]);
|
||||
export default async function InterviewDetailPage({ params }: Props) {
|
||||
const { id } = await params;
|
||||
|
||||
const interview = await prisma.interview.findUnique({
|
||||
where: { id }
|
||||
});
|
||||
|
||||
if (!interview) return null;
|
||||
if (!interview) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Helper to get YouTube embed URL
|
||||
const getEmbedUrl = (url: string) => {
|
||||
@@ -56,7 +58,7 @@ const InterviewDetailPage = () => {
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-2 text-brand-500" />
|
||||
{interview.date}
|
||||
{new Date(interview.date).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' })}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,7 +75,7 @@ const InterviewDetailPage = () => {
|
||||
className="w-full h-full"
|
||||
></iframe>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center bg-gray-900">
|
||||
<div className="w-full h-full flex items-center justify-center bg-gray-900 min-h-[300px]">
|
||||
<p className="text-white">Vidéo non disponible</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -86,11 +88,9 @@ const InterviewDetailPage = () => {
|
||||
)}
|
||||
|
||||
{/* Description / Article Content */}
|
||||
<div className="prose prose-lg prose-orange max-w-none mx-auto text-gray-600">
|
||||
<div className="prose prose-lg prose-orange max-w-none mx-auto text-gray-600 break-words overflow-hidden">
|
||||
{!isVideo && interview.content ? (
|
||||
interview.content.split('\n').map((paragraph, idx) => (
|
||||
<p key={idx}>{paragraph}</p>
|
||||
))
|
||||
<div dangerouslySetInnerHTML={{ __html: interview.content }} />
|
||||
) : (
|
||||
<p className="text-xl font-serif italic text-gray-500 border-l-4 border-brand-500 pl-6 py-2">
|
||||
{interview.excerpt}
|
||||
@@ -108,6 +108,5 @@ const InterviewDetailPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default InterviewDetailPage;
|
||||
|
||||
Reference in New Issue
Block a user