feat: implement event submission flow with image upload and modal interface
This commit is contained in:
@@ -28,7 +28,7 @@ export async function createEvent(data: {
|
||||
thumbnailUrl: data.thumbnailUrl,
|
||||
link: data.link || null,
|
||||
tags: data.tags || [],
|
||||
status: ContentStatus.PENDING, // Always pending for user submission
|
||||
status: 'PENDING' as ContentStatus, // Always pending for user submission
|
||||
slug: `${slug}-${Math.random().toString(36).substring(2, 7)}`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ export async function createNews(data: {
|
||||
author: string;
|
||||
imageUrl: string;
|
||||
tags?: string[];
|
||||
sources?: any;
|
||||
}) {
|
||||
try {
|
||||
const slug = data.title
|
||||
@@ -26,12 +27,13 @@ export async function createNews(data: {
|
||||
author: data.author,
|
||||
imageUrl: data.imageUrl,
|
||||
tags: data.tags || [],
|
||||
status: ContentStatus.PENDING, // Always pending for user submission
|
||||
sources: data.sources || [],
|
||||
status: 'PENDING' as ContentStatus, // Always pending for user submission
|
||||
slug: `${slug}-${Math.random().toString(36).substring(2, 7)}`
|
||||
}
|
||||
});
|
||||
|
||||
revalidatePath("/blog");
|
||||
revalidatePath("/actualites");
|
||||
return { success: true, post };
|
||||
} catch (error) {
|
||||
console.error("Failed to create news:", error);
|
||||
|
||||
@@ -13,7 +13,13 @@ export async function uploadImage(formData: FormData) {
|
||||
|
||||
// Convert to Base64
|
||||
const base64 = buffer.toString('base64');
|
||||
const mimeType = file.type || 'image/jpeg';
|
||||
let mimeType = file.type || 'image/jpeg';
|
||||
|
||||
// Normalize JPEG mime types
|
||||
if (mimeType === 'image/jpg' || mimeType === 'image/pjpeg') {
|
||||
mimeType = 'image/jpeg';
|
||||
}
|
||||
|
||||
const dataUrl = `data:${mimeType};base64,${base64}`;
|
||||
|
||||
// Return the data URL directly (it will be saved in the database)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ArrowLeft, ArrowRight, User, Calendar, Share2 } from 'lucide-react';
|
||||
import { sanitizeHTML } from '../../../lib/sanitize';
|
||||
import { prisma } from '../../../lib/prisma';
|
||||
import BlogShareButtons from '../../../components/BlogShareButtons';
|
||||
import LightboxImage from '@/components/LightboxImage';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
interface Props {
|
||||
@@ -117,11 +118,12 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm p-6 md:p-10 border border-gray-100 relative z-10">
|
||||
{/* Image d'en-tête */}
|
||||
<div className="w-full h-64 md:h-80 relative mb-8 rounded-lg overflow-hidden">
|
||||
<img
|
||||
src={post.imageUrl}
|
||||
alt={post.title}
|
||||
className="w-full h-full object-cover"
|
||||
<div className="w-full h-64 md:h-80 relative mb-8 rounded-lg overflow-hidden shadow-md">
|
||||
<LightboxImage
|
||||
src={post.imageUrl}
|
||||
alt={post.title}
|
||||
position={post.coverPosition || "50% 50%"}
|
||||
zoom={post.coverZoom || 1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -160,7 +162,7 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
{post.excerpt}
|
||||
</p>
|
||||
|
||||
{/* Corps du texte avec ID pour le contrôle CSS */}
|
||||
{/* Corps du texte avec ID pour le contrôle CSS */}
|
||||
<div
|
||||
id="blog-content"
|
||||
className="mt-6"
|
||||
@@ -168,6 +170,32 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Sources Section */}
|
||||
{Array.isArray(post.sources) && post.sources.length > 0 && (
|
||||
<div className="mt-12 p-6 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<h3 className="text-sm font-bold uppercase tracking-wider text-gray-400 mb-4 flex items-center gap-2">
|
||||
<Share2 className="w-4 h-4 text-brand-500" />
|
||||
Sources & Références
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{(post.sources as any[]).map((source, index) => (
|
||||
<li key={index} className="flex items-start gap-3">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-brand-500 mt-2 shrink-0" />
|
||||
<a
|
||||
href={source.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-700 hover:text-brand-600 transition-colors break-all"
|
||||
>
|
||||
<span className="font-semibold">{source.title || 'Source'}</span>
|
||||
<span className="text-gray-400 ml-2 text-sm">{source.url}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Partage / Footer */}
|
||||
<div className="mt-12 pt-8 border-t border-gray-100 flex flex-col sm:flex-row justify-between items-center gap-4">
|
||||
<p className="text-gray-500 font-medium">Vous avez aimé cet article ?</p>
|
||||
|
||||
@@ -42,7 +42,16 @@ export default async function BlogPage() {
|
||||
{posts.map(post => (
|
||||
<Link key={post.id} href={`/actualites/${post.slug || post.id}`} className="group flex flex-col rounded-lg shadow-lg overflow-hidden bg-white hover:shadow-xl transition-shadow duration-300">
|
||||
<div className="flex-shrink-0 relative overflow-hidden h-48">
|
||||
<img className="h-full w-full object-cover group-hover:scale-105 transition-transform duration-500" src={post.imageUrl} alt={post.title} />
|
||||
<img
|
||||
className="h-full w-full object-cover group-hover:scale-[1.05] transition-transform duration-500"
|
||||
src={post.imageUrl}
|
||||
alt={post.title}
|
||||
style={{
|
||||
objectPosition: post.coverPosition || '50% 50%',
|
||||
transform: `scale(${post.coverZoom || 1})`,
|
||||
transformOrigin: post.coverPosition || '50% 50%'
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/10 group-hover:bg-transparent transition-colors"></div>
|
||||
</div>
|
||||
<div className="flex-1 bg-white p-6 flex flex-col justify-between">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ArrowLeft, Share2, Play, Calendar, User, Building2, MapPin, ArrowRight
|
||||
import { sanitizeHTML } from '../../../lib/sanitize';
|
||||
import { prisma } from '../../../lib/prisma';
|
||||
import { InterviewType } from '@prisma/client';
|
||||
import LightboxImage from '@/components/LightboxImage';
|
||||
|
||||
import { Metadata } from 'next';
|
||||
|
||||
@@ -161,8 +162,12 @@ export default async function AfroLifeDetailPage({ params }: Props) {
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative h-64 md:h-96 rounded-xl overflow-hidden shadow-lg mb-10">
|
||||
<img src={item!.thumbnailUrl} alt={item!.title} className="w-full h-full object-cover" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent"></div>
|
||||
<LightboxImage
|
||||
src={item!.thumbnailUrl}
|
||||
alt={item!.title}
|
||||
position={(item as any).coverPosition || "50% 50%"}
|
||||
zoom={(item as any).coverZoom || 1}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -149,7 +149,12 @@ export default async function AfroLifePage({ searchParams }: Props) {
|
||||
<img
|
||||
src={item.thumbnailUrl}
|
||||
alt={item.title}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
|
||||
className="w-full h-full object-cover group-hover:scale-[1.05] transition-transform duration-700"
|
||||
style={{
|
||||
objectPosition: item.coverPosition || '50% 50%',
|
||||
transform: `scale(${item.coverZoom || 1})`,
|
||||
transformOrigin: item.coverPosition || '50% 50%'
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/20 group-hover:bg-black/10 transition-colors"></div>
|
||||
|
||||
|
||||
@@ -57,4 +57,34 @@ h4 {
|
||||
.prose p {
|
||||
margin-bottom: 1.25em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Styles pour les tableaux dans le contenu */
|
||||
#blog-content table,
|
||||
.prose table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.5em 0;
|
||||
font-size: 0.9em;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
#blog-content th,
|
||||
#blog-content td,
|
||||
.prose th,
|
||||
.prose td {
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#blog-content th,
|
||||
.prose th {
|
||||
background-color: #f8fafc;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#blog-content tr:nth-child(even),
|
||||
.prose tr:nth-child(even) {
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
Reference in New Issue
Block a user