@@ -4,14 +4,16 @@ import Link from "next/link";
44import { useState , useEffect } from "react" ;
55import { useRouter } from "next/navigation" ;
66import { useParams } from "next/navigation" ;
7- import {
7+ import {
88 ArrowLeft ,
99 Save ,
1010 Plus ,
1111 Trash2 ,
1212 AlertCircle ,
1313 CheckCircle2 ,
14- Users
14+ ImagePlus ,
15+ Users ,
16+ X
1517} from "lucide-react" ;
1618import "../../../../../dsoc/styles.css" ;
1719
@@ -38,7 +40,9 @@ export default function EditProjectPage() {
3840 const [ availableMentors , setAvailableMentors ] = useState < MentorOption [ ] > ( [ ] ) ;
3941 const [ imageFile , setImageFile ] = useState < File | null > ( null ) ;
4042 const [ imagePreview , setImagePreview ] = useState < string > ( '' ) ;
41-
43+ const [ galleryUploading , setGalleryUploading ] = useState ( false ) ;
44+ const [ galleryError , setGalleryError ] = useState ( '' ) ;
45+
4246 const [ formData , setFormData ] = useState ( {
4347 title : '' ,
4448 description : '' ,
@@ -60,7 +64,8 @@ export default function EditProjectPage() {
6064 learningOutcomes : [ '' ] ,
6165 season : '2026' ,
6266 status : 'draft' ,
63- featuredImage : ''
67+ featuredImage : '' ,
68+ gallery : [ ] as string [ ]
6469 } ) ;
6570
6671 useEffect ( ( ) => {
@@ -121,7 +126,8 @@ export default function EditProjectPage() {
121126 learningOutcomes : project . learningOutcomes && project . learningOutcomes . length > 0 ? project . learningOutcomes : [ '' ] ,
122127 season : project . season || '2025' ,
123128 status : project . status || 'draft' ,
124- featuredImage : project . featuredImage || project . imageUrl || ''
129+ featuredImage : project . featuredImage || project . imageUrl || '' ,
130+ gallery : Array . isArray ( project . gallery ) ? project . gallery . filter ( Boolean ) : [ ]
125131 } ) ;
126132 } else {
127133 setError ( data . error || 'Failed to load project' ) ;
@@ -199,6 +205,35 @@ export default function EditProjectPage() {
199205 return uploadData . url as string ;
200206 } ;
201207
208+ const handleGalleryAdd = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
209+ const files = Array . from ( e . target . files || [ ] ) ;
210+ if ( files . length === 0 ) return ;
211+
212+ setGalleryError ( '' ) ;
213+ setGalleryUploading ( true ) ;
214+
215+ try {
216+ const uploaded = await Promise . all ( files . map ( uploadImageToCloudinary ) ) ;
217+ setFormData ( ( current ) => ( {
218+ ...current ,
219+ gallery : [ ...current . gallery , ...uploaded ] ,
220+ } ) ) ;
221+ } catch ( err ) {
222+ console . error ( 'Gallery upload failed:' , err ) ;
223+ setGalleryError ( err instanceof Error ? err . message : 'Failed to upload one or more images' ) ;
224+ } finally {
225+ setGalleryUploading ( false ) ;
226+ e . target . value = '' ;
227+ }
228+ } ;
229+
230+ const handleGalleryRemove = ( index : number ) => {
231+ setFormData ( ( current ) => ( {
232+ ...current ,
233+ gallery : current . gallery . filter ( ( _ , i ) => i !== index ) ,
234+ } ) ) ;
235+ } ;
236+
202237 const handleSubmit = async ( e : React . FormEvent ) => {
203238 e . preventDefault ( ) ;
204239 setError ( '' ) ;
@@ -238,7 +273,8 @@ export default function EditProjectPage() {
238273 season : formData . season ,
239274 status : formData . status ,
240275 featuredImage,
241- imageUrl : featuredImage
276+ imageUrl : featuredImage ,
277+ gallery : formData . gallery
242278 } )
243279 } ) ;
244280
@@ -375,6 +411,52 @@ export default function EditProjectPage() {
375411 </ div >
376412 ) }
377413 </ div >
414+
415+ < div >
416+ < label className = "block font-bold text-sm mb-2 flex items-center gap-2" >
417+ < ImagePlus className = "w-4 h-4" />
418+ Additional Images (Gallery)
419+ </ label >
420+ < p className = "text-xs text-muted-foreground mb-2" >
421+ Optional. Shown on the project detail page below the cover.
422+ </ p >
423+ < input
424+ type = "file"
425+ accept = "image/*"
426+ multiple
427+ onChange = { handleGalleryAdd }
428+ disabled = { galleryUploading }
429+ className = "neo-brutal-input"
430+ />
431+ { galleryUploading && (
432+ < p className = "mt-2 text-sm text-muted-foreground" > Uploading...</ p >
433+ ) }
434+ { galleryError && (
435+ < p className = "mt-2 text-sm text-[var(--dsoc-pink)] font-bold" > { galleryError } </ p >
436+ ) }
437+ { formData . gallery . length > 0 && (
438+ < div className = "mt-3 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3" >
439+ { formData . gallery . map ( ( url , index ) => (
440+ < div key = { url + index } className = "relative group" >
441+ { /* eslint-disable-next-line @next/next/no-img-element */ }
442+ < img
443+ src = { url }
444+ alt = { `Gallery image ${ index + 1 } ` }
445+ className = "w-full h-28 object-cover border-4 border-[var(--dsoc-dark)]"
446+ />
447+ < button
448+ type = "button"
449+ onClick = { ( ) => handleGalleryRemove ( index ) }
450+ aria-label = "Remove image"
451+ className = "absolute -top-2 -right-2 w-7 h-7 bg-[var(--dsoc-pink)] text-white border-4 border-[var(--dsoc-dark)] flex items-center justify-center"
452+ >
453+ < X className = "w-3 h-3" />
454+ </ button >
455+ </ div >
456+ ) ) }
457+ </ div >
458+ ) }
459+ </ div >
378460 </ div >
379461
380462 { /* Links */ }
0 commit comments