From 94fdb37dc3db490f3436241c187bfec553f906da Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Tue, 21 Apr 2026 14:17:26 +0200 Subject: [PATCH] fix(admin): render visual editor as ui field inside default form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The views.edit.default override replaced Payload's
wrapper, so useField had no form-state context — every field returned undefined and setValue threw "N is not a function" during autosave. Flatten the collection into a single ui field that renders ProductPreviewEditor, with every real field marked admin.hidden:true. The panel now lives inside Payload's default form, so useField gets real values and autosave works. Co-Authored-By: Claude Opus 4.7 --- nextjs/src/collections/Products.ts | 340 ++++++++++++++--------------- 1 file changed, 158 insertions(+), 182 deletions(-) diff --git a/nextjs/src/collections/Products.ts b/nextjs/src/collections/Products.ts index bd38c1d..fac42c2 100644 --- a/nextjs/src/collections/Products.ts +++ b/nextjs/src/collections/Products.ts @@ -10,15 +10,6 @@ export const Products: CollectionConfig = { useAsTitle: 'productDisplayName', defaultColumns: ['productDisplayName', 'index', 'type', 'price', 'isPublished'], group: 'Contenu', - components: { - views: { - edit: { - default: { - Component: '@/components/admin/ProductPreviewEditor#default', - }, - }, - }, - }, }, access: { read: () => true, @@ -40,183 +31,168 @@ export const Products: CollectionConfig = { }, fields: [ { - type: 'tabs', - tabs: [ - { - label: 'Identité', - fields: [ - { - name: 'productDisplayName', - label: 'Nom affiché', - type: 'text', - required: true, - admin: { description: 'Le nom visible par le public (ex: Solar Altar)' }, - }, - { - name: 'name', - label: 'Nom technique', - type: 'text', - required: true, - admin: { description: 'Nom interne sans espaces (ex: Solar_Altar)' }, - }, - { - name: 'slug', - label: 'URL (slug)', - type: 'text', - required: true, - unique: true, - index: true, - admin: { description: 'Ex: solar-altar → /collection/solar-altar' }, - }, - { - name: 'index', - label: 'Index projet', - type: 'text', - required: true, - admin: { description: 'Ex: PROJET_001' }, - }, - { - name: 'type', - label: 'Type', - type: 'text', - required: true, - admin: { description: 'Ex: LAMPE DE TABLE' }, - }, - { - name: 'materials', - label: 'Matériaux', - type: 'text', - required: true, - }, - { - name: 'year', - label: 'Année', - type: 'text', - required: true, - defaultValue: '2026', - }, - { - name: 'status', - label: 'Statut', - type: 'text', - required: true, - admin: { description: 'Ex: PROTOTYPE [80%]' }, - }, - { - name: 'sortOrder', - label: 'Ordre d\u2019affichage', - type: 'number', - defaultValue: 0, - }, - { - name: 'isPublished', - label: 'Publié', - type: 'checkbox', - defaultValue: true, - }, - ], + name: 'previewPanel', + type: 'ui', + admin: { + components: { + Field: '@/components/admin/ProductPreviewEditor#default', }, + }, + }, + { + name: 'productDisplayName', + label: 'Nom affiché', + type: 'text', + required: true, + admin: { hidden: true }, + }, + { + name: 'name', + label: 'Nom technique', + type: 'text', + required: true, + admin: { hidden: true }, + }, + { + name: 'slug', + label: 'URL (slug)', + type: 'text', + required: true, + unique: true, + index: true, + admin: { hidden: true }, + }, + { + name: 'index', + label: 'Index projet', + type: 'text', + required: true, + admin: { hidden: true }, + }, + { + name: 'type', + label: 'Type', + type: 'text', + required: true, + admin: { hidden: true }, + }, + { + name: 'materials', + label: 'Matériaux', + type: 'text', + required: true, + admin: { hidden: true }, + }, + { + name: 'year', + label: 'Année', + type: 'text', + required: true, + defaultValue: '2026', + admin: { hidden: true }, + }, + { + name: 'status', + label: 'Statut', + type: 'text', + required: true, + admin: { hidden: true }, + }, + { + name: 'sortOrder', + label: 'Ordre d\u2019affichage', + type: 'number', + defaultValue: 0, + admin: { hidden: true }, + }, + { + name: 'isPublished', + label: 'Publié', + type: 'checkbox', + defaultValue: true, + admin: { hidden: true }, + }, + { + name: 'description', + label: 'Description', + type: 'textarea', + required: true, + admin: { hidden: true }, + }, + { + name: 'specs', + label: 'Spécifications', + type: 'textarea', + admin: { hidden: true }, + }, + { + name: 'notes', + label: 'Notes', + type: 'textarea', + admin: { hidden: true }, + }, + { + name: 'images', + label: 'Images', + type: 'array', + minRows: 1, + required: true, + admin: { hidden: true }, + fields: [ { - label: 'Contenu', - fields: [ - { - name: 'description', - label: 'Description', - type: 'textarea', - required: true, - }, - { - name: 'specs', - label: 'Spécifications', - type: 'textarea', - }, - { - name: 'notes', - label: 'Notes', - type: 'textarea', - }, - ], - }, - { - label: 'Images', - fields: [ - { - name: 'images', - label: 'Images', - type: 'array', - minRows: 1, - required: true, - fields: [ - { - name: 'image', - type: 'upload', - relationTo: 'media', - required: true, - }, - ], - }, - ], - }, - { - label: 'Commerce', - fields: [ - { - name: 'price', - label: 'Prix (en centimes)', - type: 'number', - admin: { - description: '180000 = 1 800 EUR. Vide = non disponible à la vente.', - }, - }, - { - name: 'currency', - label: 'Devise', - type: 'select', - defaultValue: 'EUR', - options: [ - { label: 'EUR', value: 'EUR' }, - { label: 'USD', value: 'USD' }, - ], - }, - { - name: 'availability', - label: 'Disponibilité (schema.org)', - type: 'select', - defaultValue: 'https://schema.org/InStock', - options: [ - { label: 'En stock', value: 'https://schema.org/InStock' }, - { label: 'Disponibilité limitée', value: 'https://schema.org/LimitedAvailability' }, - { label: 'Sur commande', value: 'https://schema.org/PreOrder' }, - { label: 'Indisponible', value: 'https://schema.org/OutOfStock' }, - ], - }, - { - name: 'stripeProductID', - type: 'text', - admin: { - readOnly: true, - description: 'Synchronisé automatiquement avec Stripe', - }, - }, - ], - }, - { - label: 'SEO', - fields: [ - { - name: 'seoTitle', - label: 'Titre SEO', - type: 'text', - admin: { description: 'Laissez vide pour utiliser le nom du produit' }, - }, - { - name: 'seoDescription', - label: 'Description SEO', - type: 'textarea', - }, - ], + name: 'image', + type: 'upload', + relationTo: 'media', + required: true, }, ], }, + { + name: 'price', + label: 'Prix (en centimes)', + type: 'number', + admin: { hidden: true }, + }, + { + name: 'currency', + label: 'Devise', + type: 'select', + defaultValue: 'EUR', + options: [ + { label: 'EUR', value: 'EUR' }, + { label: 'USD', value: 'USD' }, + ], + admin: { hidden: true }, + }, + { + name: 'availability', + label: 'Disponibilité (schema.org)', + type: 'select', + defaultValue: 'https://schema.org/InStock', + options: [ + { label: 'En stock', value: 'https://schema.org/InStock' }, + { label: 'Disponibilité limitée', value: 'https://schema.org/LimitedAvailability' }, + { label: 'Sur commande', value: 'https://schema.org/PreOrder' }, + { label: 'Indisponible', value: 'https://schema.org/OutOfStock' }, + ], + admin: { hidden: true }, + }, + { + name: 'stripeProductID', + type: 'text', + admin: { hidden: true, readOnly: true }, + }, + { + name: 'seoTitle', + label: 'Titre SEO', + type: 'text', + admin: { hidden: true }, + }, + { + name: 'seoDescription', + label: 'Description SEO', + type: 'textarea', + admin: { hidden: true }, + }, ], }