@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
@@ -1,132 +1,306 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { getAuth } from "@clerk/nextjs/server";
3
+ import crypto from "crypto";
3
4
 
5
+ // GET handler for /api/sync-user: Retrieves a Strapi user by Clerk authId (user ID).
6
+ // This is called from the client to check if a user exists in Strapi.
7
+ export async function GET(req: NextRequest) {
8
+ // Retrieve the allowed origin for CORS from environment variables, default to 'http://localhost:3000'. TODO: add development mode check
9
+ const allowedOrigin = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
10
+ // Environment variables for Strapi API integration.
11
+ const STRAPI_USER_LIST_API_URL = process.env.STRAPI_USER_LIST_API_URL;
12
+ const STRAPI_API_TOKEN = process.env.STRAPI_API_TOKEN;
13
+ // Extract the authId (Clerk user ID) from query parameters.
14
+ const authId = req.nextUrl.searchParams.get("authId");
15
+
16
+ // Validate required environment variables and authId; return 500 if missing.
17
+ if (!STRAPI_USER_LIST_API_URL || !STRAPI_API_TOKEN || !authId) {
18
+ console.error("GET /api/sync-user: Missing environment variables or authId", {
19
+ hasBaseUrl: !!allowedOrigin,
20
+ hasStrapiUrl: !!STRAPI_USER_LIST_API_URL,
21
+ hasStrapiToken: !!STRAPI_API_TOKEN,
22
+ hasAuthId: !!authId,
23
+ timestamp: new Date().toISOString(),
24
+ });
25
+ return NextResponse.json({ error: "Server configuration error or missing authId" }, { status: 500 });
26
+ }
27
+
28
+ // Set CORS headers for the response.
29
+ const headers = new Headers({
30
+ "Access-Control-Allow-Origin": allowedOrigin,
31
+ "Access-Control-Allow-Methods": "GET",
32
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
33
+ });
34
+
35
+ try {
36
+ // Fetch the user from Strapi using the authId filter.
37
+ const checkResponse = await fetch(
38
+ `${STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${encodeURIComponent(authId)}`,
39
+ {
40
+ headers: {
41
+ Authorization: `Bearer ${STRAPI_API_TOKEN}`,
42
+ "Content-Type": "application/json",
43
+ },
44
+ }
45
+ );
46
+
47
+ // If Strapi fetch fails, log and return the error status.
48
+ if (!checkResponse.ok) {
49
+ console.error("GET /api/sync-user: Failed to fetch Strapi user", {
50
+ status: checkResponse.status,
51
+ timestamp: new Date().toISOString(),
52
+ });
53
+ return NextResponse.json({ error: "Failed to fetch user" }, { status: checkResponse.status, headers });
54
+ }
55
+
56
+ // Parse the Strapi response.
57
+ const checkResult = await checkResponse.json();
58
+ // If a user is found (data array has at least one item), return the user data.
59
+ if (checkResult.data && checkResult.data.length > 0) {
60
+ const existingUser = checkResult.data[0];
61
+ // Return a sanitized version of the user object, excluding sensitive fields.
62
+ return NextResponse.json(
63
+ {
64
+ id: existingUser.id,
65
+ username: existingUser.username,
66
+ email: existingUser.email,
67
+ authId: existingUser.authId,
68
+ businessAdminId: existingUser.businessAdminId,
69
+ userRole: existingUser.userRole,
70
+ isAdmin: existingUser.isAdmin || false,
71
+ firstName: existingUser.firstName,
72
+ lastName: existingUser.lastName,
73
+ businessId: existingUser.businessId,
74
+ dateJoined: existingUser.dateJoined,
75
+ businessOwner: existingUser.businessOwner,
76
+ userStatus: existingUser.userStatus,
77
+ timezone: existingUser.timezone,
78
+ language: existingUser.language,
79
+ isVerified: existingUser.isVerified,
80
+ authProvider: existingUser.authProvider,
81
+ businessTitle: existingUser.businessTitle,
82
+ userTitle: existingUser.userTitle,
83
+ number: existingUser.number,
84
+ address: existingUser.address,
85
+ websiteUrl: existingUser.websiteUrl,
86
+ primaryBusinessColor: existingUser.primaryBusinessColor,
87
+ secondaryBusinessColor: existingUser.secondaryBusinessColor,
88
+ logoImage: existingUser.logoImage,
89
+ },
90
+ { status: 200, headers }
91
+ );
92
+ }
93
+ // If no user found, return 404.
94
+ return NextResponse.json({ error: "User not found" }, { status: 404, headers });
95
+ } catch (error) {
96
+ // Catch any unexpected errors, log them, and return 500.
97
+ console.error("GET /api/sync-user: Error:", {
98
+ error: error instanceof Error ? error.message : "Unknown error",
99
+ timestamp: new Date().toISOString(),
100
+ });
101
+ return NextResponse.json(
102
+ { error: "Internal server error", details: error instanceof Error ? error.message : "Unknown error" },
103
+ { status: 500, headers }
104
+ );
105
+ }
106
+ }
107
+
108
+ // POST handler for /api/sync-user: Verifies the current Clerk user and checks for existing Strapi user.
109
+ // This seems to be used for post-signup verification rather than creation (creation is handled elsewhere).
4
110
  export async function POST(req: NextRequest) {
111
+ // Retrieve the allowed origin for CORS.
112
+ const allowedOrigin = process.env.NEXT_PUBLIC_BASE_URL;
113
+ if (!allowedOrigin) {
114
+ console.error("NEXT_PUBLIC_BASE_URL not configured", { timestamp: new Date().toISOString() });
115
+ return NextResponse.json({ error: "Server configuration error: Missing base URL" }, { status: 500 });
116
+ }
117
+
118
+ // Set CORS headers for POST requests.
119
+ const headers = new Headers({
120
+ "Access-Control-Allow-Origin": allowedOrigin,
121
+ "Access-Control-Allow-Methods": "POST",
122
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
123
+ });
124
+
125
+ // Handle preflight OPTIONS request for CORS.
126
+ if (req.method === "OPTIONS") {
127
+ return new NextResponse(null, { status: 204, headers });
128
+ }
129
+
130
+ // Get the authenticated user ID from Clerk middleware.
5
131
  const { userId } = getAuth(req);
6
132
  if (!userId) {
7
- console.error("No userId, returning 401");
8
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
133
+ console.error("No userId found", { timestamp: new Date().toISOString() });
134
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401, headers });
135
+ }
136
+
137
+ // Generate a unique request ID for logging.
138
+ const requestId = crypto.randomUUID();
139
+
140
+ // Parse the request body (expected to contain authId, email, username).
141
+ let body;
142
+ try {
143
+ body = await req.json();
144
+ } catch (error) {
145
+ console.warn("Failed to parse request body", { requestId, error, timestamp: new Date().toISOString() });
146
+ return NextResponse.json({ error: "Invalid request body" }, { status: 400, headers });
147
+ }
148
+
149
+ // Extract required fields from body.
150
+ const { authId, email, username } = body;
151
+
152
+ // Validate required fields; return 400 if missing.
153
+ if (!authId || !email || !username) {
154
+ console.error("Missing required fields", { requestId, body, timestamp: new Date().toISOString() });
155
+ return NextResponse.json({ error: "Missing required fields" }, { status: 400, headers });
9
156
  }
10
157
 
11
- const clerkUserData = await req.json();
158
+ // Environment variables for integrations.
12
159
  const STRAPI_USER_LIST_API_URL = process.env.STRAPI_USER_LIST_API_URL;
13
160
  const STRAPI_API_TOKEN = process.env.STRAPI_API_TOKEN;
14
161
  const CLERK_SECRET_KEY = process.env.CLERK_SECRET_KEY;
15
162
 
163
+ // Validate environment variables; return 500 if missing.
16
164
  if (!STRAPI_USER_LIST_API_URL || !STRAPI_API_TOKEN || !CLERK_SECRET_KEY) {
17
- console.error("Missing environment variables:", {
18
- STRAPI_USER_LIST_API_URL,
19
- STRAPI_API_TOKEN: !!STRAPI_API_TOKEN,
20
- CLERK_SECRET_KEY: !!CLERK_SECRET_KEY,
165
+ console.error("Missing environment variables", {
166
+ requestId,
167
+ hasStrapiUrl: !!STRAPI_USER_LIST_API_URL,
168
+ hasStrapiToken: !!STRAPI_API_TOKEN,
169
+ hasClerkSecret: !!CLERK_SECRET_KEY,
170
+ timestamp: new Date().toISOString(),
21
171
  });
22
- return NextResponse.json(
23
- { error: "Server configuration error: Missing required environment variables" },
24
- { status: 500 }
25
- );
172
+ return NextResponse.json({ error: "Server configuration error" }, { status: 500, headers });
26
173
  }
27
174
 
28
175
  try {
176
+ // Fetch the full Clerk user details using the userId from middleware (with 5s timeout).
177
+ const clerkController = new AbortController();
178
+ const clerkTimeout = setTimeout(() => clerkController.abort(), 5000);
29
179
  const clerkResponse = await fetch(`https://api.clerk.dev/v1/users/${userId}`, {
30
- headers: { Authorization: `Bearer ${CLERK_SECRET_KEY}` },
180
+ headers: {
181
+ Authorization: `Bearer ${CLERK_SECRET_KEY}`,
182
+ "Content-Type": "application/json",
183
+ },
184
+ signal: clerkController.signal,
31
185
  });
186
+ clearTimeout(clerkTimeout);
187
+
188
+ // If Clerk fetch fails, log and return 500.
32
189
  if (!clerkResponse.ok) {
33
190
  const errorText = await clerkResponse.text();
34
- console.error("Clerk fetch failed:", clerkResponse.status, errorText);
35
- throw new Error(`Failed to fetch Clerk user: ${clerkResponse.status}`);
191
+ console.error("Failed to fetch Clerk user", {
192
+ requestId,
193
+ status: clerkResponse.status,
194
+ errorText,
195
+ timestamp: new Date().toISOString(),
196
+ });
197
+ return NextResponse.json({ error: "Failed to verify user" }, { status: 500, headers });
36
198
  }
37
- const clerkUser = await clerkResponse.json();
38
199
 
39
- const userResponse = await fetch(
40
- `${STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${userId}&filters[userRole][$eq]=admin`,
41
- {
42
- headers: {
43
- Authorization: `Bearer ${STRAPI_API_TOKEN}`,
44
- "Content-Type": "application/json",
45
- },
46
- }
47
- );
48
- if (!userResponse.ok) {
49
- const errorText = await userResponse.text();
50
- console.error("Strapi user fetch error:", errorText);
51
- throw new Error(`Failed to fetch user-lists for admin check: ${userResponse.status}`);
200
+ // Parse Clerk user and ensure an email exists.
201
+ const clerkUser = await clerkResponse.json();
202
+ if (!clerkUser.email_addresses?.[0]?.email_address) {
203
+ console.error("No email found for user", { requestId, userId, timestamp: new Date().toISOString() });
204
+ return NextResponse.json({ error: "User email not found" }, { status: 400, headers });
52
205
  }
53
- const userData = await userResponse.json();
54
- const isAdmin = userData.data?.length > 0;
55
-
56
- const strapiUserData = {
57
- authId: userId,
58
- authProvider: "clerk",
59
- email: clerkUser.email_addresses[0]?.email_address || clerkUserData.emailAddresses[0]?.emailAddress,
60
- username:
61
- clerkUser.username ||
62
- clerkUser.email_addresses[0]?.email_address.split("@")[0] ||
63
- clerkUserData.emailAddresses[0]?.emailAddress.split("@")[0],
64
- businessAdminId: isAdmin ? (userData.data[0]?.businessAdminId || clerkUser.public_metadata?.businessAdminId || "") : clerkUser.public_metadata?.businessAdminId || "",
65
- userRole: isAdmin ? "admin" : clerkUser.public_metadata?.userRole || "user",
66
- isVerified: clerkUser.email_addresses[0]?.verification?.status === "verified" || false,
67
- firstName: clerkUser.first_name || "",
68
- lastName: clerkUser.last_name || "",
69
- businessOwner: isAdmin ? (userData.data[0]?.businessOwner || clerkUser.public_metadata?.businessOwner || false) : clerkUser.public_metadata?.businessOwner || false,
70
- userStatus: "active",
71
- };
72
-
73
- const existingUsersResponse = await fetch(
74
- `${STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${userId}`,
206
+
207
+ // Fetch Strapi user by authId (with 5s timeout).
208
+ const authIdController = new AbortController();
209
+ const authIdTimeout = setTimeout(() => authIdController.abort(), 5000);
210
+ const authIdResponse = await fetch(
211
+ `${STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${encodeURIComponent(authId)}`,
75
212
  {
76
213
  headers: {
77
214
  Authorization: `Bearer ${STRAPI_API_TOKEN}`,
78
215
  "Content-Type": "application/json",
79
216
  },
217
+ signal: authIdController.signal,
80
218
  }
81
219
  );
82
- if (!existingUsersResponse.ok) {
83
- const errorText = await existingUsersResponse.text();
84
- console.error("Strapi fetch error:", errorText);
85
- throw new Error(`Failed to fetch user-lists: ${existingUsersResponse.status}`);
86
- }
220
+ clearTimeout(authIdTimeout);
87
221
 
88
- const existingUsers = await existingUsersResponse.json();
89
- const existingUser = existingUsers.data?.[0];
90
-
91
- let strapiUser;
92
- if (existingUser) {
93
- const updateUrl = `${STRAPI_USER_LIST_API_URL}/${existingUser.documentId}`;
94
- const updateResponse = await fetch(updateUrl, {
95
- method: "PUT",
96
- headers: {
97
- Authorization: `Bearer ${STRAPI_API_TOKEN}`,
98
- "Content-Type": "application/json",
99
- },
100
- body: JSON.stringify({ data: strapiUserData }),
222
+ // If Strapi fetch fails, log and return 500.
223
+ if (!authIdResponse.ok) {
224
+ const errorText = await authIdResponse.text();
225
+ console.error("Failed to fetch Strapi user by authId", {
226
+ requestId,
227
+ status: authIdResponse.status,
228
+ errorText,
229
+ timestamp: new Date().toISOString(),
101
230
  });
102
- const strapiUserDataResponse = await updateResponse.json();
103
- if (!updateResponse.ok) {
104
- console.error("Update failed:", updateResponse.status, strapiUserDataResponse);
105
- throw new Error(`Failed to update user: ${updateResponse.status}`);
106
- }
107
- strapiUser = strapiUserDataResponse.data?.attributes || strapiUserDataResponse.data;
108
- } else {
109
- const createResponse = await fetch(`${STRAPI_USER_LIST_API_URL}`, {
110
- method: "POST",
111
- headers: {
112
- Authorization: `Bearer ${STRAPI_API_TOKEN}`,
113
- "Content-Type": "application/json",
231
+ return NextResponse.json({ error: "Failed to verify Strapi user" }, { status: 500, headers });
232
+ }
233
+
234
+ // Parse Strapi response.
235
+ const authIdResult = await authIdResponse.json();
236
+ // If user exists, return the sanitized user data (similar to GET).
237
+ if (authIdResult.data && authIdResult.data.length > 0) {
238
+ const existingUser = authIdResult.data[0];
239
+ return NextResponse.json(
240
+ {
241
+ id: existingUser.id,
242
+ username: existingUser.username,
243
+ email: existingUser.email,
244
+ authId: existingUser.authId,
245
+ businessAdminId: existingUser.businessAdminId,
246
+ userRole: existingUser.userRole,
247
+ isAdmin: existingUser.isAdmin || false,
248
+ firstName: existingUser.firstName,
249
+ lastName: existingUser.lastName,
250
+ businessId: existingUser.businessId,
251
+ dateJoined: existingUser.dateJoined,
252
+ businessOwner: existingUser.businessOwner,
253
+ userStatus: existingUser.userStatus,
254
+ timezone: existingUser.timezone,
255
+ language: existingUser.language,
256
+ isVerified: existingUser.isVerified,
257
+ authProvider: existingUser.authProvider,
258
+ businessTitle: existingUser.businessTitle,
259
+ userTitle: existingUser.userTitle,
260
+ number: existingUser.number,
261
+ address: existingUser.address,
262
+ websiteUrl: existingUser.websiteUrl,
263
+ primaryBusinessColor: existingUser.primaryBusinessColor,
264
+ secondaryBusinessColor: existingUser.secondaryBusinessColor,
265
+ logoImage: existingUser.logoImage,
114
266
  },
115
- body: JSON.stringify({ data: strapiUserData }),
116
- });
117
- if (!createResponse.ok) {
118
- const errorText = await createResponse.text();
119
- console.error("Create failed:", createResponse.status, errorText);
120
- throw new Error(`Failed to create user: ${createResponse.status}`);
121
- }
122
- const responseData = await createResponse.json();
123
- strapiUser = responseData.data?.attributes || responseData.data;
267
+ { status: 200, headers }
268
+ );
124
269
  }
125
270
 
126
- return NextResponse.json(strapiUser, { status: 200 });
271
+ // If no user found, log warning and return 404.
272
+ console.warn("No Strapi user found for authId", { requestId, authId, timestamp: new Date().toISOString() });
273
+ return NextResponse.json({ error: "User not found in Strapi" }, { status: 404, headers });
127
274
  } catch (error) {
128
- console.error("Error syncing user to Strapi:", error);
129
- const errorMessage = error instanceof Error ? error.message : "Failed to sync user to Strapi";
130
- return NextResponse.json({ error: errorMessage }, { status: 500 });
275
+ // Catch any errors (e.g., timeouts, network issues), log, and return 500.
276
+ console.error("Error verifying or syncing user", {
277
+ requestId,
278
+ error: error instanceof Error ? error.message : "Unknown error",
279
+ timestamp: new Date().toISOString(),
280
+ });
281
+ return NextResponse.json({ error: "Internal server error" }, { status: 500, headers });
131
282
  }
132
- }
283
+ }
284
+
285
+ // Next.js API route configuration: Enable body parsing for JSON requests.
286
+ export const config = {
287
+ api: {
288
+ bodyParser: true,
289
+ },
290
+ };
291
+
292
+ // Helper function to determine allowedOrigin based on environment
293
+ function getAllowedOrigin(): string {
294
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;
295
+
296
+ if (baseUrl) {
297
+ return baseUrl;
298
+ }
299
+
300
+ if (process.env.NODE_ENV === 'development') {
301
+ return 'http://localhost:3000'; // Or '*' for even more flexibility in dev
302
+ }
303
+
304
+ // In production, fail explicitly if not set (prevents accidental '*' usage)
305
+ throw new Error('NEXT_PUBLIC_BASE_URL must be set in production');
306
+ }
@@ -0,0 +1,46 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { StrapiUser } from "@/lib/types";
3
+
4
+ export async function POST(req: NextRequest) {
5
+ try {
6
+ const { authId } = await req.json();
7
+ if (!authId) {
8
+ return NextResponse.json({ error: "authId is required" }, { status: 400 });
9
+ }
10
+
11
+ const response = await fetch(
12
+ `${process.env.STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${encodeURIComponent(authId)}`,
13
+ {
14
+ headers: {
15
+ Authorization: `Bearer ${process.env.STRAPI_API_TOKEN}`,
16
+ "Content-Type": "application/json",
17
+ },
18
+ }
19
+ );
20
+
21
+ if (!response.ok) {
22
+ const errorData = await response.json();
23
+ console.error("POST /api/verify-admin: Strapi fetch error:", { error: errorData, status: response.status, timestamp: new Date().toISOString() });
24
+ return NextResponse.json({ error: errorData.error || "Failed to fetch user" }, { status: response.status });
25
+ }
26
+
27
+ const result = await response.json();
28
+
29
+ const user: StrapiUser | null = result.data?.[0]?.attributes || result.data?.[0] || null;
30
+
31
+ if (!user) {
32
+ return NextResponse.json({ error: "User not found" }, { status: 404 });
33
+ }
34
+
35
+ const isAdmin =
36
+ !!user.businessAdminId &&
37
+ user.businessAdminId === process.env.ADMIN_BUSINESS_ID &&
38
+ user.userRole === "admin" &&
39
+ user.businessOwner === true;
40
+
41
+ return NextResponse.json({ isAdmin });
42
+ } catch (error) {
43
+ console.error("POST /api/verify-admin: Error:", { error: error instanceof Error ? error.message : "Unknown error", timestamp: new Date().toISOString() });
44
+ return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
45
+ }
46
+ }