@@ -5,6 +5,8 @@ import Navbar from "@/components/Navbar";
55import Footer from "@/components/Footer" ;
66import { Loader2 , Upload , X } from "lucide-react" ;
77import { motion } from "framer-motion" ;
8+ import ImageViewer from "@/components/ImageViewer" ;
9+
810
911export default function ContractorDetail ( ) {
1012 const { id } = useParams < { id : string } > ( ) ;
@@ -30,6 +32,13 @@ export default function ContractorDetail() {
3032 const [ message , setMessage ] = useState ( "" ) ;
3133 const [ date , setDate ] = useState ( "" ) ;
3234
35+ // image viewer states
36+ const [ viewerOpen , setViewerOpen ] = useState ( false ) ;
37+ const [ currentIndex , setCurrentIndex ] = useState ( 0 ) ;
38+
39+
40+
41+
3342 // Load logged-in user
3443 useEffect ( ( ) => {
3544 supabase . auth . getUser ( ) . then ( ( { data } ) => setCurrentUser ( data . user ) ) ;
@@ -64,10 +73,11 @@ export default function ContractorDetail() {
6473 const { data } = await supabase . storage . from ( "contractor" ) . list ( folder ) ;
6574
6675 const urls =
67- data ?. map ( ( file ) => {
68- return supabase . storage . from ( "contractor" ) . getPublicUrl ( `${ folder } /${ file . name } ` )
69- . data . publicUrl ;
70- } ) || [ ] ;
76+ data
77+ ?. filter ( ( file ) => ! file . name . startsWith ( "profile_" ) ) // 🚫 exclude profile photos
78+ . map ( ( file ) =>
79+ supabase . storage . from ( "contractor" ) . getPublicUrl ( `${ folder } /${ file . name } ` ) . data . publicUrl
80+ ) || [ ] ;
7181
7282 setPhotos ( urls ) ;
7383 } ;
@@ -78,39 +88,81 @@ export default function ContractorDetail() {
7888
7989 // Upload photo
8090 const handleUpload = async ( e : any ) => {
81- if ( ! canUpload ) return alert ( "Only the contractor can upload images." ) ;
91+ if ( ! canUpload ) return alert ( "Only the contractor can upload images." ) ;
92+
93+ const file = e . target . files ?. [ 0 ] ;
94+ if ( ! file ) return ;
95+
96+ setUploading ( true ) ;
97+
98+ try {
99+ const folder = `contractor_${ id } ` ;
100+ const filePath = `${ folder } /${ Date . now ( ) } _${ file . name } ` ;
101+
102+ const { error } = await supabase . storage
103+ . from ( "contractor" ) // your bucket name
104+ . upload ( filePath , file ) ;
105+
106+ if ( error ) {
107+ console . error ( error ) ;
108+ alert ( "Upload failed. Check console." ) ;
109+ return ;
110+ }
111+
112+ // Get public URL
113+ const { data : publicData } = supabase . storage ``
114+ . from ( "contractor" )
115+ . getPublicUrl ( filePath ) ;
116+
117+ setPhotos ( ( prev ) => [ ...prev , publicData . publicUrl ] ) ; // update UI immediately
118+ alert ( "Photo uploaded successfully!" ) ;
119+ } finally {
120+ setUploading ( false ) ;
121+ }
122+ } ;
82123
124+ const handleProfilePhotoUpdate = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
83125 const file = e . target . files ?. [ 0 ] ;
84- if ( ! file ) return ;
126+ if ( ! file || ! id ) return ;
85127
86128 setUploading ( true ) ;
87129
88130 try {
89- const folder = `contractor_${ id } ` ;
90- const filePath = `${ folder } /${ Date . now ( ) } _${ file . name } ` ;
131+ const fileExt = file . name . split ( "." ) . pop ( ) ;
132+ const fileName = `profile_${ Date . now ( ) } .${ fileExt } ` ;
133+ const filePath = `contractor_${ id } /${ fileName } ` ;
91134
92- const { error } = await supabase . storage
93- . from ( "contractor" ) // your bucket name
94- . upload ( filePath , file ) ;
135+ const { error : uploadError } = await supabase . storage
136+ . from ( "contractor" )
137+ . upload ( filePath , file , { upsert : true } ) ;
95138
96- if ( error ) {
97- console . error ( error ) ;
98- alert ( "Upload failed. Check console." ) ;
139+ if ( uploadError ) {
140+ alert ( "Error updating profile photo" ) ;
99141 return ;
100142 }
101143
102- // Get public URL
103- const { data : publicData } = supabase . storage ``
104- . from ( "contractor" )
105- . getPublicUrl ( filePath ) ;
144+ const { data : publicUrl } = supabase . storage . from ( "contractor" ) . getPublicUrl ( filePath ) ;
145+
146+ if ( ! publicUrl ?. publicUrl ) return ;
147+
148+ const { error : dbError } = await supabase
149+ . from ( "contractors_register" )
150+ . update ( { image_url : publicUrl . publicUrl } )
151+ . eq ( "id" , Number ( id ) ) ;
152+
153+ if ( dbError ) {
154+ alert ( "Failed to update profile photo." ) ;
155+ return ;
156+ }
106157
107- setPhotos ( ( prev ) => [ ...prev , publicData . publicUrl ] ) ; // update UI immediately
108- alert ( "Photo uploaded successfully !" ) ;
158+ setContractor ( ( prev : any ) => ( { ...prev , image_url : publicUrl . publicUrl } ) ) ;
159+ alert ( "Profile photo updated !" ) ;
109160 } finally {
110161 setUploading ( false ) ;
111162 }
112163} ;
113164
165+
114166 // 📌 New insert logic using `contractor_request`
115167 const handleRequest = async ( ) => {
116168 if ( ! clientName || ! phone || ! projectType || ! location ) {
@@ -172,6 +224,17 @@ export default function ContractorDetail() {
172224 return (
173225 < div className = "bg-white min-h-screen" >
174226 < Navbar />
227+ { /* Image Viewer Modal */ }
228+ { viewerOpen && (
229+ < ImageViewer
230+ photos = { photos }
231+ currentIndex = { currentIndex }
232+ setCurrentIndex = { setCurrentIndex }
233+ onClose = { ( ) => setViewerOpen ( false ) }
234+ canDelete = { false }
235+ onDelete = { ( ) => { } }
236+ />
237+ ) }
175238
176239 { /* Banner */ }
177240 < div className = "w-full h-72 md:h-96 bg-gray-200 relative overflow-hidden" >
@@ -185,10 +248,32 @@ export default function ContractorDetail() {
185248 { /* Profile Card */ }
186249 < div className = "max-w-6xl mx-auto p-6 bg-white shadow-xl rounded-2xl -mt-32 md:-mt-24 relative z-10" >
187250 < div className = "flex flex-col md:flex-row gap-6 items-start" >
188- < img
189- src = { contractor . image_url || "/placeholder-user.jpg" }
190- className = "w-40 h-40 rounded-xl object-cover border-4 border-white shadow-lg"
191- />
251+ < div className = "relative group w-40 h-40" >
252+ < img
253+ src = { contractor . image_url || "/placeholder-user.jpg" }
254+ className = "w-full h-full rounded-xl object-cover border-4 border-white shadow-lg"
255+ />
256+
257+ { canUpload && (
258+ < >
259+ < label
260+ htmlFor = "profileImageUpload"
261+ className = "absolute inset-0 bg-black/50 text-white text-sm flex items-center justify-center opacity-0 group-hover:opacity-100 cursor-pointer rounded-xl transition"
262+ >
263+ Edit Photo
264+ </ label >
265+
266+ < input
267+ type = "file"
268+ id = "profileImageUpload"
269+ className = "hidden"
270+ accept = "image/*"
271+ onChange = { handleProfilePhotoUpdate }
272+ />
273+ </ >
274+ ) }
275+ </ div >
276+
192277
193278 < div >
194279 < h1 className = "text-3xl font-bold text-yellow-700" > { contractor . name } </ h1 >
@@ -244,7 +329,17 @@ export default function ContractorDetail() {
244329 ) : (
245330 < div className = "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4" >
246331 { photos . map ( ( url , i ) => (
247- < motion . img key = { i } src = { url } className = "rounded-lg object-cover h-40 w-full" whileHover = { { scale : 1.05 } } />
332+ < motion . img
333+ key = { i }
334+ src = { url }
335+ className = "rounded-lg object-cover h-40 w-full cursor-pointer"
336+ whileHover = { { scale : 1.05 } }
337+ onClick = { ( ) => {
338+ setCurrentIndex ( i ) ;
339+ setViewerOpen ( true ) ;
340+ } }
341+ />
342+
248343 ) ) }
249344 </ div >
250345 ) }
0 commit comments