@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
|
@@ -1,51 +1,9 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { getAuth } from "@clerk/nextjs/server";
|
|
3
|
+
import { StrapiUser, UploadedImage } from "@/lib/types";
|
|
3
4
|
|
|
4
5
|
export const revalidate = 0;
|
|
5
6
|
|
|
6
|
-
interface StrapiUser {
|
|
7
|
-
id: number;
|
|
8
|
-
username: string;
|
|
9
|
-
email: string;
|
|
10
|
-
authId: string;
|
|
11
|
-
authProvider: string;
|
|
12
|
-
businessAdminId?: string;
|
|
13
|
-
userRole?: string;
|
|
14
|
-
firstName?: string;
|
|
15
|
-
lastName?: string;
|
|
16
|
-
businessId?: string[] | null;
|
|
17
|
-
dateJoined?: string;
|
|
18
|
-
businessOwner?: boolean;
|
|
19
|
-
userStatus?: string;
|
|
20
|
-
timezone?: string | null;
|
|
21
|
-
language?: string | null;
|
|
22
|
-
isVerified?: boolean;
|
|
23
|
-
businessTitle?: string | null;
|
|
24
|
-
userTitle?: string | null;
|
|
25
|
-
number?: string | null;
|
|
26
|
-
address?: {
|
|
27
|
-
zip: string;
|
|
28
|
-
city: string;
|
|
29
|
-
state: string;
|
|
30
|
-
street: string;
|
|
31
|
-
country: string;
|
|
32
|
-
} | null;
|
|
33
|
-
websiteUrl?: string | null;
|
|
34
|
-
primaryBusinessColor?: string | null;
|
|
35
|
-
secondaryBusinessColor?: string | null;
|
|
36
|
-
logoImage?: string | null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface UploadedImage {
|
|
40
|
-
id: number;
|
|
41
|
-
documentId: string;
|
|
42
|
-
title?: string;
|
|
43
|
-
description?: string;
|
|
44
|
-
url: string;
|
|
45
|
-
createdAt: string;
|
|
46
|
-
category?: "none" | "indoor" | "outdoor" | "commercial";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
7
|
const CONTENT_API_URL = process.env.STRAPI_CONTENT_API_URL || "";
|
|
50
8
|
const BASE_URL = process.env.STRAPI_API_URL || "";
|
|
51
9
|
const UPLOAD_API_URL = `${BASE_URL}/api/upload`;
|
|
@@ -151,7 +109,7 @@ export async function GET(request: NextRequest) {
|
|
|
151
109
|
}
|
|
152
110
|
|
|
153
111
|
const response = await fetch(
|
|
154
|
-
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&pagination[pageSize]=100&t=${Date.now()}`,
|
|
112
|
+
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&fields[4]=subCategory&fields[5]=banner&fields[6]=favorite&fields[7]=startDate&fields[8]=endDate&pagination[pageSize]=100&t=${Date.now()}`,
|
|
155
113
|
{
|
|
156
114
|
headers: {
|
|
157
115
|
"Content-Type": "application/json",
|
|
@@ -177,7 +135,7 @@ export async function GET(request: NextRequest) {
|
|
|
177
135
|
const totalPages = result.meta.pagination.pageCount;
|
|
178
136
|
for (let page = 2; page <= totalPages; page++) {
|
|
179
137
|
const nextPageResponse = await fetch(
|
|
180
|
-
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&pagination[page]=${page}&pagination[pageSize]=100&t=${Date.now()}`,
|
|
138
|
+
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&fields[4]=subCategory&fields[5]=banner&fields[6]=favorite&fields[7]=startDate&fields[8]=endDate&pagination[page]=${page}&pagination[pageSize]=100&t=${Date.now()}`,
|
|
181
139
|
{
|
|
182
140
|
headers: {
|
|
183
141
|
"Content-Type": "application/json",
|
|
@@ -193,23 +151,24 @@ export async function GET(request: NextRequest) {
|
|
|
193
151
|
const images: UploadedImage[] = allImages
|
|
194
152
|
.map((item: any) => {
|
|
195
153
|
const image = item.image;
|
|
196
|
-
|
|
197
|
-
console.warn(`Image missing for item ${item.id}:`, JSON.stringify(item, null, 2));
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
const rawUrl = image.url || "";
|
|
154
|
+
const rawUrl = image?.url || "";
|
|
201
155
|
const imageUrl = rawUrl.startsWith("http") ? rawUrl : `${BASE_URL}${rawUrl}`;
|
|
202
156
|
return {
|
|
203
157
|
id: item.id,
|
|
204
|
-
documentId: item.documentId,
|
|
158
|
+
documentId: item.documentId || "",
|
|
205
159
|
title: item.title || "",
|
|
206
160
|
description: item.description || "",
|
|
207
|
-
url: imageUrl,
|
|
208
|
-
createdAt: item.createdAt,
|
|
161
|
+
url: image ? imageUrl : "",
|
|
162
|
+
createdAt: item.createdAt || new Date().toISOString(),
|
|
209
163
|
category: item.category || "none",
|
|
164
|
+
subCategory: item.subCategory || "",
|
|
165
|
+
banner: item.banner || false,
|
|
166
|
+
favorite: item.favorite ?? false,
|
|
167
|
+
startDate: item.startDate || undefined,
|
|
168
|
+
endDate: item.endDate || undefined,
|
|
210
169
|
};
|
|
211
170
|
})
|
|
212
|
-
.filter((image: UploadedImage
|
|
171
|
+
.filter((image: UploadedImage): image is UploadedImage => image !== null)
|
|
213
172
|
.sort((a: UploadedImage, b: UploadedImage) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
214
173
|
|
|
215
174
|
return NextResponse.json({
|
|
@@ -262,7 +221,12 @@ export async function POST(request: NextRequest) {
|
|
|
262
221
|
const file = formData.get("file") as File | null;
|
|
263
222
|
const title = formData.get("title") as string | null;
|
|
264
223
|
const description = formData.get("description") as string | null;
|
|
265
|
-
const category = formData.get("category") as
|
|
224
|
+
const category = formData.get("category") as string | null;
|
|
225
|
+
const subCategory = formData.get("subCategory") as string | null;
|
|
226
|
+
const banner = formData.get("banner") === "true";
|
|
227
|
+
const favorite = formData.get("favorite") === "true";
|
|
228
|
+
const startDate = formData.get("startDate") as string | null;
|
|
229
|
+
const endDate = formData.get("endDate") as string | null;
|
|
266
230
|
|
|
267
231
|
if (!file) {
|
|
268
232
|
console.error("POST /api/gallery-data: No file provided");
|
|
@@ -310,6 +274,11 @@ export async function POST(request: NextRequest) {
|
|
|
310
274
|
title: title || `Image ${new Date().toISOString()}`,
|
|
311
275
|
description: description || "",
|
|
312
276
|
category: validCategory,
|
|
277
|
+
subCategory: subCategory || "",
|
|
278
|
+
banner: banner,
|
|
279
|
+
favorite: favorite ?? false,
|
|
280
|
+
startDate: startDate || null,
|
|
281
|
+
endDate: endDate || null,
|
|
313
282
|
publishedAt: new Date().toISOString(),
|
|
314
283
|
},
|
|
315
284
|
};
|
|
@@ -334,7 +303,7 @@ export async function POST(request: NextRequest) {
|
|
|
334
303
|
}
|
|
335
304
|
|
|
336
305
|
const filesResponse = await fetch(
|
|
337
|
-
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&pagination[pageSize]=100&t=${Date.now()}`,
|
|
306
|
+
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&fields[4]=subCategory&fields[5]=banner&fields[6]=favorite&fields[7]=startDate&fields[8]=endDate&pagination[pageSize]=100&t=${Date.now()}`,
|
|
338
307
|
{
|
|
339
308
|
headers: {
|
|
340
309
|
"Content-Type": "application/json",
|
|
@@ -371,6 +340,11 @@ export async function POST(request: NextRequest) {
|
|
|
371
340
|
url: imageUrl,
|
|
372
341
|
createdAt: item.createdAt,
|
|
373
342
|
category: item.category || "none",
|
|
343
|
+
subCategory: item.subCategory || "",
|
|
344
|
+
banner: item.banner || false,
|
|
345
|
+
favorite: item.favorite ?? false,
|
|
346
|
+
startDate: item.startDate || undefined,
|
|
347
|
+
endDate: item.endDate || undefined,
|
|
374
348
|
};
|
|
375
349
|
})
|
|
376
350
|
.filter((image: UploadedImage | null): image is UploadedImage => image !== null)
|
|
@@ -428,6 +402,11 @@ export async function PUT(request: NextRequest) {
|
|
|
428
402
|
const title = formData.get("title") as string | null;
|
|
429
403
|
const description = formData.get("description") as string | null;
|
|
430
404
|
const category = formData.get("category") as string | null;
|
|
405
|
+
const subCategory = formData.get("subCategory") as string | null;
|
|
406
|
+
const banner = formData.get("banner") === "true";
|
|
407
|
+
const favorite = formData.get("favorite") === "true";
|
|
408
|
+
const startDate = formData.get("startDate") as string | null;
|
|
409
|
+
const endDate = formData.get("endDate") as string | null;
|
|
431
410
|
const file = formData.get("file") as File | null;
|
|
432
411
|
|
|
433
412
|
if (!documentId) {
|
|
@@ -498,6 +477,11 @@ export async function PUT(request: NextRequest) {
|
|
|
498
477
|
title: title || `Image ${new Date().toISOString()}`,
|
|
499
478
|
description: description || "",
|
|
500
479
|
category: validCategory,
|
|
480
|
+
subCategory: subCategory || "",
|
|
481
|
+
banner: banner,
|
|
482
|
+
favorite: favorite ?? false,
|
|
483
|
+
startDate: startDate || null,
|
|
484
|
+
endDate: endDate || null,
|
|
501
485
|
...(imageId && { image: imageId }),
|
|
502
486
|
},
|
|
503
487
|
};
|
|
@@ -535,7 +519,7 @@ export async function PUT(request: NextRequest) {
|
|
|
535
519
|
}
|
|
536
520
|
|
|
537
521
|
const filesResponse = await fetch(
|
|
538
|
-
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&pagination[pageSize]=100&t=${Date.now()}`,
|
|
522
|
+
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&fields[4]=subCategory&fields[5]=banner&fields[6]=favorite&fields[7]=startDate&fields[8]=endDate&pagination[pageSize]=100&t=${Date.now()}`,
|
|
539
523
|
{
|
|
540
524
|
headers: {
|
|
541
525
|
"Content-Type": "application/json",
|
|
@@ -572,6 +556,11 @@ export async function PUT(request: NextRequest) {
|
|
|
572
556
|
url: imageUrl,
|
|
573
557
|
createdAt: item.createdAt,
|
|
574
558
|
category: item.category || "none",
|
|
559
|
+
subCategory: item.subCategory || "",
|
|
560
|
+
banner: item.banner || false,
|
|
561
|
+
favorite: item.favorite ?? false,
|
|
562
|
+
startDate: item.startDate || undefined,
|
|
563
|
+
endDate: item.endDate || undefined,
|
|
575
564
|
};
|
|
576
565
|
})
|
|
577
566
|
.filter((image: UploadedImage | null): image is UploadedImage => image !== null)
|
|
@@ -668,19 +657,17 @@ export async function DELETE(request: NextRequest) {
|
|
|
668
657
|
const deleteFileResponse = await fetch(`${UPLOAD_API_URL}/files/${uploadFileId}`, {
|
|
669
658
|
method: "DELETE",
|
|
670
659
|
headers: {
|
|
671
|
-
"Content-Type": "application/json",
|
|
672
660
|
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
673
661
|
},
|
|
674
662
|
});
|
|
675
663
|
|
|
676
664
|
if (!deleteFileResponse.ok) {
|
|
677
|
-
|
|
678
|
-
console.warn(`DELETE /api/gallery-data: Failed to delete associated file: ${deleteFileResponse.status} - ${errorText}`);
|
|
665
|
+
console.warn(`DELETE /api/gallery-data: Failed to delete associated file: ${deleteFileResponse.status}`);
|
|
679
666
|
}
|
|
680
667
|
}
|
|
681
668
|
|
|
682
669
|
const filesResponse = await fetch(
|
|
683
|
-
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&pagination[pageSize]=100&t=${Date.now()}`,
|
|
670
|
+
`${CONTENT_API_URL}?publicationState=live&populate=image&fields[0]=title&fields[1]=description&fields[2]=createdAt&fields[3]=category&fields[4]=subCategory&fields[5]=banner&fields[6]=favorite&fields[7]=startDate&fields[8]=endDate&pagination[pageSize]=100&t=${Date.now()}`,
|
|
684
671
|
{
|
|
685
672
|
headers: {
|
|
686
673
|
"Content-Type": "application/json",
|
|
@@ -717,6 +704,11 @@ export async function DELETE(request: NextRequest) {
|
|
|
717
704
|
url: imageUrl,
|
|
718
705
|
createdAt: item.createdAt,
|
|
719
706
|
category: item.category || "none",
|
|
707
|
+
subCategory: item.subCategory || "",
|
|
708
|
+
banner: item.banner || false,
|
|
709
|
+
favorite: item.favorite ?? false,
|
|
710
|
+
startDate: item.startDate || undefined,
|
|
711
|
+
endDate: item.endDate || undefined,
|
|
720
712
|
};
|
|
721
713
|
})
|
|
722
714
|
.filter((image: UploadedImage | null): image is UploadedImage => image !== null)
|
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
import { getAuth } from "@clerk/nextjs/server";
|
|
3
|
-
|
|
4
|
-
interface StrapiUser {
|
|
5
|
-
id: number;
|
|
6
|
-
username: string;
|
|
7
|
-
email: string;
|
|
8
|
-
businessAdminId?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface ScheduleClass {
|
|
12
|
-
id: number;
|
|
13
|
-
documentId: string;
|
|
14
|
-
title: string;
|
|
15
|
-
startTime: string;
|
|
16
|
-
endTime: string;
|
|
17
|
-
daysOfWeek: number[];
|
|
18
|
-
classDescription?: string;
|
|
19
|
-
}
|
|
3
|
+
import { StrapiUser, ScheduleClass } from "@/lib/types";
|
|
20
4
|
|
|
21
5
|
const CALENDAR_API_URL = process.env.STRAPI_CALENDAR_API_URL || "";
|
|
22
6
|
const BASE_URL = process.env.STRAPI_API_URL || "";
|
|
@@ -94,7 +78,7 @@ export async function GET() {
|
|
|
94
78
|
|
|
95
79
|
const result = await response.json();
|
|
96
80
|
|
|
97
|
-
const events = (result.data || []).map((event: any) => {
|
|
81
|
+
const events: ScheduleClass[] = (result.data || []).map((event: any) => {
|
|
98
82
|
const daysOfWeekRaw = event.attributes?.daysOfWeek || event.daysOfWeek || [];
|
|
99
83
|
const daysOfWeekNumbers = Array.isArray(daysOfWeekRaw)
|
|
100
84
|
? daysOfWeekRaw
|
|
@@ -231,7 +215,7 @@ export async function POST(request: NextRequest) {
|
|
|
231
215
|
}
|
|
232
216
|
|
|
233
217
|
const result = await eventsResponse.json();
|
|
234
|
-
const events = (result.data || []).map((event: any) => ({
|
|
218
|
+
const events: ScheduleClass[] = (result.data || []).map((event: any) => ({
|
|
235
219
|
id: event.id || 0,
|
|
236
220
|
documentId: event.documentId || "",
|
|
237
221
|
title: event.attributes?.title || event.title || "Untitled",
|
|
@@ -335,7 +319,7 @@ export async function PUT(request: NextRequest) {
|
|
|
335
319
|
}
|
|
336
320
|
|
|
337
321
|
const result = await eventsResponse.json();
|
|
338
|
-
const events = (result.data || []).map((event: any) => ({
|
|
322
|
+
const events: ScheduleClass[] = (result.data || []).map((event: any) => ({
|
|
339
323
|
id: event.id || 0,
|
|
340
324
|
documentId: event.documentId || "",
|
|
341
325
|
title: event.attributes?.title || event.title || "Untitled",
|
|
@@ -433,7 +417,7 @@ export async function DELETE(request: NextRequest) {
|
|
|
433
417
|
}
|
|
434
418
|
|
|
435
419
|
const result = await eventsResponse.json();
|
|
436
|
-
const events = (result.data || []).map((event: any) => ({
|
|
420
|
+
const events: ScheduleClass[] = (result.data || []).map((event: any) => ({
|
|
437
421
|
id: event.id || 0,
|
|
438
422
|
documentId: event.documentId || "",
|
|
439
423
|
title: event.attributes?.title || event.title || "Untitled",
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
|
|
4
|
+
export async function POST(req: NextRequest) {
|
|
5
|
+
const STRAPI_USER_LIST_API_URL = process.env.STRAPI_USER_LIST_API_URL;
|
|
6
|
+
const STRAPI_API_TOKEN = process.env.STRAPI_API_TOKEN;
|
|
7
|
+
const allowedOrigin = process.env.NEXT_PUBLIC_BASE_URL;
|
|
8
|
+
|
|
9
|
+
if (!STRAPI_USER_LIST_API_URL || !STRAPI_API_TOKEN || !allowedOrigin) {
|
|
10
|
+
console.error("POST /api/signup: Missing environment variables", {
|
|
11
|
+
hasStrapiUrl: !!STRAPI_USER_LIST_API_URL,
|
|
12
|
+
hasStrapiToken: !!STRAPI_API_TOKEN,
|
|
13
|
+
hasBaseUrl: !!allowedOrigin,
|
|
14
|
+
timestamp: new Date().toISOString(),
|
|
15
|
+
});
|
|
16
|
+
return NextResponse.json({ error: "Server configuration error" }, { status: 500 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const headers = new Headers({
|
|
20
|
+
"Access-Control-Allow-Origin": allowedOrigin,
|
|
21
|
+
"Access-Control-Allow-Methods": "POST",
|
|
22
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (req.method === "OPTIONS") {
|
|
26
|
+
return new NextResponse(null, { status: 204, headers });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const requestId = crypto.randomUUID();
|
|
30
|
+
let body;
|
|
31
|
+
try {
|
|
32
|
+
body = await req.json();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("POST /api/signup: Failed to parse request body", { requestId, error, timestamp: new Date().toISOString() });
|
|
35
|
+
return NextResponse.json({ error: "Invalid request body" }, { status: 400, headers });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { authId, email, username } = body;
|
|
39
|
+
|
|
40
|
+
if (!authId || !email || !username) {
|
|
41
|
+
console.error("POST /api/signup: Missing required fields", { requestId, body, timestamp: new Date().toISOString() });
|
|
42
|
+
return NextResponse.json({ error: "Missing required fields: authId, email, or username" }, { status: 400, headers });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
46
|
+
if (!emailRegex.test(email)) {
|
|
47
|
+
console.error("POST /api/signup: Invalid email format", { requestId, email, timestamp: new Date().toISOString() });
|
|
48
|
+
return NextResponse.json({ error: "Invalid email format" }, { status: 400, headers });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Check if user exists by authId or email
|
|
53
|
+
const checkResponse = await fetch(
|
|
54
|
+
`${STRAPI_USER_LIST_API_URL}?filters[$or][0][authId][$eq]=${encodeURIComponent(authId)}&filters[$or][1][email][$eq]=${encodeURIComponent(email)}`,
|
|
55
|
+
{
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
58
|
+
"Content-Type": "application/json",
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (!checkResponse.ok) {
|
|
64
|
+
console.error("POST /api/signup: Failed to check existing user:", {
|
|
65
|
+
requestId,
|
|
66
|
+
status: checkResponse.status,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
});
|
|
69
|
+
return NextResponse.json({ error: "Failed to check existing user" }, { status: checkResponse.status, headers });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const checkResult = await checkResponse.json();
|
|
73
|
+
if (checkResult.data && checkResult.data.length > 0) {
|
|
74
|
+
return NextResponse.json(checkResult.data[0], { status: 200, headers });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Create new Strapi user
|
|
78
|
+
const strapiUserData = {
|
|
79
|
+
data: {
|
|
80
|
+
authId,
|
|
81
|
+
email,
|
|
82
|
+
username,
|
|
83
|
+
userRole: "user",
|
|
84
|
+
businessAdminId: null,
|
|
85
|
+
publishedAt: new Date().toISOString(),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const createResponse = await fetch(STRAPI_USER_LIST_API_URL, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: {
|
|
92
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify(strapiUserData),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!createResponse.ok) {
|
|
99
|
+
const errorData = await createResponse.json();
|
|
100
|
+
console.error("POST /api/signup: Failed to create Strapi user:", {
|
|
101
|
+
requestId,
|
|
102
|
+
status: createResponse.status,
|
|
103
|
+
errorData,
|
|
104
|
+
timestamp: new Date().toISOString(),
|
|
105
|
+
});
|
|
106
|
+
return NextResponse.json({ error: errorData.error?.message || "Failed to create user" }, { status: createResponse.status, headers });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const newStrapiUser = await createResponse.json();
|
|
110
|
+
|
|
111
|
+
return NextResponse.json(newStrapiUser.data, { status: 201, headers });
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error("POST /api/signup: Error:", {
|
|
114
|
+
requestId,
|
|
115
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
});
|
|
118
|
+
return NextResponse.json(
|
|
119
|
+
{ error: "Internal server error", details: error instanceof Error ? error.message : "Unknown error" },
|
|
120
|
+
{ status: 500, headers }
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const config = {
|
|
126
|
+
api: {
|
|
127
|
+
bodyParser: true,
|
|
128
|
+
},
|
|
129
|
+
};
|