rebours/nextjs/src/components/admin/ImageUploadSlot.tsx
ordinarthur f5671008a7
All checks were successful
Build & Deploy to K3s / build-and-deploy (push) Successful in 2m39s
feat(admin): replace default edit view with visual preview editor
Overriding admin.components.views.edit.default makes /admin/collections/
products/:id and /create render the product-detail panel directly —
text fields are contentEditable, the image is click-to-upload, and
price is inline-editable in the checkout-price-line. Fields that don't
fit the public template (slug, name, currency, availability, SEO,
isPublished, sortOrder, stripeID) live in a collapsible "Réglages
avancés" drawer below the panel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 13:54:50 +02:00

76 lines
2.5 KiB
TypeScript

'use client'
import { useField } from '@payloadcms/ui'
import { useRef, useState } from 'react'
type MediaDoc = { id: number | string; url?: string | null; alt?: string | null }
type ImageEntry = { id?: string; image?: MediaDoc | string | null } | null | undefined
type Props = {
displayName?: string
}
function resolveFirstImage(entries: ImageEntry[] | undefined | null): { url: string; alt: string } {
const first = entries?.[0]?.image
if (!first) return { url: '', alt: '' }
if (typeof first === 'string') return { url: '', alt: '' }
return { url: first.url ?? '', alt: first.alt ?? '' }
}
export function ImageUploadSlot({ displayName }: Props) {
const { value: images, setValue } = useField<ImageEntry[]>({ path: 'images' })
const fileInput = useRef<HTMLInputElement | null>(null)
const [uploading, setUploading] = useState(false)
const [error, setError] = useState<string | null>(null)
const { url, alt } = resolveFirstImage(images)
const onPick = () => fileInput.current?.click()
const onFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
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: displayName || 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 } = await res.json()
const media: MediaDoc = { id: doc.id, url: doc.url, alt: doc.alt }
const next = [{ image: media }, ...(images ?? []).slice(1)]
setValue(next)
} catch (err) {
setError(err instanceof Error ? err.message : 'upload failed')
} finally {
setUploading(false)
}
}
return (
<div className="rebours-admin-panel__img-slot" onClick={onPick} role="button" tabIndex={0}>
{url ? (
<img src={url} alt={alt || displayName || 'Produit'} />
) : (
<div className="rebours-admin-panel__img-placeholder">
Cliquez pour<br />uploader une image
</div>
)}
<div className="rebours-admin-panel__img-overlay">
{error ? `Erreur: ${error}` : 'Cliquez pour changer l\u2019image'}
</div>
{uploading && <div className="rebours-admin-panel__img-uploading">Upload</div>}
<input
ref={fileInput}
type="file"
accept="image/*"
style={{ display: 'none' }}
onChange={onFile}
/>
</div>
)
}