diff --git a/nextjs/src/components/admin/HeroImageUploadSlot.tsx b/nextjs/src/components/admin/HeroImageUploadSlot.tsx new file mode 100644 index 0000000..14ce458 --- /dev/null +++ b/nextjs/src/components/admin/HeroImageUploadSlot.tsx @@ -0,0 +1,127 @@ +'use client' + +import { useField } from '@payloadcms/ui' +import { useEffect, useRef, useState } from 'react' + +type MediaDoc = { id: number | string; url?: string | null; alt?: string | null } + +function extractMediaId(value: unknown): string | number | null { + if (value == null) return null + if (typeof value === 'number' || typeof value === 'string') return value + if (typeof value === 'object' && 'id' in (value as object)) { + return (value as { id: number | string }).id ?? null + } + return null +} + +type Props = { + path: string + placeholder?: string + alt?: string +} + +/** Single-upload click-to-replace slot for the hero image. */ +export function HeroImageUploadSlot({ path, placeholder = 'Cliquez pour uploader une image', alt = 'Hero' }: Props) { + const { value, setValue } = useField({ path }) + const fileInput = useRef(null) + const [uploading, setUploading] = useState(false) + const [error, setError] = useState(null) + const [doc, setDoc] = useState(null) + + const mediaId = extractMediaId(value) + + // Fetch current media doc to display its URL. + useEffect(() => { + let cancelled = false + if (mediaId == null) { + setDoc(null) + return + } + if (doc && String(doc.id) === String(mediaId)) return + ;(async () => { + try { + const res = await fetch(`/api/media/${mediaId}`, { credentials: 'include' }) + if (!res.ok) return + const fetched = await res.json() + if (cancelled) return + setDoc({ id: fetched.id, url: fetched.url, alt: fetched.alt }) + } catch { + /* ignore */ + } + })() + return () => { + cancelled = true + } + }, [mediaId, doc]) + + const onPick = () => fileInput.current?.click() + + const onFile = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + e.target.value = '' + if (!file) return + setError(null) + setUploading(true) + try { + const fd = new FormData() + fd.append('file', file) + fd.append('_payload', JSON.stringify({ alt: alt || file.name })) + const res = await fetch('/api/media', { method: 'POST', body: fd, credentials: 'include' }) + if (!res.ok) throw new Error(`upload ${res.status}`) + const { doc: uploaded } = await res.json() + setDoc({ id: uploaded.id, url: uploaded.url, alt: uploaded.alt }) + setValue(uploaded.id) + } catch (err) { + setError(err instanceof Error ? err.message : 'upload failed') + } finally { + setUploading(false) + } + } + + const clear = (e: React.MouseEvent) => { + e.stopPropagation() + setValue(null) + setDoc(null) + } + + return ( +
+ {doc?.url ? ( + {doc.alt + ) : ( +
{placeholder}
+ )} + +
+ {error ? `Erreur: ${error}` : 'Cliquez pour changer'} +
+ + {uploading &&
Upload…
} + + {doc?.url && ( + + )} + + +
+ ) +} diff --git a/nextjs/src/components/admin/HomePreviewEditor.tsx b/nextjs/src/components/admin/HomePreviewEditor.tsx new file mode 100644 index 0000000..dd9899b --- /dev/null +++ b/nextjs/src/components/admin/HomePreviewEditor.tsx @@ -0,0 +1,140 @@ +'use client' + +import { HeroImageUploadSlot } from './HeroImageUploadSlot' +import { HomeSettingsDrawer } from './HomeSettingsDrawer' +import { InlineEditable } from './InlineEditable' +import './home-panel.css' + +export default function HomePreviewEditor() { + return ( +
+
+ Cliquez sur un texte ou sur une image pour modifier. Les changements sont enregistrés automatiquement. +
+ + {/* ---------- Header (preview only) ---------- */} +
+ REBOURS + +
+ + {/* ---------- HERO ---------- */} +
+
+

+ +

+

+ +

+

+ +

+

+ +

+
+
+ +
+
+ +
+ + {/* ---------- COLLECTION HEADER ---------- */} +
+
+

+ +

+ + N OBJETS —{' '} + + +
+
+
PRODUIT 001
+
PRODUIT 002
+
PRODUIT 003
+
+

+ ↳ Les produits sont gérés dans la section Produits. +

+
+ + {/* ---------- CONTACT ---------- */} +
+
+

+ +

+

+ +

+
+
+

+ +

+
+ + +
+

+ {' '} + +

+
+
+ + {/* ---------- FOOTER ---------- */} +
+ + +
+ + +
+ ) +} diff --git a/nextjs/src/components/admin/HomeSettingsDrawer.tsx b/nextjs/src/components/admin/HomeSettingsDrawer.tsx new file mode 100644 index 0000000..4d9b6b3 --- /dev/null +++ b/nextjs/src/components/admin/HomeSettingsDrawer.tsx @@ -0,0 +1,48 @@ +'use client' + +import { useField } from '@payloadcms/ui' + +function TextInput({ path, label, placeholder }: { path: string; label: string; placeholder?: string }) { + const { value, setValue } = useField({ path }) + return ( +
+ + setValue(e.target.value)} + /> +
+ ) +} + +function TextAreaInput({ path, label, placeholder }: { path: string; label: string; placeholder?: string }) { + const { value, setValue } = useField({ path }) + return ( +
+ +