diff --git a/admin/prisma/schema.prisma b/admin/prisma/schema.prisma index 2838107..37d2feb 100644 --- a/admin/prisma/schema.prisma +++ b/admin/prisma/schema.prisma @@ -121,32 +121,40 @@ model Offer { } model BlogPost { - id String @id @default(uuid()) - title String - excerpt String - content String - author String - date DateTime @default(now()) - imageUrl String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(uuid()) + title String + slug String? @unique + excerpt String + content String + author String + date DateTime @default(now()) + imageUrl String + tags String[] @default([]) + metaTitle String? + metaDescription String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Interview { - id String @id @default(uuid()) - title String - guestName String - companyName String - role String - type InterviewType - thumbnailUrl String - videoUrl String? - content String? - excerpt String - date DateTime @default(now()) - duration String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(uuid()) + title String + slug String? @unique + guestName String + companyName String + role String + type InterviewType + thumbnailUrl String + videoUrl String? + content String? + excerpt String + date DateTime @default(now()) + duration String? + tags String[] @default([]) + metaTitle String? + metaDescription String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Comment { @@ -288,3 +296,19 @@ model LegalDocument { content String @db.Text updatedAt DateTime @updatedAt } + +model Event { + id String @id @default(uuid()) + title String + slug String? @unique + description String @db.Text + date DateTime + location String + thumbnailUrl String + link String? + tags String[] @default([]) + metaTitle String? + metaDescription String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/admin/src/app/actions/blog.ts b/admin/src/app/actions/blog.ts index 234e817..94c2023 100644 --- a/admin/src/app/actions/blog.ts +++ b/admin/src/app/actions/blog.ts @@ -2,18 +2,27 @@ import { prisma } from "@/lib/prisma"; import { revalidatePath } from "next/cache"; +import { generateSlug } from "@/lib/utils"; export async function createBlogPost(data: { title: string; + slug?: string; excerpt: string; content: string; author: string; imageUrl: string; + tags?: string[]; + metaTitle?: string; + metaDescription?: string; }) { try { + const slug = data.slug || generateSlug(data.title); + const post = await prisma.blogPost.create({ data: { ...data, + slug, + tags: data.tags || [], }, }); revalidatePath("/blog"); @@ -26,15 +35,25 @@ export async function createBlogPost(data: { export async function updateBlogPost(id: string, data: { title: string; + slug?: string; excerpt: string; content: string; author: string; imageUrl: string; + tags?: string[]; + metaTitle?: string; + metaDescription?: string; }) { try { + const slug = data.slug || generateSlug(data.title); + const post = await prisma.blogPost.update({ where: { id }, - data, + data: { + ...data, + slug, + tags: data.tags || [], + }, }); revalidatePath("/blog"); return { success: true, data: post }; diff --git a/admin/src/app/actions/event.ts b/admin/src/app/actions/event.ts new file mode 100644 index 0000000..07b5b47 --- /dev/null +++ b/admin/src/app/actions/event.ts @@ -0,0 +1,82 @@ +"use server"; + +import { prisma } from "@/lib/prisma"; +import { revalidatePath } from "next/cache"; +import { generateSlug } from "@/lib/utils"; + +export async function createEvent(data: { + title: string; + slug?: string; + description: string; + date: Date; + location: string; + thumbnailUrl: string; + link?: string; + tags?: string[]; + metaTitle?: string; + metaDescription?: string; +}) { + try { + const slug = data.slug || generateSlug(data.title); + + const event = await prisma.event.create({ + data: { + ...data, + slug, + tags: data.tags || [], + }, + }); + revalidatePath("/blog"); + revalidatePath("/afrolife"); + return { success: true, data: event }; + } catch (error) { + console.error("Failed to create event:", error); + return { success: false, error: "Erreur lors de la création de l'événement" }; + } +} + +export async function updateEvent(id: string, data: { + title: string; + slug?: string; + description: string; + date: Date; + location: string; + thumbnailUrl: string; + link?: string; + tags?: string[]; + metaTitle?: string; + metaDescription?: string; +}) { + try { + const slug = data.slug || generateSlug(data.title); + + const event = await prisma.event.update({ + where: { id }, + data: { + ...data, + slug, + tags: data.tags || [], + }, + }); + revalidatePath("/blog"); + revalidatePath("/afrolife"); + return { success: true, data: event }; + } catch (error) { + console.error("Failed to update event:", error); + return { success: false, error: "Erreur lors de la mise à jour" }; + } +} + +export async function deleteEvent(id: string) { + try { + await prisma.event.delete({ + where: { id }, + }); + revalidatePath("/blog"); + revalidatePath("/afrolife"); + return { success: true }; + } catch (error) { + console.error("Failed to delete event:", error); + return { success: false, error: "Erreur lors de la suppression" }; + } +} diff --git a/admin/src/app/actions/interview.ts b/admin/src/app/actions/interview.ts index 4392120..e9fba53 100644 --- a/admin/src/app/actions/interview.ts +++ b/admin/src/app/actions/interview.ts @@ -3,9 +3,11 @@ import { prisma } from "@/lib/prisma"; import { revalidatePath } from "next/cache"; import { InterviewType } from "@prisma/client"; +import { generateSlug } from "@/lib/utils"; export async function createInterview(data: { title: string; + slug?: string; guestName: string; companyName: string; role: string; @@ -15,10 +17,19 @@ export async function createInterview(data: { content?: string; excerpt: string; duration?: string; + tags?: string[]; + metaTitle?: string; + metaDescription?: string; }) { try { + const slug = data.slug || generateSlug(data.title); + const interview = await prisma.interview.create({ - data, + data: { + ...data, + slug, + tags: data.tags || [], + }, }); revalidatePath("/blog"); return { success: true, data: interview }; @@ -30,6 +41,7 @@ export async function createInterview(data: { export async function updateInterview(id: string, data: { title: string; + slug?: string; guestName: string; companyName: string; role: string; @@ -39,11 +51,20 @@ export async function updateInterview(id: string, data: { content?: string; excerpt: string; duration?: string; + tags?: string[]; + metaTitle?: string; + metaDescription?: string; }) { try { + const slug = data.slug || generateSlug(data.title); + const interview = await prisma.interview.update({ where: { id }, - data, + data: { + ...data, + slug, + tags: data.tags || [], + }, }); revalidatePath("/blog"); return { success: true, data: interview }; diff --git a/admin/src/app/blog/page.tsx b/admin/src/app/blog/page.tsx index 13af211..40edfd1 100644 --- a/admin/src/app/blog/page.tsx +++ b/admin/src/app/blog/page.tsx @@ -1,34 +1,40 @@ import { prisma } from '@/lib/prisma'; import Link from 'next/link'; -import { Plus, BookOpen, Mic2, Trash2, Edit } from 'lucide-react'; +import { Plus, BookOpen, Mic2, Trash2, Edit, Calendar, MapPin } from 'lucide-react'; import DeleteBlogButton from '@/components/DeleteBlogButton'; import DeleteInterviewButton from '@/components/DeleteInterviewButton'; +import DeleteEventButton from '@/components/DeleteEventButton'; async function getData() { const posts = await prisma.blogPost.findMany({ orderBy: { createdAt: 'desc' } }); const interviews = await prisma.interview.findMany({ orderBy: { createdAt: 'desc' } }); - return { posts, interviews }; + const events = await prisma.event.findMany({ orderBy: { date: 'desc' } }); + return { posts, interviews, events }; } export default async function BlogCMSPage() { - const { posts, interviews } = await getData(); + const { posts, interviews, events } = await getData(); return ( -
Gérez le contenu éditorial de la plateforme.
+Gérez le contenu éditorial et événementiel de la plateforme.
| Titre | -Auteur | -Date | -Actions | +Titre | +Auteur | +Date | +Actions | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| + | ||||||||||||||||||||||||||||||||||||||||
|
{post.title}
{post.excerpt}
|
- {post.author} | -{new Date(post.createdAt).toLocaleDateString('fr-FR')} | -+ | {post.author} | +{new Date(post.createdAt).toLocaleDateString('fr-FR')} | +
Interviews ({interviews.length})
-
+
+ {/* Recent IP Connections Table */}
+
+
);
diff --git a/admin/src/app/event/edit/[id]/page.tsx b/admin/src/app/event/edit/[id]/page.tsx
new file mode 100644
index 0000000..f46f1fc
--- /dev/null
+++ b/admin/src/app/event/edit/[id]/page.tsx
@@ -0,0 +1,20 @@
+import EventForm from "@/components/EventForm";
+import { prisma } from "@/lib/prisma";
+import { notFound } from "next/navigation";
+
+interface Props {
+ params: Promise<{ id: string }>;
+}
+
+export default async function EditEventPage({ params }: Props) {
+ const { id } = await params;
+ const event = await prisma.event.findUnique({
+ where: { id },
+ });
+
+ if (!event) {
+ notFound();
+ }
+
+ return
+
+ Événements ({events.length})+
+
+
@@ -357,6 +378,70 @@ export default async function MetricsPage({ searchParams: searchParamsPromise }:
+
);
diff --git a/admin/src/components/BlogForm.tsx b/admin/src/components/BlogForm.tsx
index 86ab415..87d94d7 100644
--- a/admin/src/components/BlogForm.tsx
+++ b/admin/src/components/BlogForm.tsx
@@ -16,6 +16,10 @@ interface Props {
content: string;
author: string;
imageUrl: string;
+ slug?: string | null;
+ tags?: string[];
+ metaTitle?: string | null;
+ metaDescription?: string | null;
};
}
@@ -29,10 +33,14 @@ export default function BlogForm({ initialData }: Props) {
const formData = new FormData(event.currentTarget);
const data = {
title: formData.get('title') as string,
+ slug: formData.get('slug') as string,
excerpt: formData.get('excerpt') as string,
content: content,
author: formData.get('author') as string,
imageUrl: formData.get('imageUrl') as string,
+ tags: (formData.get('tags') as string)?.split(',').map(t => t.trim()).filter(t => t !== ''),
+ metaTitle: formData.get('metaTitle') as string,
+ metaDescription: formData.get('metaDescription') as string,
};
startTransition(async () => {
@@ -51,7 +59,7 @@ export default function BlogForm({ initialData }: Props) {
};
return (
-
+
+
+
+ |
| Adresse IP | +Localisation | +Appareil / OS | +Navigateur | +Dernière Page | +Date / Heure | +
|---|---|---|---|---|---|
| + + {e.ip || '---.---.---.---'} + + | +
+
+ {e.country || 'Inconnu'}
+ {e.city || '---'}
+
+ |
+
+
+
+ {e.device}
+
+ {e.os}
+
+ |
+ + {e.browser} + | ++ + {e.path} + + | +
+
+ {new Date(e.createdAt).toLocaleDateString('fr-FR')}
+ {new Date(e.createdAt).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
+
+ |
+