@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.
- package/README.md +40 -0
- package/app/ClientLayout.tsx +66 -0
- package/app/about/page.tsx +11 -248
- package/app/adRequest/page.tsx +101 -25
- package/app/admin-profile/page.tsx +123 -0
- package/app/analytics/page.tsx +41 -5
- package/app/api/about/route.ts +2 -18
- package/app/api/adRequest/route.ts +7 -27
- package/app/api/analytics/[reportType]/route.ts +1 -64
- package/app/api/bio/route.ts +1 -17
- package/app/api/blog/route.ts +1 -19
- package/app/api/contacts/route.ts +1 -46
- package/app/api/files/route.ts +1 -15
- package/app/api/gallery-data/route.ts +53 -61
- package/app/api/schedule/route.ts +5 -21
- package/app/api/signup/route.ts +129 -0
- package/app/api/sync-user/route.ts +268 -94
- package/app/api/verify-admin/route.ts +46 -0
- package/app/blog/[id]/page.tsx +71 -52
- package/app/blog/page.tsx +43 -10
- package/app/favicon.ico +0 -0
- package/app/gallery/page.tsx +27 -6
- package/app/layout.tsx +31 -82
- package/app/page.tsx +20 -311
- package/app/products/constants/product.ts +27 -0
- package/app/products/page.tsx +296 -0
- package/app/products/productOne/page.tsx +266 -0
- package/app/products/productTwo/page.tsx +272 -0
- package/app/schedule/page.tsx +78 -40
- package/bin/init.js +0 -12
- package/components/addOns/functional/ClassList.tsx +21 -17
- package/components/addOns/functional/ProductList.tsx +1027 -0
- package/components/addOns/functional/aboutSections/AboutSection.tsx +107 -70
- package/components/addOns/functional/aboutSections/constants/aboutSection.ts +9 -4
- package/components/addOns/functional/banner/Banner.tsx +150 -0
- package/components/addOns/functional/banner/BannerDashboard.tsx +283 -0
- package/components/addOns/functional/bioSections/BioEditor.tsx +471 -0
- package/components/addOns/functional/bioSections/constants/bioEditor.ts +36 -0
- package/components/addOns/functional/blogSections/BlogDashboard.tsx +1 -1
- package/components/addOns/functional/blogSections/BlogFormPopUp.tsx +2 -1
- package/components/addOns/functional/{ImageDescCarousel.tsx → carousels/ImageDescCarousel.tsx} +166 -57
- package/components/addOns/functional/carousels/ProductDescCarousel.tsx +1129 -0
- package/components/addOns/functional/{ScheduleCarousel.tsx → carousels/ScheduleCarousel.tsx} +110 -50
- package/components/addOns/functional/carousels/constants.ts/productDescCarousel.ts +197 -0
- package/components/addOns/functional/carousels/constants.ts/scheduleCarousel.ts +20 -0
- package/components/addOns/functional/contactsDashboard/ContactsDashboard.tsx +1 -1
- package/components/addOns/functional/fileUploaders/FileUploader.tsx +437 -0
- package/components/addOns/functional/fileUploaders/constants/fileUploader.ts +45 -0
- package/components/addOns/functional/galleries/GalleryComplex.tsx +468 -267
- package/components/addOns/functional/galleries/GallerySimple.tsx +78 -50
- package/components/addOns/functional/galleries/ThreeSetGallery.tsx +260 -0
- package/components/addOns/functional/schedules/ScheduleGridOne.tsx +22 -8
- package/components/addOns/functional/schedules/ScheduleGridTwo.tsx +12 -7
- package/components/addOns/functional/schedules/ScheduleGridTwoBasic.tsx +12 -7
- package/components/addOns/non-functional/SampleCarousel.tsx +3 -3
- package/components/addOns/non-functional/ThreeSetGallery.tsx +3 -3
- package/components/addOns/non-functional/featureSections/FeaturesSection.tsx +74 -0
- package/components/addOns/non-functional/featureSections/constants/featuresSection.ts +30 -0
- package/components/addOns/non-functional/{Heros/HeroSection.tsx → heros/HomeHero.tsx} +17 -15
- package/components/addOns/non-functional/heros/ProductHero.tsx +111 -0
- package/components/addOns/non-functional/heros/constants/hero.ts +62 -0
- package/components/addOns/non-functional/imageCarousels/ProductSlider.tsx +6 -6
- package/components/addOns/non-functional/imageCarousels/ProgramCarousel.tsx +10 -10
- package/components/footers/footer.tsx +161 -198
- package/components/other/admin-menu.tsx +1 -1
- package/lib/auth/auth-context.tsx +225 -0
- package/lib/auth/auth-utils.tsx +30 -0
- package/lib/constants/adRequest.ts +199 -56
- package/lib/constants/admin-profile.ts +12 -0
- package/lib/constants/page.ts +15 -15
- package/lib/google/google-analytics-tracking.tsx +44 -0
- package/lib/types.ts +235 -0
- package/lib/utils/compressImage.tsx +32 -0
- package/middleware.ts +9 -5
- package/next.config.js +1 -1
- package/package.json +3 -2
- package/public/images/test.png +0 -0
- package/components/addOns/functional/BioEditor.tsx +0 -447
- package/components/addOns/functional/FileUploader.tsx +0 -295
- package/components/addOns/non-functional/FeaturesSection.tsx +0 -63
- package/components/types.ts +0 -50
- package/lib/auth-context.tsx +0 -131
- package/lib/verify-user.ts +0 -118
- /package/lib/{google-analytics.tsx → google/google-analytics.tsx} +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { useAuth } from "@clerk/nextjs";
|
|
5
|
+
import { UploadedImage } from "@/lib/types";
|
|
6
|
+
import { GALLERY_COMPLEX } from "../galleries/constants/galleryComplex";
|
|
7
|
+
import Spinner from "@/components/addOns/non-functional/spinner";
|
|
8
|
+
import { SubmitButton } from "@/components/other/button";
|
|
9
|
+
|
|
10
|
+
interface BannerDashboardProps {
|
|
11
|
+
onUpdate?: (updatedImages: UploadedImage[]) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function BannerDashboard({ onUpdate }: BannerDashboardProps) {
|
|
15
|
+
const { getToken } = useAuth();
|
|
16
|
+
const [bannerData, setBannerData] = useState<UploadedImage | null>(null);
|
|
17
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
18
|
+
const [error, setError] = useState<string | null>(null);
|
|
19
|
+
const [form, setForm] = useState({
|
|
20
|
+
title: "",
|
|
21
|
+
description: "",
|
|
22
|
+
startDate: "",
|
|
23
|
+
endDate: "",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const fetchBanner = async () => {
|
|
28
|
+
setIsLoading(true);
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(`/api/gallery-data?t=${Date.now()}`, {
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
cache: "no-store",
|
|
33
|
+
});
|
|
34
|
+
if (response.ok) {
|
|
35
|
+
const result = await response.json();
|
|
36
|
+
const images = Array.isArray(result.data)
|
|
37
|
+
? result.data.map((item: any) => ({
|
|
38
|
+
id: Number(item.id) || 0,
|
|
39
|
+
documentId: item.documentId || "",
|
|
40
|
+
title: item.title || "Untitled",
|
|
41
|
+
description: item.description || "",
|
|
42
|
+
url: item.image?.url
|
|
43
|
+
? item.image.url.startsWith("http")
|
|
44
|
+
? item.image.url
|
|
45
|
+
: `${process.env.STRAPI_API_URL}${item.image.url}`
|
|
46
|
+
: "",
|
|
47
|
+
createdAt: item.createdAt || new Date().toISOString(),
|
|
48
|
+
category: item.category || "none",
|
|
49
|
+
subCategory: item.subCategory || "",
|
|
50
|
+
favorite: item.favorite || false,
|
|
51
|
+
banner: item.banner || false,
|
|
52
|
+
startDate: item.startDate || undefined,
|
|
53
|
+
endDate: item.endDate || undefined,
|
|
54
|
+
}))
|
|
55
|
+
: [];
|
|
56
|
+
const banner = images.find((image: UploadedImage) => image.banner);
|
|
57
|
+
setBannerData((prev) =>
|
|
58
|
+
JSON.stringify(prev) !== JSON.stringify(banner) ? banner : prev
|
|
59
|
+
);
|
|
60
|
+
if (banner) {
|
|
61
|
+
setForm({
|
|
62
|
+
title: banner.title || "",
|
|
63
|
+
description: banner.description || "",
|
|
64
|
+
startDate: banner.startDate
|
|
65
|
+
? new Date(banner.startDate).toISOString().slice(0, 10)
|
|
66
|
+
: "",
|
|
67
|
+
endDate: banner.endDate
|
|
68
|
+
? new Date(banner.endDate).toISOString().slice(0, 10)
|
|
69
|
+
: "",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
setError("Failed to fetch banner data");
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
setError("Error fetching banner data");
|
|
77
|
+
console.error("BannerDashboard: Fetch Error", err);
|
|
78
|
+
} finally {
|
|
79
|
+
setIsLoading(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
fetchBanner();
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
if (!bannerData) {
|
|
88
|
+
setError("No banner image selected");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!form.startDate || !form.endDate) {
|
|
92
|
+
setError("Both start date and end date are required");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (new Date(form.startDate) > new Date(form.endDate)) {
|
|
96
|
+
setError("End date must be after start date");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
setIsLoading(true);
|
|
100
|
+
try {
|
|
101
|
+
const token = await getToken();
|
|
102
|
+
if (!token) {
|
|
103
|
+
setError(GALLERY_COMPLEX.ERRORS.AUTHENTICATION_ERROR);
|
|
104
|
+
console.error("BannerDashboard: No authentication token available", {
|
|
105
|
+
timestamp: new Date().toISOString(),
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const formData = new FormData();
|
|
111
|
+
formData.append("documentId", bannerData.documentId);
|
|
112
|
+
formData.append("title", form.title);
|
|
113
|
+
formData.append("description", form.description);
|
|
114
|
+
formData.append("banner", String(bannerData.banner));
|
|
115
|
+
formData.append("startDate", new Date(form.startDate).toISOString());
|
|
116
|
+
formData.append("endDate", new Date(form.endDate).toISOString());
|
|
117
|
+
|
|
118
|
+
console.log("Submitting FormData:", Object.fromEntries(formData));
|
|
119
|
+
|
|
120
|
+
const response = await fetch("/api/gallery-data", {
|
|
121
|
+
method: "PUT",
|
|
122
|
+
headers: {
|
|
123
|
+
Authorization: `Bearer ${token}`,
|
|
124
|
+
},
|
|
125
|
+
body: formData,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
const errorData = await response.json();
|
|
130
|
+
console.error("BannerDashboard: Update failed", {
|
|
131
|
+
status: response.status,
|
|
132
|
+
errorData,
|
|
133
|
+
timestamp: new Date().toISOString(),
|
|
134
|
+
});
|
|
135
|
+
if (response.status === 401) {
|
|
136
|
+
setError(GALLERY_COMPLEX.ERRORS.AUTHENTICATION_ERROR);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
throw new Error(
|
|
140
|
+
errorData.error ||
|
|
141
|
+
GALLERY_COMPLEX.ERRORS.EDIT_FAILED_STATUS.replace(
|
|
142
|
+
"${response.status}",
|
|
143
|
+
response.status.toString()
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { data } = await response.json();
|
|
149
|
+
if (onUpdate) {
|
|
150
|
+
onUpdate(data || []);
|
|
151
|
+
}
|
|
152
|
+
setBannerData((prev) =>
|
|
153
|
+
prev
|
|
154
|
+
? {
|
|
155
|
+
...prev,
|
|
156
|
+
title: form.title,
|
|
157
|
+
description: form.description,
|
|
158
|
+
startDate: form.startDate
|
|
159
|
+
? new Date(form.startDate).toISOString()
|
|
160
|
+
: undefined,
|
|
161
|
+
endDate: form.endDate
|
|
162
|
+
? new Date(form.endDate).toISOString()
|
|
163
|
+
: undefined,
|
|
164
|
+
banner: prev.banner,
|
|
165
|
+
}
|
|
166
|
+
: prev
|
|
167
|
+
);
|
|
168
|
+
setError(null);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error("BannerDashboard: Update Error", {
|
|
171
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
172
|
+
timestamp: new Date().toISOString(),
|
|
173
|
+
});
|
|
174
|
+
setError(
|
|
175
|
+
err instanceof Error ? err.message : GALLERY_COMPLEX.ERRORS.EDIT_FAILED
|
|
176
|
+
);
|
|
177
|
+
} finally {
|
|
178
|
+
setIsLoading(false);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (isLoading) {
|
|
183
|
+
return (
|
|
184
|
+
<div className="p-4 sm:p-6 bg-gray-800 rounded-lg min-h-[200px] flex items-center justify-center">
|
|
185
|
+
<Spinner />
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div className="p-4 sm:p-6 bg-gray-800 rounded-lg shadow-lg max-w-2xl mx-auto">
|
|
192
|
+
<h3 className="text-lg sm:text-xl font-bold text-white mb-4">Manage Banner</h3>
|
|
193
|
+
{error && <p className="text-red-500 text-sm mb-4">{error}</p>}
|
|
194
|
+
{bannerData ? (
|
|
195
|
+
<form onSubmit={handleSubmit} className="flex flex-col space-y-4">
|
|
196
|
+
<div>
|
|
197
|
+
<label
|
|
198
|
+
htmlFor="title"
|
|
199
|
+
className="block text-sm font-medium text-gray-300 mb-1"
|
|
200
|
+
>
|
|
201
|
+
Banner Title
|
|
202
|
+
</label>
|
|
203
|
+
<input
|
|
204
|
+
id="title"
|
|
205
|
+
type="text"
|
|
206
|
+
value={form.title}
|
|
207
|
+
onChange={(e) => setForm({ ...form, title: e.target.value })}
|
|
208
|
+
className="p-2 bg-gray-700 text-white rounded-lg w-full focus:ring-2 focus:ring-blue-500 focus:outline-none text-sm sm:text-base"
|
|
209
|
+
disabled={isLoading}
|
|
210
|
+
placeholder="Enter banner title"
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
<div>
|
|
214
|
+
<label
|
|
215
|
+
htmlFor="description"
|
|
216
|
+
className="block text-sm font-medium text-gray-300 mb-1"
|
|
217
|
+
>
|
|
218
|
+
Banner Description
|
|
219
|
+
</label>
|
|
220
|
+
<textarea
|
|
221
|
+
id="description"
|
|
222
|
+
value={form.description}
|
|
223
|
+
onChange={(e) => setForm({ ...form, description: e.target.value })}
|
|
224
|
+
className="p-2 bg-gray-700 text-white rounded-lg w-full focus:ring-2 focus:ring-blue-500 focus:outline-none text-sm sm:text-base"
|
|
225
|
+
disabled={isLoading}
|
|
226
|
+
placeholder="Enter banner description"
|
|
227
|
+
rows={4}
|
|
228
|
+
/>
|
|
229
|
+
</div>
|
|
230
|
+
<div>
|
|
231
|
+
<label
|
|
232
|
+
htmlFor="start-date"
|
|
233
|
+
className="block text-sm font-medium text-gray-300 mb-1"
|
|
234
|
+
>
|
|
235
|
+
Start Date
|
|
236
|
+
</label>
|
|
237
|
+
<input
|
|
238
|
+
id="start-date"
|
|
239
|
+
type="date"
|
|
240
|
+
value={form.startDate}
|
|
241
|
+
onChange={(e) => setForm({ ...form, startDate: e.target.value })}
|
|
242
|
+
className="p-2 bg-gray-700 text-white rounded-lg w-full focus:ring-2 focus:ring-blue-500 focus:outline-none text-sm sm:text-base"
|
|
243
|
+
disabled={isLoading}
|
|
244
|
+
required
|
|
245
|
+
/>
|
|
246
|
+
</div>
|
|
247
|
+
<div>
|
|
248
|
+
<label
|
|
249
|
+
htmlFor="end-date"
|
|
250
|
+
className="block text-sm font-medium text-gray-300 mb-1"
|
|
251
|
+
>
|
|
252
|
+
End Date
|
|
253
|
+
</label>
|
|
254
|
+
<input
|
|
255
|
+
id="end-date"
|
|
256
|
+
type="date"
|
|
257
|
+
value={form.endDate}
|
|
258
|
+
onChange={(e) => setForm({ ...form, endDate: e.target.value })}
|
|
259
|
+
className="p-2 bg-gray-700 text-white rounded-lg w-full focus:ring-2 focus:ring-blue-500 focus:outline-none text-sm sm:text-base"
|
|
260
|
+
disabled={isLoading}
|
|
261
|
+
required
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex justify-end">
|
|
265
|
+
<SubmitButton
|
|
266
|
+
type="submit"
|
|
267
|
+
disabled={isLoading}
|
|
268
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:outline-none disabled:bg-blue-400 text-sm sm:text-base"
|
|
269
|
+
>
|
|
270
|
+
{isLoading
|
|
271
|
+
? GALLERY_COMPLEX.BUTTONS.SAVING_BUTTON
|
|
272
|
+
: GALLERY_COMPLEX.BUTTONS.SAVE_BUTTON}
|
|
273
|
+
</SubmitButton>
|
|
274
|
+
</div>
|
|
275
|
+
</form>
|
|
276
|
+
) : (
|
|
277
|
+
<p className="text-gray-400 text-sm sm:text-base">
|
|
278
|
+
No banner image found. Please upload an image with banner: true in the gallery.
|
|
279
|
+
</p>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
}
|