Maj taxonomies
Some checks failed
Build and Push App / build (push) Failing after 11m28s

maj url photo
+ page 404
+ gestion programmation d'article
This commit is contained in:
2026-05-03 21:22:12 +02:00
parent 0e72867d4c
commit 5f421b418e
41 changed files with 1946 additions and 735 deletions

View File

@@ -14,16 +14,21 @@ interface Props {
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params;
const now = new Date();
const interview = await prisma.interview.findFirst({
where: {
OR: [{ id }, { slug: id }]
OR: [{ id }, { slug: id }],
publishedAt: { lte: now },
status: 'PUBLISHED'
}
});
const event = !interview ? await prisma.event.findFirst({
where: {
OR: [{ id }, { slug: id }]
OR: [{ id }, { slug: id }],
publishedAt: { lte: now },
status: 'PUBLISHED'
}
}) : null;
@@ -47,18 +52,23 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
export default async function AfroLifeDetailPage({ params }: Props) {
const { id } = await params;
const now = new Date();
// 1. Try to find an interview
const interview = await prisma.interview.findFirst({
where: {
OR: [{ id }, { slug: id }]
OR: [{ id }, { slug: id }],
publishedAt: { lte: now },
status: 'PUBLISHED'
}
});
// 2. If not found, try to find an event
const event = !interview ? await prisma.event.findFirst({
where: {
OR: [{ id }, { slug: id }]
OR: [{ id }, { slug: id }],
publishedAt: { lte: now },
status: 'PUBLISHED'
}
}) : null;
@@ -157,7 +167,7 @@ export default async function AfroLifeDetailPage({ params }: Props) {
)}
{/* Description / Article Content / Event Content */}
<div className="prose prose-lg prose-orange max-w-none mx-auto text-gray-600 break-words overflow-hidden">
<div className="prose prose-lg prose-orange max-w-none mx-auto text-gray-600">
{isEvent ? (
<div dangerouslySetInnerHTML={{ __html: sanitizeHTML((event as any).description) }} />
) : (
@@ -195,4 +205,3 @@ export default async function AfroLifeDetailPage({ params }: Props) {
</div>
);
}

View File

@@ -19,10 +19,17 @@ export default async function AfroLifePage({ searchParams }: Props) {
const filter = allowedFilters.includes(rawFilter) ? rawFilter : 'ALL';
let items: any[] = [];
const now = new Date();
try {
if (filter === 'EVENT') {
const events = await prisma.event.findMany({
where: {
status: 'PUBLISHED',
publishedAt: {
lte: now
}
},
orderBy: { date: 'desc' }
});
items = events.map(e => ({
@@ -34,7 +41,12 @@ export default async function AfroLifePage({ searchParams }: Props) {
duration: new Date(e.date).toLocaleDateString('fr-FR')
}));
} else {
const where: any = {};
const where: any = {
status: 'PUBLISHED',
publishedAt: {
lte: now
}
};
if (filter !== 'ALL') {
where.type = filter as InterviewType;
}
@@ -47,6 +59,12 @@ export default async function AfroLifePage({ searchParams }: Props) {
// If 'ALL', we might want to also fetch events and mix them
if (filter === 'ALL') {
const events = await prisma.event.findMany({
where: {
status: 'PUBLISHED',
publishedAt: {
lte: now
}
},
orderBy: { date: 'desc' },
take: 10
});

View File

@@ -6,6 +6,12 @@ import { generateSlug } from '@/lib/utils'
export async function GET() {
try {
const posts = await prisma.blogPost.findMany({
where: {
status: 'PUBLISHED',
publishedAt: {
lte: new Date()
}
},
orderBy: { date: 'desc' },
})
return NextResponse.json(posts)

View File

@@ -5,11 +5,6 @@ import { USER_SAFE_SELECT, getAuthUser } from '@/lib/auth-utils'
// GET /api/businesses — List all businesses (Mock + DB)
export async function GET(request: NextRequest) {
try {
const user = await getAuthUser(request)
if (!user) {
return NextResponse.json({ error: 'Authentification requise' }, { status: 401 })
}
const { searchParams } = new URL(request.url)
const category = searchParams.get('category')
const q = searchParams.get('q')?.toLowerCase()

View File

@@ -5,6 +5,12 @@ import prisma from '@/lib/prisma'
export async function GET() {
try {
const interviews = await prisma.interview.findMany({
where: {
status: 'PUBLISHED',
publishedAt: {
lte: new Date()
}
},
orderBy: { date: 'desc' },
})
return NextResponse.json(interviews)

20
app/api/tags/route.ts Normal file
View File

@@ -0,0 +1,20 @@
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function GET() {
try {
const blogPosts = await prisma.blogPost.findMany({ select: { tags: true } });
const interviews = await prisma.interview.findMany({ select: { tags: true } });
const events = await prisma.event.findMany({ select: { tags: true } });
const businesses = await prisma.business.findMany({ select: { tags: true } });
const allTags = new Set<string>();
[...blogPosts, ...interviews, ...events, ...businesses].forEach(item => {
item.tags.forEach(tag => allTags.add(tag.toLowerCase()));
});
return NextResponse.json(Array.from(allTags).sort());
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch tags' }, { status: 500 });
}
}

View File

@@ -19,7 +19,11 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
OR: [
{ id: id },
{ slug: id }
]
],
publishedAt: {
lte: new Date()
},
status: 'PUBLISHED'
}
});
@@ -46,7 +50,11 @@ export default async function BlogPostPage({ params }: Props) {
OR: [
{ id: id },
{ slug: id }
]
],
publishedAt: {
lte: new Date()
},
status: 'PUBLISHED'
}
});
@@ -102,7 +110,7 @@ export default async function BlogPostPage({ params }: Props) {
</header>
{/* Content */}
<div className="prose prose-lg prose-orange max-w-none text-gray-600 break-words overflow-hidden">
<div className="prose prose-lg prose-orange max-w-none text-gray-600">
<p className="lead text-xl text-gray-500 font-serif italic mb-6 border-b border-gray-100 pb-6">
{post.excerpt}
</p>

View File

@@ -10,6 +10,12 @@ export default async function BlogPage() {
try {
posts = await prisma.blogPost.findMany({
where: {
status: 'PUBLISHED',
publishedAt: {
lte: new Date()
}
},
orderBy: { createdAt: 'desc' }
});
} catch (error) {

View File

@@ -23,4 +23,15 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: var(--font-sans);
}
.prose {
text-align: justify;
hyphens: auto;
word-break: normal;
overflow-wrap: break-word;
}
.prose p {
margin-bottom: 1.25em;
}

50
app/not-found.tsx Normal file
View File

@@ -0,0 +1,50 @@
import React from 'react';
import Link from 'next/link';
import { Home, ArrowLeft, Search } from 'lucide-react';
export default function NotFound() {
return (
<div className="min-h-screen bg-white flex items-center justify-center px-4 sm:px-6 lg:px-8 py-24">
<div className="max-w-md w-full text-center">
{/* Animated 404 number */}
<div className="relative mb-8">
<h1 className="text-9xl font-serif font-black text-gray-100 select-none">404</h1>
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-4xl md:text-5xl font-serif font-bold text-brand-600">Oups !</span>
</div>
</div>
<h2 className="text-2xl md:text-3xl font-bold text-gray-900 mb-4 font-serif">
Page introuvable
</h2>
<p className="text-gray-500 mb-10 text-lg">
Il semblerait que le chemin que vous avez emprunté n'existe pas ou a été déplacé.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="/"
className="inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-full text-white bg-brand-600 hover:bg-brand-700 shadow-lg hover:shadow-xl transition-all"
>
<Home className="w-5 h-5 mr-2" />
Retour à l'accueil
</Link>
<Link
href="/annuaire"
className="inline-flex items-center justify-center px-6 py-3 border border-gray-200 text-base font-medium rounded-full text-gray-700 bg-white hover:bg-gray-50 shadow-sm transition-all"
>
<Search className="w-5 h-5 mr-2" />
Explorer l'annuaire
</Link>
</div>
<div className="mt-12 pt-12 border-t border-gray-100">
<p className="text-sm text-gray-400 italic">
"L'échec est une étape vers la réussite, mais une erreur 404 est juste une impasse."
</p>
</div>
</div>
</div>
);
}

View File

@@ -62,6 +62,10 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
// 3. Fetch all blog posts
const blogPosts = await prisma.blogPost.findMany({
where: {
status: 'PUBLISHED',
publishedAt: { lte: new Date() }
},
select: { id: true, slug: true, updatedAt: true },
});
@@ -74,6 +78,10 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
// 4. Fetch all Afro Life (Interviews)
const interviews = await prisma.interview.findMany({
where: {
status: 'PUBLISHED',
publishedAt: { lte: new Date() }
},
select: { id: true, slug: true, updatedAt: true },
});
@@ -84,7 +92,23 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
priority: 0.7,
}));
return [...staticRoutes, ...businessRoutes, ...blogRoutes, ...afroLifeRoutes];
// 5. Fetch all Events
const events = await prisma.event.findMany({
where: {
status: 'PUBLISHED',
publishedAt: { lte: new Date() }
},
select: { id: true, slug: true, updatedAt: true },
});
const eventRoutes = events.map((event) => ({
url: `${baseUrl}/afrolife/${event.slug || event.id}`,
lastModified: event.updatedAt,
changeFrequency: 'monthly' as const,
priority: 0.7,
}));
return [...staticRoutes, ...businessRoutes, ...blogRoutes, ...afroLifeRoutes, ...eventRoutes];
} catch (error) {
console.error("Error generating sitemap:", error);
// Fallback to only static routes if DB connection fails