feat: implement event submission flow with image upload and modal interface
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { useTransition, useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { createBlogPost, updateBlogPost } from '@/app/actions/actualites';
|
||||
import { Loader2, ArrowLeft, Save, Calendar, CheckCircle2, FileText, Archive } from 'lucide-react';
|
||||
import { Loader2, ArrowLeft, Save, Calendar, CheckCircle2, FileText, Archive, Link as LinkIcon, Plus, Trash2 } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import RichTextEditor from './RichTextEditor';
|
||||
@@ -26,6 +26,9 @@ interface Props {
|
||||
metaDescription?: string | null;
|
||||
publishedAt?: Date | string | null;
|
||||
status?: ContentStatus;
|
||||
coverPosition?: string | null;
|
||||
coverZoom?: number | null;
|
||||
sources?: any;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,6 +37,8 @@ export default function BlogForm({ initialData }: Props) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [content, setContent] = useState(initialData?.content || '');
|
||||
const [imageUrl, setImageUrl] = useState(initialData?.imageUrl || '');
|
||||
const [coverPosition, setCoverPosition] = useState(initialData?.coverPosition || '50% 50%');
|
||||
const [coverZoom, setCoverZoom] = useState(initialData?.coverZoom || 1);
|
||||
const [tags, setTags] = useState<string[]>(initialData?.tags || []);
|
||||
const [allExistingTags, setAllExistingTags] = useState<string[]>([]);
|
||||
const [publishedAtValue, setPublishedAtValue] = useState(
|
||||
@@ -41,6 +46,9 @@ export default function BlogForm({ initialData }: Props) {
|
||||
? new Date(initialData.publishedAt).toISOString().slice(0, 16)
|
||||
: new Date().toISOString().slice(0, 16)
|
||||
);
|
||||
const [sources, setSources] = useState<{ title: string; url: string }[]>(
|
||||
Array.isArray(initialData?.sources) ? initialData.sources : []
|
||||
);
|
||||
|
||||
const isPublished = new Date(publishedAtValue) <= new Date();
|
||||
|
||||
@@ -65,6 +73,9 @@ export default function BlogForm({ initialData }: Props) {
|
||||
metaDescription: formData.get('metaDescription') as string,
|
||||
publishedAt: publishedAtStr ? new Date(publishedAtStr) : undefined,
|
||||
status: formData.get('status') as ContentStatus,
|
||||
coverPosition: coverPosition,
|
||||
coverZoom: coverZoom,
|
||||
sources: sources,
|
||||
};
|
||||
|
||||
if (!data.imageUrl) {
|
||||
@@ -148,6 +159,11 @@ export default function BlogForm({ initialData }: Props) {
|
||||
value={imageUrl}
|
||||
onChange={setImageUrl}
|
||||
name="imageUrl"
|
||||
showPositionControls={true}
|
||||
position={coverPosition}
|
||||
zoom={coverZoom}
|
||||
onPositionChange={setCoverPosition}
|
||||
onZoomChange={setCoverZoom}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -187,6 +203,61 @@ export default function BlogForm({ initialData }: Props) {
|
||||
placeholder="Rédigez votre article ici..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* SOURCES SECTION */}
|
||||
<div className="pt-6 border-t border-slate-800">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<label className="text-sm font-medium text-slate-400 flex items-center gap-2">
|
||||
<LinkIcon className="w-4 h-4 text-brand-500" />
|
||||
Sources & Liens externes
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSources([...sources, { title: '', url: '' }])}
|
||||
className="text-xs font-bold text-brand-500 hover:text-brand-400 flex items-center gap-1 bg-brand-500/10 px-3 py-1.5 rounded-lg transition-colors"
|
||||
>
|
||||
<Plus className="w-3 h-3" />
|
||||
Ajouter une source
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{sources.map((source, index) => (
|
||||
<div key={index} className="flex gap-3 animate-in fade-in slide-in-from-top-1 duration-200">
|
||||
<input
|
||||
placeholder="Nom de la source (ex: Le Monde)"
|
||||
value={source.title}
|
||||
onChange={(e) => {
|
||||
const newSources = [...sources];
|
||||
newSources[index].title = e.target.value;
|
||||
setSources(newSources);
|
||||
}}
|
||||
className="flex-1 bg-slate-900 border border-slate-700 rounded-lg p-2.5 text-sm text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
<input
|
||||
placeholder="URL (ex: https://...)"
|
||||
value={source.url}
|
||||
onChange={(e) => {
|
||||
const newSources = [...sources];
|
||||
newSources[index].url = e.target.value;
|
||||
setSources(newSources);
|
||||
}}
|
||||
className="flex-2 bg-slate-900 border border-slate-700 rounded-lg p-2.5 text-sm text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSources(sources.filter((_, i) => i !== index))}
|
||||
className="p-2.5 text-slate-500 hover:text-red-400 transition-colors"
|
||||
>
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{sources.length === 0 && (
|
||||
<p className="text-xs text-slate-600 italic">Aucune source ajoutée pour cet article.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SEO SECTION */}
|
||||
|
||||
Reference in New Issue
Block a user