@devvistatech/devvista-kit 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +40 -0
  2. package/app/ClientLayout.tsx +66 -0
  3. package/app/about/page.tsx +11 -248
  4. package/app/adRequest/page.tsx +101 -25
  5. package/app/admin-profile/page.tsx +123 -0
  6. package/app/analytics/page.tsx +41 -5
  7. package/app/api/about/route.ts +2 -18
  8. package/app/api/adRequest/route.ts +7 -27
  9. package/app/api/analytics/[reportType]/route.ts +1 -64
  10. package/app/api/bio/route.ts +1 -17
  11. package/app/api/blog/route.ts +1 -19
  12. package/app/api/contacts/route.ts +1 -46
  13. package/app/api/files/route.ts +1 -15
  14. package/app/api/gallery-data/route.ts +53 -61
  15. package/app/api/schedule/route.ts +5 -21
  16. package/app/api/signup/route.ts +129 -0
  17. package/app/api/sync-user/route.ts +268 -94
  18. package/app/api/verify-admin/route.ts +46 -0
  19. package/app/blog/[id]/page.tsx +71 -52
  20. package/app/blog/page.tsx +43 -10
  21. package/app/favicon.ico +0 -0
  22. package/app/gallery/page.tsx +27 -6
  23. package/app/layout.tsx +31 -82
  24. package/app/page.tsx +20 -311
  25. package/app/products/constants/product.ts +27 -0
  26. package/app/products/page.tsx +296 -0
  27. package/app/products/productOne/page.tsx +266 -0
  28. package/app/products/productTwo/page.tsx +272 -0
  29. package/app/schedule/page.tsx +78 -40
  30. package/bin/init.js +0 -12
  31. package/components/addOns/functional/ClassList.tsx +21 -17
  32. package/components/addOns/functional/ProductList.tsx +1027 -0
  33. package/components/addOns/functional/aboutSections/AboutSection.tsx +107 -70
  34. package/components/addOns/functional/aboutSections/constants/aboutSection.ts +9 -4
  35. package/components/addOns/functional/banner/Banner.tsx +150 -0
  36. package/components/addOns/functional/banner/BannerDashboard.tsx +283 -0
  37. package/components/addOns/functional/bioSections/BioEditor.tsx +471 -0
  38. package/components/addOns/functional/bioSections/constants/bioEditor.ts +36 -0
  39. package/components/addOns/functional/blogSections/BlogDashboard.tsx +1 -1
  40. package/components/addOns/functional/blogSections/BlogFormPopUp.tsx +2 -1
  41. package/components/addOns/functional/{ImageDescCarousel.tsx → carousels/ImageDescCarousel.tsx} +166 -57
  42. package/components/addOns/functional/carousels/ProductDescCarousel.tsx +1129 -0
  43. package/components/addOns/functional/{ScheduleCarousel.tsx → carousels/ScheduleCarousel.tsx} +110 -50
  44. package/components/addOns/functional/carousels/constants.ts/productDescCarousel.ts +197 -0
  45. package/components/addOns/functional/carousels/constants.ts/scheduleCarousel.ts +20 -0
  46. package/components/addOns/functional/contactsDashboard/ContactsDashboard.tsx +1 -1
  47. package/components/addOns/functional/fileUploaders/FileUploader.tsx +437 -0
  48. package/components/addOns/functional/fileUploaders/constants/fileUploader.ts +45 -0
  49. package/components/addOns/functional/galleries/GalleryComplex.tsx +468 -267
  50. package/components/addOns/functional/galleries/GallerySimple.tsx +78 -50
  51. package/components/addOns/functional/galleries/ThreeSetGallery.tsx +260 -0
  52. package/components/addOns/functional/schedules/ScheduleGridOne.tsx +22 -8
  53. package/components/addOns/functional/schedules/ScheduleGridTwo.tsx +12 -7
  54. package/components/addOns/functional/schedules/ScheduleGridTwoBasic.tsx +12 -7
  55. package/components/addOns/non-functional/SampleCarousel.tsx +3 -3
  56. package/components/addOns/non-functional/ThreeSetGallery.tsx +3 -3
  57. package/components/addOns/non-functional/featureSections/FeaturesSection.tsx +74 -0
  58. package/components/addOns/non-functional/featureSections/constants/featuresSection.ts +30 -0
  59. package/components/addOns/non-functional/{Heros/HeroSection.tsx → heros/HomeHero.tsx} +17 -15
  60. package/components/addOns/non-functional/heros/ProductHero.tsx +111 -0
  61. package/components/addOns/non-functional/heros/constants/hero.ts +62 -0
  62. package/components/addOns/non-functional/imageCarousels/ProductSlider.tsx +6 -6
  63. package/components/addOns/non-functional/imageCarousels/ProgramCarousel.tsx +10 -10
  64. package/components/footers/footer.tsx +161 -198
  65. package/components/other/admin-menu.tsx +1 -1
  66. package/lib/auth/auth-context.tsx +225 -0
  67. package/lib/auth/auth-utils.tsx +30 -0
  68. package/lib/constants/adRequest.ts +199 -56
  69. package/lib/constants/admin-profile.ts +12 -0
  70. package/lib/constants/page.ts +15 -15
  71. package/lib/google/google-analytics-tracking.tsx +44 -0
  72. package/lib/types.ts +235 -0
  73. package/lib/utils/compressImage.tsx +32 -0
  74. package/middleware.ts +9 -5
  75. package/next.config.js +1 -1
  76. package/package.json +3 -2
  77. package/public/images/test.png +0 -0
  78. package/components/addOns/functional/BioEditor.tsx +0 -447
  79. package/components/addOns/functional/FileUploader.tsx +0 -295
  80. package/components/addOns/non-functional/FeaturesSection.tsx +0 -63
  81. package/components/types.ts +0 -50
  82. package/lib/auth-context.tsx +0 -131
  83. package/lib/verify-user.ts +0 -118
  84. /package/lib/{google-analytics.tsx → google/google-analytics.tsx} +0 -0
@@ -0,0 +1,266 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { ProductDescCarousel } from "@/components/addOns/functional/carousels/ProductDescCarousel";
5
+ import Spinner from "@/components/addOns/non-functional/spinner";
6
+ import { useStrapiAuth } from "@/lib/auth/auth-context";
7
+ import { useAuth } from "@clerk/nextjs";
8
+ import ProductHero from "@/components/addOns/non-functional/heros/ProductHero";
9
+ import { StrapiUser, UploadedImage, Category } from "@/lib/types";
10
+ import { compressImage } from "@/lib/utils/compressImage";
11
+ import { PRODUCT_PAGE } from "../constants/product";
12
+
13
+ const PRODUCT_CATEGORIES: Category[] = [
14
+ "indoor",
15
+ "outdoor",
16
+ "commercial",
17
+
18
+ ];
19
+
20
+ export default function BaggedCementProducts() {
21
+ const { user, authLoading, checkSession } = useStrapiAuth();
22
+ const { isSignedIn, getToken } = useAuth();
23
+ const [isLoading, setIsLoading] = useState(true);
24
+ const [error, setError] = useState<string | null>(null);
25
+ const [uploadedImages, setUploadedImages] = useState<UploadedImage[]>([]);
26
+ const [galleryUser, setUser] = useState<StrapiUser | null>(null);
27
+ const [subCategories, setSubCategories] = useState<string[]>([]);
28
+
29
+ const isAdmin = isSignedIn && !!user?.businessAdminId;
30
+
31
+ useEffect(() => {
32
+ setUser(user);
33
+ }, [user, authLoading, isSignedIn]);
34
+
35
+ useEffect(() => {
36
+ window.scrollTo(0, 0);
37
+ }, []);
38
+
39
+ useEffect(() => {
40
+ const handleLogin = () => checkSession();
41
+ const handleLogout = () => checkSession();
42
+
43
+ window.addEventListener("user-login", handleLogin);
44
+ window.addEventListener("user-logout", handleLogout);
45
+
46
+ return () => {
47
+ window.removeEventListener("user-login", handleLogin);
48
+ window.removeEventListener("user-logout", handleLogout);
49
+ };
50
+ }, [checkSession]);
51
+
52
+ const fetchInitialData = useCallback(async () => {
53
+ try {
54
+ setIsLoading(true);
55
+
56
+ const headers: HeadersInit = { "Content-Type": "application/json" };
57
+ if (isSignedIn) {
58
+ const token = await getToken();
59
+ if (!token) {
60
+ throw new Error(PRODUCT_PAGE.ERRORS.NO_AUTH_TOKEN);
61
+ }
62
+ headers.Authorization = `Bearer ${token}`;
63
+ }
64
+
65
+ const response = await fetch("/api/gallery-data", { headers });
66
+ if (!response.ok) {
67
+ const errorText = await response.text();
68
+ console.error("Gallery fetch failed:", { status: response.status, errorText });
69
+ throw new Error(PRODUCT_PAGE.ERRORS.FETCH_IMAGES_FAILED.replace("${errorText}", errorText));
70
+ }
71
+
72
+ const { data: images, meta }: { data: UploadedImage[]; meta?: { pagination?: { total: number } } } = await response.json();
73
+
74
+ if (meta && meta.pagination && meta.pagination.total > 100) {
75
+ setError(PRODUCT_PAGE.ERRORS.PAGINATION_WARNING);
76
+ }
77
+
78
+ setUploadedImages(images || []);
79
+ const subCats: string[] = images
80
+ .filter((img) => img.category === "indoor" && typeof img.subCategory === "string" && img.subCategory !== "")
81
+ .reduce<string[]>((acc, img) => {
82
+ const subCategory = img.subCategory as string;
83
+ return acc.includes(subCategory) ? acc : [...acc, subCategory];
84
+ }, []);
85
+ setSubCategories(subCats);
86
+ } catch (err) {
87
+ console.error("Fetch error details:", err);
88
+ setError(err instanceof Error ? err.message : PRODUCT_PAGE.ERRORS.FETCH_ERROR);
89
+ } finally {
90
+ setIsLoading(false);
91
+ }
92
+ }, [isSignedIn, getToken]);
93
+
94
+ useEffect(() => {
95
+ fetchInitialData();
96
+ }, [fetchInitialData]);
97
+
98
+ const handleImageUpload = async (
99
+ e: React.FormEvent<HTMLFormElement>,
100
+ file: File | null,
101
+ title: string,
102
+ description: string,
103
+ category: Category,
104
+ subCategory: string
105
+ ) => {
106
+ e.preventDefault();
107
+
108
+ if (!file) {
109
+ setError(PRODUCT_PAGE.ERRORS.NO_FILE_SELECTED);
110
+ return;
111
+ }
112
+
113
+ if (!["image/jpeg", "image/png", "image/gif"].includes(file.type)) {
114
+ setError(PRODUCT_PAGE.ERRORS.INVALID_FILE_TYPE);
115
+ return;
116
+ }
117
+
118
+ if (!isAdmin) {
119
+ setError(PRODUCT_PAGE.ERRORS.UNAUTHORIZED_UPLOAD);
120
+ return;
121
+ }
122
+
123
+ if (!subCategory) {
124
+ setError("Subcategory is required");
125
+ return;
126
+ }
127
+
128
+ try {
129
+ setIsLoading(true);
130
+
131
+ const token = await getToken();
132
+ if (!token) {
133
+ setError(PRODUCT_PAGE.ERRORS.AUTHENTICATION_ERROR);
134
+ return;
135
+ }
136
+
137
+ const compressedFile = await compressImage(file);
138
+
139
+ const formData = new FormData();
140
+ formData.append("file", compressedFile);
141
+ formData.append("title", title || `Image ${new Date().toISOString()}`);
142
+ formData.append("description", description || "");
143
+ formData.append("category", category || "bagged-cement-products");
144
+ formData.append("subCategory", subCategory);
145
+
146
+ const response = await fetch("/api/gallery-data", {
147
+ method: "POST",
148
+ headers: { Authorization: `Bearer ${token}` },
149
+ body: formData,
150
+ });
151
+
152
+ if (!response.ok) {
153
+ const errorData = await response.json();
154
+ console.error("Upload error response:", errorData);
155
+ if (response.status === 401) {
156
+ setError(PRODUCT_PAGE.ERRORS.AUTHENTICATION_ERROR);
157
+ return;
158
+ }
159
+ throw new Error(PRODUCT_PAGE.ERRORS.UPLOAD_IMAGE_FAILED.replace("${response.status}", response.status.toString()));
160
+ }
161
+
162
+ const { data }: { data: UploadedImage[] } = await response.json();
163
+ setUploadedImages(data || []);
164
+ if (subCategory && !subCategories.includes(subCategory)) {
165
+ setSubCategories([...subCategories, subCategory]);
166
+ }
167
+ setError(null);
168
+ } catch (err) {
169
+ console.error("Upload error details:", err);
170
+ setError(err instanceof Error ? err.message : PRODUCT_PAGE.ERRORS.UPLOAD_IMAGE_ERROR);
171
+ } finally {
172
+ setIsLoading(false);
173
+ }
174
+ };
175
+
176
+ const handleDeleteImage = async (documentId: string) => {
177
+ if (!isAdmin) {
178
+ setError(PRODUCT_PAGE.ERRORS.UNAUTHORIZED_DELETE);
179
+ return;
180
+ }
181
+
182
+ try {
183
+ setIsLoading(true);
184
+
185
+ const token = await getToken();
186
+ if (!token) {
187
+ setError(PRODUCT_PAGE.ERRORS.AUTHENTICATION_ERROR);
188
+ return;
189
+ }
190
+
191
+ const response = await fetch("/api/gallery-data", {
192
+ method: "DELETE",
193
+ headers: {
194
+ "Content-Type": "application/json",
195
+ Authorization: `Bearer ${token}`,
196
+ },
197
+ body: JSON.stringify({ documentId }),
198
+ });
199
+
200
+ if (!response.ok) {
201
+ const errorData = await response.json();
202
+ console.error("Delete error response:", errorData);
203
+ if (response.status === 401) {
204
+ setError(PRODUCT_PAGE.ERRORS.AUTHENTICATION_ERROR);
205
+ return;
206
+ }
207
+ throw new Error(PRODUCT_PAGE.ERRORS.DELETE_IMAGE_FAILED.replace("${response.status}", response.status.toString()));
208
+ }
209
+
210
+ const { data }: { data: UploadedImage[] } = await response.json();
211
+ setUploadedImages(data || []);
212
+ const subCats: string[] = data
213
+ .filter((img) => img.category === "indoor" && typeof img.subCategory === "string" && img.subCategory !== "")
214
+ .reduce<string[]>((acc, img) => {
215
+ const subCategory = img.subCategory as string;
216
+ return acc.includes(subCategory) ? acc : [...acc, subCategory];
217
+ }, []);
218
+ setSubCategories(subCats);
219
+ setError(null);
220
+ } catch (err) {
221
+ console.error("Delete error details:", err);
222
+ setError(err instanceof Error ? err.message : PRODUCT_PAGE.ERRORS.DELETE_IMAGE_ERROR);
223
+ } finally {
224
+ setIsLoading(false);
225
+ }
226
+ };
227
+
228
+ if (isLoading || authLoading) {
229
+ return <Spinner />;
230
+ }
231
+
232
+ const imageSrc = uploadedImages[0]?.url ?? "/placeholder.jpg";
233
+
234
+ return (
235
+ <div className="min-h-screen w-full bg-white font-sans">
236
+ <div className="w-full pt-20 lg:pt-40 pb-16 relative z-10 container-padding">
237
+ {error && (
238
+ <div className="text-red-600 text-center p-4 mb-4 max-w-4xl mx-auto bg-red-50 border border-red-200 rounded-lg backdrop-blur-sm">
239
+ {error}
240
+ </div>
241
+ )}
242
+
243
+ <ProductHero />
244
+
245
+ <div className="mb-8">
246
+ <ProductDescCarousel
247
+ user={galleryUser}
248
+ uploadedImages={uploadedImages}
249
+ setUploadedImages={setUploadedImages}
250
+ error={error}
251
+ setError={setError}
252
+ isLoading={isLoading}
253
+ setIsLoading={setIsLoading}
254
+ handleImageUpload={handleImageUpload}
255
+ handleDeleteImage={handleDeleteImage}
256
+ visibleCategories={["indoor"]}
257
+ fixedCategory="indoor"
258
+ visibleSubCategories={subCategories}
259
+ setVisibleSubCategories={setSubCategories}
260
+ />
261
+ </div>
262
+
263
+ </div>
264
+ </div>
265
+ );
266
+ }
@@ -0,0 +1,272 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { ProductList } from "@/components/addOns/functional/ProductList";
5
+ import Spinner from "@/components/addOns/non-functional/spinner";
6
+ import { useStrapiAuth } from "@/lib/auth/auth-context";
7
+ import { useAuth } from "@clerk/nextjs";
8
+ import { PRODUCT_PAGE } from "../constants/product";
9
+ import { StrapiUser, UploadedImage, Category } from "@/lib/types";
10
+ import { compressImage } from "@/lib/utils/compressImage";
11
+ import { ArrowLeft } from 'lucide-react';
12
+ import Link from "next/link";
13
+ import ProductHero from "@/components/addOns/non-functional/heros/ProductHero";
14
+
15
+
16
+ export default function SolidSurfaceProducts() {
17
+ const { user, authLoading, checkSession } = useStrapiAuth();
18
+ const { isSignedIn, getToken } = useAuth();
19
+ const [isLoading, setIsLoading] = useState(true);
20
+ const [error, setError] = useState<string | null>(null);
21
+ const [uploadedImages, setUploadedImages] = useState<UploadedImage[]>([]);
22
+ const [galleryUser, setUser] = useState<StrapiUser | null>(null);
23
+ const [subCategories, setSubCategories] = useState<string[]>([]);
24
+
25
+ const isAdmin = isSignedIn && !!user?.businessAdminId;
26
+ const category: Category = "indoor";
27
+
28
+ useEffect(() => {
29
+ setUser(user);
30
+ }, [user, authLoading, isSignedIn]);
31
+
32
+ useEffect(() => {
33
+ window.scrollTo(0, 0);
34
+ }, []);
35
+
36
+ useEffect(() => {
37
+ const handleLogin = () => checkSession();
38
+ const handleLogout = () => checkSession();
39
+
40
+ window.addEventListener("user-login", handleLogin);
41
+ window.addEventListener("user-logout", handleLogout);
42
+
43
+ return () => {
44
+ window.removeEventListener("user-login", handleLogin);
45
+ window.removeEventListener("user-logout", handleLogout);
46
+ };
47
+ }, [checkSession]);
48
+
49
+ const fetchInitialData = useCallback(async () => {
50
+ try {
51
+ setIsLoading(true);
52
+
53
+ const headers: HeadersInit = { "Content-Type": "application/json" };
54
+ if (isSignedIn) {
55
+ const token = await getToken();
56
+ if (!token) {
57
+ throw new Error(PRODUCT_PAGE.ERRORS.NO_AUTH_TOKEN);
58
+ }
59
+ headers.Authorization = `Bearer ${token}`;
60
+ }
61
+
62
+ const response = await fetch("/api/gallery-data", { headers });
63
+ if (!response.ok) {
64
+ const errorText = await response.text();
65
+ console.error("Gallery fetch failed:", { status: response.status, errorText });
66
+ throw new Error(PRODUCT_PAGE.ERRORS.FETCH_IMAGES_FAILED.replace("${errorText}", errorText));
67
+ }
68
+
69
+ const { data: images, meta }: { data: UploadedImage[]; meta?: { pagination?: { total: number } } } = await response.json();
70
+
71
+ if (meta && meta.pagination && meta.pagination.total > 100) {
72
+ setError(PRODUCT_PAGE.ERRORS.PAGINATION_WARNING);
73
+ }
74
+
75
+ setUploadedImages(images || []);
76
+ const subCats: string[] = images
77
+ .filter((img) => img.category === category && typeof img.subCategory === "string" && img.subCategory !== "")
78
+ .reduce<string[]>((acc, img) => {
79
+ const subCategory = img.subCategory as string;
80
+ return acc.includes(subCategory) ? acc : [...acc, subCategory];
81
+ }, []);
82
+ setSubCategories(subCats);
83
+ } catch (err) {
84
+ console.error("Fetch error details:", err);
85
+ setError(err instanceof Error ? err.message : PRODUCT_PAGE.ERRORS.FETCH_ERROR);
86
+ } finally {
87
+ setIsLoading(false);
88
+ }
89
+ }, [isSignedIn, getToken, category]);
90
+
91
+ useEffect(() => {
92
+ fetchInitialData();
93
+ }, [fetchInitialData]);
94
+
95
+ const handleImageUpload = async (
96
+ e: React.FormEvent<HTMLFormElement>,
97
+ file: File | null,
98
+ title: string,
99
+ description: string,
100
+ category: Category,
101
+ subCategory: string
102
+ ) => {
103
+ e.preventDefault();
104
+
105
+ if (!file) {
106
+ setError(PRODUCT_PAGE.ERRORS.NO_FILE_SELECTED);
107
+ return;
108
+ }
109
+
110
+ if (!["image/jpeg", "image/png", "image/gif"].includes(file.type)) {
111
+ setError(PRODUCT_PAGE.ERRORS.INVALID_FILE_TYPE);
112
+ return;
113
+ }
114
+
115
+ if (!isAdmin) {
116
+ setError(PRODUCT_PAGE.ERRORS.UNAUTHORIZED_UPLOAD);
117
+ return;
118
+ }
119
+
120
+ if (!subCategory) {
121
+ setError("Subcategory is required");
122
+ return;
123
+ }
124
+
125
+ try {
126
+ setIsLoading(true);
127
+
128
+ const token = await getToken();
129
+ if (!token) {
130
+ setError(PRODUCT_PAGE.ERRORS.AUTHENTICATION_ERROR);
131
+ return;
132
+ }
133
+
134
+ const compressedFile = await compressImage(file);
135
+
136
+ const formData = new FormData();
137
+ formData.append("file", compressedFile);
138
+ formData.append("title", title || `Image ${new Date().toISOString()}`);
139
+ formData.append("description", description || "");
140
+ formData.append("category", category);
141
+ formData.append("subCategory", subCategory);
142
+
143
+ const response = await fetch("/api/gallery-data", {
144
+ method: "POST",
145
+ headers: { Authorization: `Bearer ${token}` },
146
+ body: formData,
147
+ });
148
+
149
+ if (!response.ok) {
150
+ const errorData = await response.json();
151
+ console.error("Upload error response:", errorData);
152
+ if (response.status === 401) {
153
+ setError(PRODUCT_PAGE.ERRORS.AUTHENTICATION_ERROR);
154
+ return;
155
+ }
156
+ throw new Error(PRODUCT_PAGE.ERRORS.UPLOAD_IMAGE_FAILED.replace("${response.status}", response.status.toString()));
157
+ }
158
+
159
+ const { data }: { data: UploadedImage[] } = await response.json();
160
+ setUploadedImages(data || []);
161
+ if (subCategory && !subCategories.includes(subCategory)) {
162
+ setSubCategories([...subCategories, subCategory]);
163
+ }
164
+ setError(null);
165
+ } catch (err) {
166
+ console.error("Upload error details:", err);
167
+ setError(err instanceof Error ? err.message : PRODUCT_PAGE.ERRORS.UPLOAD_IMAGE_ERROR);
168
+ } finally {
169
+ setIsLoading(false);
170
+ }
171
+ };
172
+
173
+ const handleDeleteImage = async (documentId: string) => {
174
+ if (!isAdmin) {
175
+ setError(PRODUCT_PAGE.ERRORS.UNAUTHORIZED_DELETE);
176
+ return;
177
+ }
178
+
179
+ try {
180
+ setIsLoading(true);
181
+
182
+ const token = await getToken();
183
+ if (!token) {
184
+ setError(PRODUCT_PAGE.ERRORS.AUTHENTICATION_ERROR);
185
+ return;
186
+ }
187
+
188
+ const response = await fetch("/api/gallery-data", {
189
+ method: "DELETE",
190
+ headers: {
191
+ "Content-Type": "application/json",
192
+ Authorization: `Bearer ${token}`,
193
+ },
194
+ body: JSON.stringify({ documentId }),
195
+ });
196
+
197
+ if (!response.ok) {
198
+ const errorData = await response.json();
199
+ console.error("Delete error response:", errorData);
200
+ if (response.status === 401) {
201
+ setError(PRODUCT_PAGE.ERRORS.AUTHENTICATION_ERROR);
202
+ return;
203
+ }
204
+ throw new Error(PRODUCT_PAGE.ERRORS.DELETE_IMAGE_FAILED.replace("${response.status}", response.status.toString()));
205
+ }
206
+
207
+ const { data }: { data: UploadedImage[] } = await response.json();
208
+
209
+ setUploadedImages(data || []);
210
+ const subCats: string[] = data
211
+ .filter((img) => img.category === category && typeof img.subCategory === "string" && img.subCategory !== "")
212
+ .reduce<string[]>((acc, img) => {
213
+ const subCategory = img.subCategory as string;
214
+ return acc.includes(subCategory) ? acc : [...acc, subCategory];
215
+ }, []);
216
+ setSubCategories(subCats);
217
+ setError(null);
218
+ } catch (err) {
219
+ console.error("Delete error details:", err);
220
+ setError(err instanceof Error ? err.message : PRODUCT_PAGE.ERRORS.DELETE_IMAGE_ERROR);
221
+ } finally {
222
+ setIsLoading(false);
223
+ }
224
+ };
225
+
226
+ if (isLoading || authLoading) {
227
+ return <Spinner />;
228
+ }
229
+
230
+ const imageSrc = uploadedImages[0]?.url ?? "/placeholder.jpg";
231
+
232
+ return (
233
+ <div className="min-h-screen w-full bg-white font-sans">
234
+ <div className="w-full pt-20 lg:pt-40 pb-16 relative z-10 container-padding">
235
+ {error && (
236
+ <div className="text-red-600 text-center p-4 mb-4 max-w-4xl mx-auto bg-red-50 border border-red-200 rounded-lg backdrop-blur-sm">
237
+ {error}
238
+ </div>
239
+ )}
240
+
241
+ <ProductHero />
242
+
243
+ <div className="mb-8">
244
+ <ProductList
245
+ user={galleryUser}
246
+ uploadedImages={uploadedImages}
247
+ setUploadedImages={setUploadedImages}
248
+ error={error}
249
+ setError={setError}
250
+ isLoading={isLoading}
251
+ setIsLoading={setIsLoading}
252
+ handleImageUpload={handleImageUpload}
253
+ handleDeleteImage={handleDeleteImage}
254
+ visibleCategories={[category]}
255
+ category={category}
256
+ visibleSubCategories={subCategories}
257
+ setVisibleSubCategories={setSubCategories}
258
+ />
259
+ </div>
260
+ <div className="text-center mt-8">
261
+ <Link
262
+ href="/gallery"
263
+ className="inline-flex items-center gap-2 px-6 py-3 bg-gray-600 text-white font-semibold rounded-lg hover:bg-gray-700 transition-colors"
264
+ >
265
+ <ArrowLeft className="w-5 h-5" />
266
+ Back to Services
267
+ </Link>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ );
272
+ }