@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
package/app/blog/[id]/page.tsx
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import React, { useState, useEffect } from
|
|
4
|
-
import { useRouter } from
|
|
5
|
-
import { Card, CardContent } from
|
|
6
|
-
import { BackButton, EditToggleButton, UpdateButton } from
|
|
7
|
-
import { ArrowLeft, Calendar } from
|
|
8
|
-
import { useAuth, useUser } from
|
|
9
|
-
import { useStrapiAuth } from
|
|
10
|
-
import BlogSidebar from
|
|
11
|
-
import ClassList from
|
|
12
|
-
import Spinner from
|
|
3
|
+
import React, { useState, useEffect } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { Card, CardContent } from "@/components/other/card";
|
|
6
|
+
import { BackButton, EditToggleButton, UpdateButton } from "@/components/other/button";
|
|
7
|
+
import { ArrowLeft, Calendar } from "lucide-react";
|
|
8
|
+
import { useAuth, useUser } from "@clerk/nextjs";
|
|
9
|
+
import { useStrapiAuth } from "@/lib/auth/auth-context";
|
|
10
|
+
import BlogSidebar from "@/components/addOns/functional/blogSections/BlogSidebar";
|
|
11
|
+
import ClassList from "@/components/addOns/functional/ClassList";
|
|
12
|
+
import Spinner from "@/components/addOns/non-functional/spinner";
|
|
13
|
+
import { isAdminUser } from "@/lib/auth/auth-utils";
|
|
13
14
|
|
|
14
15
|
interface BlogPost {
|
|
15
16
|
id: number;
|
|
@@ -25,8 +26,8 @@ interface BlogPostPageProps {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
28
|
-
const resolvedParams = React.use(params);
|
|
29
|
-
const id = resolvedParams.id;
|
|
29
|
+
const resolvedParams = React.use(params);
|
|
30
|
+
const id = resolvedParams.id;
|
|
30
31
|
const router = useRouter();
|
|
31
32
|
const { user, authLoading, checkSession } = useStrapiAuth();
|
|
32
33
|
const { getToken, isSignedIn } = useAuth();
|
|
@@ -36,23 +37,41 @@ export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
|
36
37
|
const [isLoading, setIsLoading] = useState(true);
|
|
37
38
|
const [error, setError] = useState<string | null>(null);
|
|
38
39
|
const [isEditing, setIsEditing] = useState(false);
|
|
39
|
-
const [editTitle, setEditTitle] = useState(
|
|
40
|
-
const [editDescription, setEditDescription] = useState(
|
|
40
|
+
const [editTitle, setEditTitle] = useState("");
|
|
41
|
+
const [editDescription, setEditDescription] = useState("");
|
|
41
42
|
const [titleCharCount, setTitleCharCount] = useState(0);
|
|
42
43
|
const [titleWordCount, setTitleWordCount] = useState(0);
|
|
43
44
|
const [descriptionCharCount, setDescriptionCharCount] = useState(0);
|
|
44
45
|
const [descriptionWordCount, setDescriptionWordCount] = useState(0);
|
|
46
|
+
const [isAdmin, setIsAdmin] = useState(false);
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
let isMounted = true;
|
|
50
|
+
const checkAdmin = async () => {
|
|
51
|
+
if (!isSignedIn || !user?.authId) {
|
|
52
|
+
if (isMounted) setIsAdmin(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const adminStatus = await isAdminUser(isSignedIn, user);
|
|
57
|
+
if (isMounted) {
|
|
58
|
+
setIsAdmin(adminStatus);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
checkAdmin();
|
|
62
|
+
return () => {
|
|
63
|
+
isMounted = false;
|
|
64
|
+
};
|
|
65
|
+
}, [isSignedIn, user]);
|
|
47
66
|
|
|
48
67
|
useEffect(() => {
|
|
49
68
|
const handleLogin = () => checkSession();
|
|
50
69
|
const handleLogout = () => checkSession();
|
|
51
|
-
window.addEventListener(
|
|
52
|
-
window.addEventListener(
|
|
70
|
+
window.addEventListener("user-login", handleLogin);
|
|
71
|
+
window.addEventListener("user-logout", handleLogout);
|
|
53
72
|
return () => {
|
|
54
|
-
window.removeEventListener(
|
|
55
|
-
window.removeEventListener(
|
|
73
|
+
window.removeEventListener("user-login", handleLogin);
|
|
74
|
+
window.removeEventListener("user-logout", handleLogout);
|
|
56
75
|
};
|
|
57
76
|
}, [checkSession]);
|
|
58
77
|
|
|
@@ -60,34 +79,34 @@ export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
|
60
79
|
const fetchPost = async () => {
|
|
61
80
|
setIsLoading(true);
|
|
62
81
|
try {
|
|
63
|
-
const headers: HeadersInit = {
|
|
82
|
+
const headers: HeadersInit = { "Content-Type": "application/json" };
|
|
64
83
|
if (isSignedIn) {
|
|
65
84
|
const token = await getToken();
|
|
66
|
-
if (token) headers[
|
|
85
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
67
86
|
}
|
|
68
87
|
|
|
69
|
-
const response = await fetch(
|
|
88
|
+
const response = await fetch("/api/blog", { headers, cache: "no-store" });
|
|
70
89
|
if (!response.ok) {
|
|
71
90
|
const errorData = await response.json();
|
|
72
|
-
throw new Error(errorData.error ||
|
|
91
|
+
throw new Error(errorData.error || "Failed to fetch blog posts");
|
|
73
92
|
}
|
|
74
93
|
const { data } = await response.json();
|
|
75
94
|
const blogPosts = data.blogPosts.map((p: BlogPost) => ({
|
|
76
95
|
...p,
|
|
77
|
-
title: p.title.length > 50 ? p.title.substring(0, 50) +
|
|
78
|
-
description: p.description.length > 1500 ? p.description.substring(0, 1500) +
|
|
96
|
+
title: p.title.length > 50 ? p.title.substring(0, 50) + "..." : p.title,
|
|
97
|
+
description: p.description.length > 1500 ? p.description.substring(0, 1500) + "..." : p.description,
|
|
79
98
|
publishedAt: new Date(p.publishedAt).toISOString(),
|
|
80
99
|
}));
|
|
81
100
|
|
|
82
101
|
const parsedId = parseInt(id);
|
|
83
102
|
if (isNaN(parsedId)) {
|
|
84
|
-
router.push(
|
|
103
|
+
router.push("/404");
|
|
85
104
|
return;
|
|
86
105
|
}
|
|
87
106
|
|
|
88
107
|
const foundPost = blogPosts.find((p: BlogPost) => p.id === parsedId);
|
|
89
108
|
if (!foundPost) {
|
|
90
|
-
router.push(
|
|
109
|
+
router.push("/404");
|
|
91
110
|
return;
|
|
92
111
|
}
|
|
93
112
|
|
|
@@ -100,7 +119,7 @@ export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
|
100
119
|
setDescriptionCharCount(foundPost.description.length);
|
|
101
120
|
setDescriptionWordCount(foundPost.description.trim().split(/\s+/).filter(Boolean).length);
|
|
102
121
|
} catch (err) {
|
|
103
|
-
router.push(
|
|
122
|
+
router.push("/404");
|
|
104
123
|
} finally {
|
|
105
124
|
setIsLoading(false);
|
|
106
125
|
}
|
|
@@ -126,16 +145,16 @@ export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
|
126
145
|
try {
|
|
127
146
|
const token = await getToken();
|
|
128
147
|
if (!token) {
|
|
129
|
-
throw new Error(
|
|
148
|
+
throw new Error("Unauthorized: No token provided");
|
|
130
149
|
}
|
|
131
150
|
if (!post?.documentId) {
|
|
132
|
-
throw new Error(
|
|
151
|
+
throw new Error("Missing documentId for update");
|
|
133
152
|
}
|
|
134
153
|
|
|
135
|
-
const response = await fetch(
|
|
136
|
-
method:
|
|
154
|
+
const response = await fetch("/api/blog", {
|
|
155
|
+
method: "PUT",
|
|
137
156
|
headers: {
|
|
138
|
-
|
|
157
|
+
"Content-Type": "application/json",
|
|
139
158
|
Authorization: `Bearer ${token}`,
|
|
140
159
|
},
|
|
141
160
|
body: JSON.stringify({
|
|
@@ -151,36 +170,36 @@ export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
|
151
170
|
|
|
152
171
|
if (!response.ok) {
|
|
153
172
|
const errorData = await response.json();
|
|
154
|
-
throw new Error(errorData.error ||
|
|
173
|
+
throw new Error(errorData.error || "Failed to update post");
|
|
155
174
|
}
|
|
156
175
|
|
|
157
|
-
const fetchResponse = await fetch(
|
|
176
|
+
const fetchResponse = await fetch("/api/blog", {
|
|
158
177
|
headers: {
|
|
159
|
-
|
|
178
|
+
"Content-Type": "application/json",
|
|
160
179
|
Authorization: `Bearer ${token}`,
|
|
161
180
|
},
|
|
162
|
-
cache:
|
|
181
|
+
cache: "no-store",
|
|
163
182
|
});
|
|
164
183
|
if (!fetchResponse.ok) {
|
|
165
184
|
const errorData = await fetchResponse.json();
|
|
166
|
-
throw new Error(errorData.error ||
|
|
185
|
+
throw new Error(errorData.error || "Failed to fetch blog posts");
|
|
167
186
|
}
|
|
168
187
|
const { data } = await fetchResponse.json();
|
|
169
188
|
const blogPosts = data.blogPosts.map((p: BlogPost) => ({
|
|
170
189
|
...p,
|
|
171
|
-
title: p.title.length > 50 ? p.title.substring(0, 50) +
|
|
172
|
-
description: p.description.length > 1500 ? p.description.substring(0, 1500) +
|
|
190
|
+
title: p.title.length > 50 ? p.title.substring(0, 50) + "..." : p.title,
|
|
191
|
+
description: p.description.length > 1500 ? p.description.substring(0, 1500) + "..." : p.description,
|
|
173
192
|
publishedAt: new Date(p.publishedAt).toISOString(),
|
|
174
193
|
}));
|
|
175
194
|
const updatedPost = blogPosts.find((p: BlogPost) => p.documentId === post.documentId);
|
|
176
195
|
if (!updatedPost) {
|
|
177
|
-
throw new Error(
|
|
196
|
+
throw new Error("Updated post not found");
|
|
178
197
|
}
|
|
179
198
|
setPost(updatedPost);
|
|
180
199
|
setPosts(blogPosts);
|
|
181
200
|
setIsEditing(false);
|
|
182
201
|
} catch (err) {
|
|
183
|
-
setError(err instanceof Error ? err.message :
|
|
202
|
+
setError(err instanceof Error ? err.message : "An unknown error occurred");
|
|
184
203
|
}
|
|
185
204
|
};
|
|
186
205
|
|
|
@@ -195,7 +214,7 @@ export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
|
195
214
|
}
|
|
196
215
|
|
|
197
216
|
if (!post) {
|
|
198
|
-
router.push(
|
|
217
|
+
router.push("/404");
|
|
199
218
|
return null;
|
|
200
219
|
}
|
|
201
220
|
|
|
@@ -204,11 +223,11 @@ export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
|
204
223
|
<div className="max-w-7xl mx-auto">
|
|
205
224
|
{isAdmin && (
|
|
206
225
|
<div className="mb-4 flex gap-4">
|
|
207
|
-
<BackButton onClick={() => router.push(
|
|
226
|
+
<BackButton onClick={() => router.push("/blog")}>
|
|
208
227
|
<ArrowLeft size={16} /> Blog Dashboard
|
|
209
228
|
</BackButton>
|
|
210
229
|
<EditToggleButton onClick={() => setIsEditing(!isEditing)}>
|
|
211
|
-
{isEditing ?
|
|
230
|
+
{isEditing ? "Cancel" : "Edit Post"}
|
|
212
231
|
</EditToggleButton>
|
|
213
232
|
</div>
|
|
214
233
|
)}
|
|
@@ -259,16 +278,16 @@ export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
|
259
278
|
<div className="flex items-center gap-2 text-sm text-gray-400 mb-4">
|
|
260
279
|
<Calendar className="w-5 h-5 text-blue-400" />
|
|
261
280
|
<span className="text-blue-400/90">
|
|
262
|
-
By {post.author} |{
|
|
263
|
-
{new Date(post.publishedAt).toLocaleDateString(
|
|
264
|
-
year:
|
|
265
|
-
month:
|
|
266
|
-
day:
|
|
281
|
+
By {post.author} |{" "}
|
|
282
|
+
{new Date(post.publishedAt).toLocaleDateString("en-US", {
|
|
283
|
+
year: "numeric",
|
|
284
|
+
month: "long",
|
|
285
|
+
day: "numeric",
|
|
267
286
|
})}
|
|
268
287
|
</span>
|
|
269
288
|
</div>
|
|
270
289
|
<div className="text-black/90 leading-relaxed break-words">
|
|
271
|
-
{post.description.split(
|
|
290
|
+
{post.description.split("\n").map((paragraph, index) => (
|
|
272
291
|
<p key={index} className="mb-4 last:mb-0">
|
|
273
292
|
{paragraph}
|
|
274
293
|
</p>
|
package/app/blog/page.tsx
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/other/card";
|
|
5
|
-
import { Button } from "@/components/other/button";
|
|
6
4
|
import { AlertCircle } from "lucide-react";
|
|
7
|
-
import { Alert, AlertDescription } from "@/components/other/alert";
|
|
8
5
|
import { useAuth, useUser } from "@clerk/nextjs";
|
|
9
|
-
import { useStrapiAuth } from "@/lib/auth-context";
|
|
6
|
+
import { useStrapiAuth } from "@/lib/auth/auth-context";
|
|
7
|
+
import { isAdminUser } from "@/lib/auth/auth-utils";
|
|
10
8
|
import Spinner from "@/components/addOns/non-functional/spinner";
|
|
11
9
|
import BlogDashboard from "@/components/addOns/functional/blogSections/BlogDashboard";
|
|
12
10
|
import BlogFormPopUp from "@/components/addOns/functional/blogSections/BlogFormPopUp";
|
|
@@ -36,11 +34,10 @@ export default function BlogPage() {
|
|
|
36
34
|
documentId: "",
|
|
37
35
|
title: "",
|
|
38
36
|
description: "",
|
|
39
|
-
author: user?.
|
|
37
|
+
author: user?.username || "",
|
|
40
38
|
publishedAt: "",
|
|
41
39
|
});
|
|
42
|
-
|
|
43
|
-
const isAdmin = isSignedIn && !!user?.businessAdminId;
|
|
40
|
+
const [isAdmin, setIsAdmin] = useState(false);
|
|
44
41
|
|
|
45
42
|
const timeoutPromise = (promise: Promise<any>, timeout: number) => {
|
|
46
43
|
return Promise.race([
|
|
@@ -65,13 +62,49 @@ export default function BlogPage() {
|
|
|
65
62
|
}, [checkSession]);
|
|
66
63
|
|
|
67
64
|
useEffect(() => {
|
|
68
|
-
if (isModalOpen && !formData.id && user?.
|
|
65
|
+
if (isModalOpen && !formData.id && user?.username) {
|
|
69
66
|
setFormData({
|
|
70
67
|
...formData,
|
|
71
|
-
author:
|
|
68
|
+
author: user.username,
|
|
72
69
|
});
|
|
73
70
|
}
|
|
74
|
-
}, [isModalOpen, formData.id, user?.
|
|
71
|
+
}, [isModalOpen, formData.id, user?.username]);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
let isMounted = true;
|
|
75
|
+
const checkAdmin = async (retries = 3, delay = 1000) => {
|
|
76
|
+
if (!isSignedIn || !user?.authId) {
|
|
77
|
+
if (isMounted) setIsAdmin(false);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
81
|
+
try {
|
|
82
|
+
const adminStatus = await isAdminUser(isSignedIn, user);
|
|
83
|
+
if (isMounted) {
|
|
84
|
+
setIsAdmin(adminStatus);
|
|
85
|
+
setError(null);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error("BlogPage: Admin check failed:", {
|
|
90
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
91
|
+
attempt,
|
|
92
|
+
timestamp: new Date().toISOString(),
|
|
93
|
+
});
|
|
94
|
+
if (attempt < retries) {
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
96
|
+
} else if (isMounted) {
|
|
97
|
+
setIsAdmin(false);
|
|
98
|
+
setError("Failed to verify admin status");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
checkAdmin();
|
|
104
|
+
return () => {
|
|
105
|
+
isMounted = false;
|
|
106
|
+
};
|
|
107
|
+
}, [isSignedIn, user]);
|
|
75
108
|
|
|
76
109
|
const fetchData = async () => {
|
|
77
110
|
if (!isSignedIn || !clerkUser?.id) {
|
package/app/favicon.ico
CHANGED
|
Binary file
|
package/app/gallery/page.tsx
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
// src/pages/gallery.tsx
|
|
2
1
|
"use client";
|
|
3
2
|
|
|
4
3
|
import { useState, useEffect, useCallback } from "react";
|
|
5
|
-
import { ImageDescCarousel } from "@/components/addOns/functional/ImageDescCarousel";
|
|
4
|
+
import { ImageDescCarousel } from "@/components/addOns/functional/carousels/ImageDescCarousel";
|
|
6
5
|
import { GallerySection } from "@/components/addOns/functional/galleries/GalleryComplex";
|
|
7
6
|
import Spinner from "@/components/addOns/non-functional/spinner";
|
|
8
7
|
import { motion } from "framer-motion";
|
|
9
|
-
import { useStrapiAuth } from "@/lib/auth-context";
|
|
8
|
+
import { useStrapiAuth } from "@/lib/auth/auth-context";
|
|
10
9
|
import { useAuth } from "@clerk/nextjs";
|
|
11
10
|
import { GALLERY_PAGE } from "../../lib/constants/gallery";
|
|
12
|
-
import { StrapiUser, UploadedImage, Category
|
|
11
|
+
import { StrapiUser, UploadedImage, Category } from "@/lib/types";
|
|
13
12
|
|
|
14
13
|
export default function GalleryPage() {
|
|
15
14
|
const { user, authLoading, checkSession } = useStrapiAuth();
|
|
@@ -64,11 +63,26 @@ export default function GalleryPage() {
|
|
|
64
63
|
}
|
|
65
64
|
const { data: images, meta } = await imagesResponse.json();
|
|
66
65
|
|
|
66
|
+
const mappedImages: UploadedImage[] = images.map((item: any) => ({
|
|
67
|
+
id: Number(item.id) || 0,
|
|
68
|
+
documentId: item.documentId || "",
|
|
69
|
+
title: item.title || "",
|
|
70
|
+
description: item.description || "",
|
|
71
|
+
url: item.url || "",
|
|
72
|
+
createdAt: item.createdAt || new Date().toISOString(),
|
|
73
|
+
category: item.category || "none",
|
|
74
|
+
subCategory: item.subCategory || "",
|
|
75
|
+
favorite: item.favorite ?? false,
|
|
76
|
+
banner: item.banner ?? false,
|
|
77
|
+
startDate: item.startDate || undefined,
|
|
78
|
+
endDate: item.endDate || undefined,
|
|
79
|
+
}));
|
|
80
|
+
|
|
67
81
|
if (meta?.pagination?.total > 100) {
|
|
68
82
|
setError(GALLERY_PAGE.ERRORS.PAGINATION_WARNING);
|
|
69
83
|
}
|
|
70
84
|
|
|
71
|
-
setUploadedImages(
|
|
85
|
+
setUploadedImages(mappedImages || []);
|
|
72
86
|
} catch (err) {
|
|
73
87
|
console.error("GalleryPage: Fetch error", err);
|
|
74
88
|
setError(err instanceof Error ? err.message : GALLERY_PAGE.ERRORS.FETCH_ERROR);
|
|
@@ -86,7 +100,9 @@ export default function GalleryPage() {
|
|
|
86
100
|
file: File | null,
|
|
87
101
|
title: string,
|
|
88
102
|
description: string,
|
|
89
|
-
category: Category
|
|
103
|
+
category: Category,
|
|
104
|
+
subCategory: string,
|
|
105
|
+
favorite: boolean
|
|
90
106
|
) => {
|
|
91
107
|
e.preventDefault();
|
|
92
108
|
if (!file) {
|
|
@@ -119,6 +135,11 @@ export default function GalleryPage() {
|
|
|
119
135
|
formData.append("title", title || `Image ${new Date().toISOString()}`);
|
|
120
136
|
formData.append("description", description || "");
|
|
121
137
|
formData.append("category", category);
|
|
138
|
+
formData.append("subCategory", subCategory || "");
|
|
139
|
+
formData.append("favorite", String(favorite));
|
|
140
|
+
formData.append("banner", String(false));
|
|
141
|
+
formData.append("startDate", "");
|
|
142
|
+
formData.append("endDate", "");
|
|
122
143
|
|
|
123
144
|
const response = await fetch("/api/gallery-data", {
|
|
124
145
|
method: "POST",
|
package/app/layout.tsx
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
// app/layout.tsx for devvistakit
|
|
1
2
|
import './globals.css';
|
|
2
3
|
import type { Metadata } from 'next';
|
|
3
4
|
import { Inter } from 'next/font/google';
|
|
4
5
|
import { ClerkProvider } from '@clerk/nextjs';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import Navbar from '@/components/navBars/navbar';
|
|
8
|
-
import { Toaster } from '@/components/other/toaster';
|
|
9
|
-
import Footer from '@/components/footers/footer';
|
|
10
|
-
import IconBubble from '@/components/addOns/non-functional/IconBubble';
|
|
6
|
+
import GoogleAnalyticsTracking from '@/lib/google/google-analytics-tracking';
|
|
7
|
+
import ClientLayout from './ClientLayout';
|
|
11
8
|
|
|
12
9
|
const inter = Inter({ subsets: ['latin'], preload: true });
|
|
13
10
|
|
|
@@ -17,8 +14,8 @@ export const metadata: Metadata = {
|
|
|
17
14
|
description: 'Your kit To a new website',
|
|
18
15
|
openGraph: {
|
|
19
16
|
title: 'DevVista Kit',
|
|
20
|
-
description: '',
|
|
21
|
-
url: 'https://devvistatech.com/',
|
|
17
|
+
description: 'Your kit To a new website',
|
|
18
|
+
url: 'https://devvistakit.devvistatech.com/',
|
|
22
19
|
images: [{ url: '/images/test.png' }],
|
|
23
20
|
},
|
|
24
21
|
};
|
|
@@ -26,86 +23,38 @@ export const metadata: Metadata = {
|
|
|
26
23
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
27
24
|
const clerkKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
const HtmlShell = (
|
|
27
|
+
<html lang="en" className="h-full" suppressHydrationWarning>
|
|
28
|
+
<head>
|
|
29
|
+
<GoogleAnalyticsTracking />
|
|
30
|
+
</head>
|
|
31
|
+
<body className={`${inter.className} h-full bg-background text-foreground antialiased`}>
|
|
32
|
+
<ClientLayout>{children}</ClientLayout>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
);
|
|
36
|
+
|
|
30
37
|
if (!clerkKey) {
|
|
31
38
|
console.warn(
|
|
32
|
-
'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY is not defined. Clerk authentication will be disabled.
|
|
33
|
-
'Please set this environment variable in a .env.local file or your deployment environment to enable Clerk functionality.'
|
|
34
|
-
);
|
|
35
|
-
return (
|
|
36
|
-
<html lang="en" className="h-full" suppressHydrationWarning>
|
|
37
|
-
<head>
|
|
38
|
-
{/* Conditionally load Google Analytics script if NEXT_PUBLIC_GA_MEASUREMENT_ID is defined */}
|
|
39
|
-
{process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID && (
|
|
40
|
-
<script async src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}`}></script>
|
|
41
|
-
)}
|
|
42
|
-
<script
|
|
43
|
-
dangerouslySetInnerHTML={{
|
|
44
|
-
__html: `
|
|
45
|
-
window.dataLayer = window.dataLayer || [];
|
|
46
|
-
function gtag(){dataLayer.push(arguments);}
|
|
47
|
-
gtag('js', new Date());
|
|
48
|
-
${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID ? `gtag('config', '${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}');` : ''}
|
|
49
|
-
`,
|
|
50
|
-
}}
|
|
51
|
-
/>
|
|
52
|
-
</head>
|
|
53
|
-
<body className={`${inter.className} h-full bg-gray-900 text-gray-700 antialiased`}>
|
|
54
|
-
<ThemeProvider
|
|
55
|
-
attribute="class"
|
|
56
|
-
defaultTheme="dark"
|
|
57
|
-
enableSystem={false}
|
|
58
|
-
disableTransitionOnChange
|
|
59
|
-
>
|
|
60
|
-
<AuthProvider>
|
|
61
|
-
<Navbar />
|
|
62
|
-
<main className="min-h-screen w-full">{children}</main>
|
|
63
|
-
<Toaster />
|
|
64
|
-
<Footer />
|
|
65
|
-
<IconBubble />
|
|
66
|
-
</AuthProvider>
|
|
67
|
-
</ThemeProvider>
|
|
68
|
-
</body>
|
|
69
|
-
</html>
|
|
39
|
+
'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY is not defined. Clerk authentication will be disabled.'
|
|
70
40
|
);
|
|
41
|
+
return HtmlShell;
|
|
71
42
|
}
|
|
72
43
|
|
|
73
|
-
// If clerkKey is defined, render with ClerkProvider
|
|
74
44
|
return (
|
|
75
|
-
<ClerkProvider
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`,
|
|
89
|
-
}}
|
|
90
|
-
/>
|
|
91
|
-
</head>
|
|
92
|
-
<body className={`${inter.className} h-full bg-gray-900 text-gray-700 antialiased`}>
|
|
93
|
-
<ThemeProvider
|
|
94
|
-
attribute="class"
|
|
95
|
-
defaultTheme="dark"
|
|
96
|
-
enableSystem={false}
|
|
97
|
-
disableTransitionOnChange
|
|
98
|
-
>
|
|
99
|
-
<AuthProvider>
|
|
100
|
-
<Navbar />
|
|
101
|
-
<main className="min-h-screen w-full">{children}</main>
|
|
102
|
-
<Toaster />
|
|
103
|
-
<Footer />
|
|
104
|
-
<IconBubble />
|
|
105
|
-
</AuthProvider>
|
|
106
|
-
</ThemeProvider>
|
|
107
|
-
</body>
|
|
108
|
-
</html>
|
|
45
|
+
<ClerkProvider
|
|
46
|
+
publishableKey={clerkKey}
|
|
47
|
+
appearance={{
|
|
48
|
+
baseTheme: undefined,
|
|
49
|
+
variables: { colorPrimary: '#3498db' },
|
|
50
|
+
}}
|
|
51
|
+
allowedRedirectOrigins={[
|
|
52
|
+
'https://devvistakit.devvistatech.com',
|
|
53
|
+
'https://devvista-kit.netlify.app',
|
|
54
|
+
'http://localhost:3000',
|
|
55
|
+
]}
|
|
56
|
+
>
|
|
57
|
+
{HtmlShell}
|
|
109
58
|
</ClerkProvider>
|
|
110
59
|
);
|
|
111
60
|
}
|