@@ -6,6 +6,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
66import { useRouter } from '@/i18n/routing' ;
77import { CATEGORIES , COLORS , PRODUCT_TYPES , SIZES } from '@/lib/config/catalog' ;
88import { logError } from '@/lib/logging' ;
9+ import type { AdminProductPhotoPlan } from '@/lib/validation/shop' ;
910import type { ProductAdminInput , ProductImage } from '@/lib/validation/shop' ;
1011
1112const localSlugify = ( input : string ) : string => {
@@ -46,9 +47,9 @@ type UiPriceRow = {
4647 originalPrice : string ;
4748} ;
4849
49- type UiPhoto = {
50+ export type UiPhoto = {
5051 key : string ;
51- source : 'existing' | 'new' ;
52+ source : 'existing' | 'legacy' | ' new';
5253 imageId ?: string ;
5354 uploadId ?: string ;
5455 previewUrl : string ;
@@ -152,7 +153,54 @@ function normalizeUiPhotos(photos: UiPhoto[]): UiPhoto[] {
152153 } ) ) ;
153154}
154155
155- function ensureUiPhotos ( fromInitial : {
156+ type SerializableUiPhoto =
157+ | ( UiPhoto & { source : 'existing' ; imageId : string } )
158+ | ( UiPhoto & { source : 'new' ; uploadId : string ; file ?: File } ) ;
159+
160+ function isSerializableUiPhoto ( photo : UiPhoto ) : photo is SerializableUiPhoto {
161+ if ( photo . source === 'existing' ) {
162+ return typeof photo . imageId === 'string' && photo . imageId . trim ( ) . length > 0 ;
163+ }
164+
165+ if ( photo . source === 'new' ) {
166+ return (
167+ typeof photo . uploadId === 'string' && photo . uploadId . trim ( ) . length > 0
168+ ) ;
169+ }
170+
171+ return false ;
172+ }
173+
174+ export function buildPhotoPlanSubmission ( photos : UiPhoto [ ] ) : {
175+ photoPlan ?: AdminProductPhotoPlan ;
176+ newPhotos : Array < UiPhoto & { source : 'new' ; uploadId : string ; file : File } > ;
177+ } {
178+ const serializablePhotos = photos . filter ( isSerializableUiPhoto ) ;
179+
180+ if ( serializablePhotos . length === 0 ) {
181+ return { photoPlan : undefined , newPhotos : [ ] } ;
182+ }
183+
184+ const primaryIndex = serializablePhotos . findIndex ( photo => photo . isPrimary ) ;
185+ const effectivePrimaryIndex = primaryIndex >= 0 ? primaryIndex : 0 ;
186+
187+ const photoPlan = serializablePhotos . map ( ( photo , index ) => ( {
188+ imageId : photo . source === 'existing' ? photo . imageId : undefined ,
189+ uploadId : photo . source === 'new' ? photo . uploadId : undefined ,
190+ isPrimary : index === effectivePrimaryIndex ,
191+ } ) ) ;
192+
193+ const newPhotos = serializablePhotos . filter (
194+ (
195+ photo
196+ ) : photo is UiPhoto & { source : 'new' ; uploadId : string ; file : File } =>
197+ photo . source === 'new' && Boolean ( photo . file )
198+ ) ;
199+
200+ return { photoPlan, newPhotos } ;
201+ }
202+
203+ export function ensureUiPhotos ( fromInitial : {
156204 images ?: ProductImage [ ] ;
157205 imageUrl ?: string ;
158206} ) : UiPhoto [ ] {
@@ -177,8 +225,7 @@ function ensureUiPhotos(fromInitial: {
177225 return [
178226 {
179227 key : 'legacy-image' ,
180- source : 'existing' ,
181- imageId : 'legacy-image' ,
228+ source : 'legacy' ,
182229 previewUrl : fromInitial . imageUrl ,
183230 isPrimary : true ,
184231 } ,
@@ -507,29 +554,18 @@ export function ProductForm({
507554 formData . append ( 'isActive' , isActive ? 'true' : 'false' ) ;
508555 formData . append ( 'isFeatured' , isFeatured ? 'true' : 'false' ) ;
509556
510- const photoPlan = photos . map ( photo => ( {
511- imageId : photo . source === 'existing' ? photo . imageId : undefined ,
512- uploadId : photo . source === 'new' ? photo . uploadId : undefined ,
513- isPrimary : photo . isPrimary ,
514- } ) ) ;
515-
516- const newPhotos = photos . filter (
517- (
518- photo
519- ) : photo is UiPhoto & { source : 'new' ; uploadId : string ; file : File } =>
520- photo . source === 'new' &&
521- Boolean ( photo . uploadId ) &&
522- Boolean ( photo . file )
523- ) ;
557+ const { photoPlan, newPhotos } = buildPhotoPlanSubmission ( photos ) ;
524558
525- formData . append ( 'photoPlan' , JSON . stringify ( photoPlan ) ) ;
526- formData . append (
527- 'newImageUploadIds' ,
528- JSON . stringify ( newPhotos . map ( photo => photo . uploadId ) )
529- ) ;
530- newPhotos . forEach ( photo => {
531- formData . append ( 'newImages' , photo . file ) ;
532- } ) ;
559+ if ( photoPlan ?. length ) {
560+ formData . append ( 'photoPlan' , JSON . stringify ( photoPlan ) ) ;
561+ formData . append (
562+ 'newImageUploadIds' ,
563+ JSON . stringify ( newPhotos . map ( photo => photo . uploadId ) )
564+ ) ;
565+ newPhotos . forEach ( photo => {
566+ formData . append ( 'newImages' , photo . file ) ;
567+ } ) ;
568+ }
533569
534570 if ( ! csrfToken ) {
535571 setError ( 'Security token missing. Refresh the page and retry.' ) ;
@@ -1127,7 +1163,11 @@ export function ProductForm({
11271163 </ span >
11281164 ) : null }
11291165 < span className = "text-muted-foreground text-xs" >
1130- { photo . source === 'existing' ? 'Saved' : 'New upload' }
1166+ { photo . source === 'existing'
1167+ ? 'Saved'
1168+ : photo . source === 'legacy'
1169+ ? 'Legacy image'
1170+ : 'New upload' }
11311171 </ span >
11321172 </ div >
11331173
0 commit comments