Skip to content

Commit 458232f

Browse files
committed
added image preview + zoom in out in DeveloperDetails +edit profile
1 parent b430f6f commit 458232f

File tree

3 files changed

+170
-68
lines changed

3 files changed

+170
-68
lines changed

src/pages/categories/ArchitectDetail.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,15 @@ export default function ArchitectDetail() {
140140
}
141141

142142
// Generate fresh, non-cached URLs
143-
const urls = data.map((file) => {
144-
const { data: publicUrl } = supabase.storage
145-
.from("Architects")
146-
.getPublicUrl(`${folderName}/${file.name}`);
147-
148-
// prevent browser and CDN caching old files
149-
return `${publicUrl.publicUrl}?v=${Date.now()}`;
150-
});
143+
const urls = data
144+
.filter((file) => !file.name.startsWith("profile_")) // 🚫 Exclude profile images
145+
.map((file) => {
146+
const { data: publicUrl } = supabase.storage
147+
.from("Architects")
148+
.getPublicUrl(`${folderName}/${file.name}`);
149+
150+
return `${publicUrl.publicUrl}?v=${Date.now()}`;
151+
});
151152

152153
setPhotos(urls);
153154
} catch (err) {

src/pages/categories/ContractorDetail.tsx

Lines changed: 120 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Navbar from "@/components/Navbar";
55
import Footer from "@/components/Footer";
66
import { Loader2, Upload, X } from "lucide-react";
77
import { motion } from "framer-motion";
8+
import ImageViewer from "@/components/ImageViewer";
9+
810

911
export 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
)}

src/pages/categories/DeveloperDetail.tsx

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -149,35 +149,35 @@ export default function DeveloperDetail() {
149149
};
150150

151151

152-
const submitRequest = async () => {
153-
if (!currentUser) {
154-
alert("Please login first!");
155-
return;
156-
}
157-
158-
const { error } = await supabase.from("Developer_Request").insert([
159-
{
160-
name: formData.name,
161-
email: currentUser.email,
162-
phone_number: formData.phone,
163-
company: "-",
164-
project_type: formData.project_type,
165-
location: formData.location,
166-
estimated_budget: formData.budget,
167-
details: formData.message,
168-
const_id: developer.id,
169-
},
170-
]);
171-
172-
if (error) {
173-
console.log(error);
174-
alert("Request failed!");
175-
return;
176-
}
177-
178-
alert("Request sent successfully!");
179-
setShowRequestModal(false);
180-
};
152+
const submitRequest = async () => {
153+
if (!currentUser) {
154+
alert("Please login first!");
155+
return;
156+
}
157+
158+
const { error } = await supabase.from("Developer_Request").insert([
159+
{
160+
name: formData.name,
161+
email: currentUser.email,
162+
phone_number: formData.phone,
163+
company: "-",
164+
project_type: formData.project_type,
165+
location: formData.location,
166+
estimated_budget: formData.budget,
167+
details: formData.message,
168+
const_id: developer.id,
169+
},
170+
]);
171+
172+
if (error) {
173+
console.log(error);
174+
alert("Request failed!");
175+
return;
176+
}
177+
178+
alert("Request sent successfully!");
179+
setShowRequestModal(false);
180+
};
181181

182182

183183
if (loading)
@@ -191,6 +191,8 @@ const submitRequest = async () => {
191191
<div className="bg-white min-h-screen">
192192
<Navbar />
193193

194+
195+
194196
{/* DONE BUTTON */}
195197
{canEdit && (
196198
<div className="w-full flex justify-end px-6 mt-3">
@@ -268,10 +270,14 @@ const submitRequest = async () => {
268270
{photos.map((photo) => (
269271
<div key={photo.path} className="relative group">
270272
<motion.img
271-
src={photo.url}
272-
className="rounded-lg object-cover h-40 w-full"
273-
whileHover={{ scale: 1.05 }}
274-
/>
273+
src={photo.url}
274+
className="rounded-lg object-cover h-40 w-full cursor-pointer"
275+
whileHover={{ scale: 1.05 }}
276+
onClick={() => {
277+
setCurrentIndex(photos.findIndex((p) => p.url === photo.url));
278+
setViewerOpen(true);
279+
}}
280+
/>
275281

276282
{canEdit && (
277283
<button
@@ -313,8 +319,8 @@ const submitRequest = async () => {
313319
key === "project_type"
314320
? "Project Type (Home, Office)"
315321
: key === "message"
316-
? "Message (optional)"
317-
: key
322+
? "Message (optional)"
323+
: key
318324
.replace("_", " ")
319325
.replace(/\b\w/g, (l) => l.toUpperCase())
320326
}

0 commit comments

Comments
 (0)