@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,295 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect } from 'react';
4
- import { Button, UploadButton, ToggleButton, CloseButton, DeleteButton, CancelButton, TrashIconButton } from '@/components/other/button'; // Updated import
5
- import { Card } from '@/components/other/card';
6
- import { Upload, FileText, Trash2, X } from 'lucide-react';
7
- import { motion } from 'framer-motion';
8
- import { useUser } from '@clerk/nextjs';
9
-
10
- interface UploadedFile {
11
- id: number;
12
- documentId: string;
13
- name: string;
14
- url: string;
15
- createdAt: string;
16
- }
17
-
18
- interface FileUploaderProps {
19
- uploadedFiles: UploadedFile[];
20
- setUploadedFiles: (files: UploadedFile[]) => void;
21
- setPdfUrl: (url: string) => void;
22
- pdfUrl: string;
23
- isAdmin: boolean;
24
- error?: string | null;
25
- setError: (error: string | null) => void;
26
- handleFileUpload: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
27
- handleDeleteFile: (documentId: string) => Promise<void>;
28
- }
29
-
30
- export function FileUploader({
31
- uploadedFiles,
32
- setUploadedFiles,
33
- setPdfUrl,
34
- pdfUrl,
35
- isAdmin,
36
- error,
37
- setError,
38
- handleFileUpload,
39
- handleDeleteFile,
40
- }: FileUploaderProps) {
41
- const { isSignedIn } = useUser();
42
- const [uploading, setUploading] = useState(false);
43
- const [showAllFiles, setShowAllFiles] = useState(false);
44
- const [isConfirmDeleteOpen, setIsConfirmDeleteOpen] = useState(false);
45
- const [fileToDelete, setFileToDelete] = useState<string | null>(null);
46
-
47
- const baseUrl = process.env.BASE_URL || '';
48
-
49
- useEffect(() => { }, [isSignedIn, isAdmin]);
50
-
51
- useEffect(() => {
52
- const event = new CustomEvent('modalStateChange', {
53
- detail: { isOpen: isConfirmDeleteOpen },
54
- });
55
- window.dispatchEvent(event);
56
- }, [isConfirmDeleteOpen]);
57
-
58
- useEffect(() => {
59
- if (isConfirmDeleteOpen) {
60
- document.body.classList.add('overflow-hidden');
61
- } else {
62
- document.body.classList.remove('overflow-hidden');
63
- }
64
- return () => {
65
- document.body.classList.remove('overflow-hidden');
66
- };
67
- }, [isConfirmDeleteOpen]);
68
-
69
- useEffect(() => {
70
- const handleEsc = (e: KeyboardEvent) => {
71
- if (e.key === 'Escape' && isConfirmDeleteOpen) {
72
- handleCancelDelete();
73
- }
74
- };
75
- window.addEventListener('keydown', handleEsc);
76
- return () => window.removeEventListener('keydown', handleEsc);
77
- }, [isConfirmDeleteOpen]);
78
-
79
- const openConfirmDelete = (documentId: string) => {
80
- setFileToDelete(documentId);
81
- setIsConfirmDeleteOpen(true);
82
- };
83
-
84
- const handleConfirmDelete = async () => {
85
- if (fileToDelete !== null) {
86
- setUploading(true);
87
- setError(null);
88
- try {
89
- await handleDeleteFile(fileToDelete);
90
- } finally {
91
- setUploading(false);
92
- setIsConfirmDeleteOpen(false);
93
- setFileToDelete(null);
94
- }
95
- }
96
- };
97
-
98
- const handleCancelDelete = () => {
99
- setIsConfirmDeleteOpen(false);
100
- setFileToDelete(null);
101
- };
102
-
103
- const truncateText = (text: string, maxLength: number = 30): string => {
104
- if (text.length <= maxLength) return text;
105
- return text.slice(0, maxLength - 3) + '...';
106
- };
107
-
108
- const modalVariants = {
109
- hidden: { opacity: 0, y: '100vh' },
110
- visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: [0.16, 1, 0.3, 1] } },
111
- exit: { opacity: 0, y: '100vh', transition: { duration: 0.3, ease: 'easeIn' } },
112
- };
113
-
114
- const onFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
115
- setUploading(true);
116
- setError(null);
117
- try {
118
- await handleFileUpload(e);
119
- } finally {
120
- setUploading(false);
121
- }
122
- };
123
-
124
- return (
125
- <div className="w-full bg-[#1E2A44] px-4 py-12 sm:px-8 sm:py-16 border-none outline-none z-0">
126
- <style jsx>{`
127
- .modal-glassmorphism {
128
- background: rgba(255, 255, 255, 0.08);
129
- backdrop-filter: blur(10px);
130
- -webkit-backdrop-filter: blur(10px);
131
- border: 2px solid transparent;
132
- border-image: linear-gradient(
133
- 45deg,
134
- var(--exuberant-blue),
135
- var(--jubilee),
136
- var(--modern-purple)
137
- ) 1;
138
- box-shadow: 0 16px 48px rgba(0, 0, 0, 0.1);
139
- border-radius: 2rem;
140
- max-height: 90vh;
141
- overflow: hidden;
142
- }
143
-
144
- @supports not (backdrop-filter: blur(10px)) {
145
- .modal-glassmorphism {
146
- background: rgba(255, 255, 255, 0.2);
147
- }
148
- }
149
-
150
- @media (max-width: 640px) {
151
- .modal-glassmorphism {
152
- padding: 1rem;
153
- max-height: 85vh;
154
- }
155
- }
156
-
157
- .file-item {
158
- z-index: 30;
159
- pointer-events: auto;
160
- cursor: pointer;
161
- }
162
- `}</style>
163
- <Card className="w-full max-w-7xl mx-auto p-4 sm:p-8 bg-[#1E2A44] text-white flex flex-col items-center space-y-6 rounded-none border-none outline-none relative z-10">
164
- <h3 className="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight text-white text-center">
165
- Fitness Forum
166
- </h3>
167
- <div className="space-y-4 w-full">
168
- {error && (
169
- <p className="text-red-400 text-base sm:text-lg md:text-xl text-center">{error}</p>
170
- )}
171
- {isAdmin ? (
172
- <div className="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-4">
173
- <input
174
- type="file"
175
- accept="application/pdf"
176
- onChange={onFileChange}
177
- disabled={uploading}
178
- className="hidden"
179
- id="file-upload"
180
- />
181
- <label htmlFor="file-upload" className="cursor-pointer">
182
- <UploadButton disabled={uploading} asChild>
183
- <span className="flex items-center justify-center">
184
- <Upload className="mr-2 h-4 w-4 sm:h-5 sm:w-5" />
185
- Upload PDF
186
- </span>
187
- </UploadButton>
188
- </label>
189
- {uploading && (
190
- <span className="text-white font-bold text-base sm:text-lg">Uploading...</span>
191
- )}
192
- </div>
193
- ) : (
194
- <p className="text-gray-400 text-sm font-medium text-center">
195
- {isSignedIn ? '' : ''}
196
- </p>
197
- )}
198
- {uploadedFiles.length > 0 ? (
199
- <>
200
- {uploadedFiles.slice(0, showAllFiles ? undefined : 8).map((file) => {
201
- const token = localStorage.getItem('jwt') || '';
202
- const fullUrl = file.url.startsWith('http')
203
- ? `${file.url}${token ? (file.url.includes('?') ? '&' : '?') + `token=${token}` : ''}`
204
- : `${baseUrl}${file.url}${token ? `?token=${token}` : ''}`;
205
- return (
206
- <div
207
- key={file.documentId}
208
- className="file-item flex flex-col sm:flex-row items-center justify-between p-4 sm:p-6 rounded-lg bg-[#2A3A66]/50 hover:bg-[#2A3A66]/70 hover:shadow-lg hover:border-[#FF69B4]/50 transition-all cursor-pointer"
209
- onClick={() => {
210
- setPdfUrl(fullUrl);
211
- window.open(fullUrl, '_blank', 'noopener,noreferrer');
212
- }}
213
- role="button"
214
- tabIndex={0}
215
- onKeyDown={(e) => {
216
- if (e.key === 'Enter' || e.key === ' ') {
217
- setPdfUrl(fullUrl);
218
- window.open(fullUrl, '_blank', 'noopener,noreferrer');
219
- }
220
- }}
221
- aria-label={`Open PDF: ${truncateText(file.name)}`}
222
- >
223
- <div className="text-white font-bold text-base sm:text-lg md:text-xl flex items-center w-full sm:w-auto min-w-0 sm:pb-0 pb-6">
224
- <FileText className="mr-2 h-4 w-4 sm:h-5 sm:w-5 text-[#FF69B4] flex-shrink-0" />
225
- <span className="truncate">{truncateText(file.name, 30)}</span>
226
- </div>
227
- {isAdmin && (
228
- <TrashIconButton
229
- onClick={(e) => {
230
- e.stopPropagation();
231
- openConfirmDelete(file.documentId);
232
- }}
233
- aria-label={`Delete file ${truncateText(file.name)}`}
234
- variant="trash-icon"
235
- className="relative"
236
- >
237
- <Trash2 />
238
- </TrashIconButton>
239
- )}
240
- </div>
241
- );
242
- })}
243
- {uploadedFiles.length > 8 && (
244
- <div className="text-center mt-4">
245
- <ToggleButton onClick={() => setShowAllFiles(!showAllFiles)}>
246
- {showAllFiles ? 'Show Less' : 'Show More'}
247
- </ToggleButton>
248
- </div>
249
- )}
250
- </>
251
- ) : (
252
- <p className="text-gray-300 font-bold text-base sm:text-lg md:text-xl text-center">
253
- No files uploaded yet.
254
- </p>
255
- )}
256
- </div>
257
- </Card>
258
-
259
- {isConfirmDeleteOpen && isAdmin && (
260
- <motion.div
261
- variants={modalVariants}
262
- initial="hidden"
263
- animate="visible"
264
- exit="exit"
265
- className="fixed inset-0 bg-black/90 flex items-center justify-center z-[10000] p-4 isolate"
266
- onClick={handleCancelDelete}
267
- role="dialog"
268
- aria-modal="true"
269
- aria-labelledby="modal-title"
270
- >
271
- <div
272
- className="relative max-w-5xl w-full mx-4 p-6 bg-gray-800/50 border border-gray-700/50 rounded-lg shadow-lg"
273
- onClick={(e) => e.stopPropagation()}
274
- >
275
- <CloseButton onClick={handleCancelDelete}>
276
- <X className="h-6 w-6 sm:h-8 sm:w-8" />
277
- </CloseButton>
278
- <h3 id="modal-title" className="text-xl font-bold text-white mb-4">
279
- Confirm Deletion
280
- </h3>
281
- <p className="text-gray-300 text-sm mb-4">
282
- Are you sure you want to delete this file? This action cannot be undone.
283
- </p>
284
- <div className="flex space-x-3">
285
- <DeleteButton onClick={handleConfirmDelete} disabled={uploading}>
286
- {uploading ? 'Deleting...' : 'Delete'}
287
- </DeleteButton>
288
- <CancelButton onClick={handleCancelDelete}>Cancel</CancelButton>
289
- </div>
290
- </div>
291
- </motion.div>
292
- )}
293
- </div>
294
- );
295
- }
@@ -1,63 +0,0 @@
1
- // src/components/FeaturesSection.tsx
2
- "use client";
3
-
4
- import { ActionButton } from "@/components/other/button";
5
- import { CheckCircle, ArrowRight } from "lucide-react";
6
- import Link from "next/link";
7
-
8
- interface Feature {
9
- text: string;
10
- }
11
-
12
- interface FeaturesSectionProps {
13
- features: Feature[];
14
- title: string;
15
- description: string;
16
- ctaText: string;
17
- ctaLink: string;
18
- }
19
-
20
- export function FeaturesSection({
21
- features,
22
- title,
23
- description,
24
- ctaText,
25
- ctaLink,
26
- }: FeaturesSectionProps) {
27
- return (
28
- <section className="w-full py-16 sm:py-24 px-4 sm:px-8 bg-blue-200 z-0">
29
- <div className="w-full pt-16 sm:pt-24 pb-16 sm:pb-24">
30
- <div className="max-w-7xl mx-auto px-4 sm:px-6">
31
- <div className="flex flex-col md:flex-row gap-6 justify-center items-center">
32
- <div className="w-full md:w-1/2 space-y-6 flex justify-center">
33
- <ul className="space-y-4 text-2xl font-semibold">
34
- {features.map((feature, index) => (
35
- <li key={index} className="flex items-center justify-start">
36
- <CheckCircle className="mr-3 h-6 w-6 text-[#FF69B4] flex-shrink-0" />
37
- <span>{feature.text}</span>
38
- </li>
39
- ))}
40
- </ul>
41
- </div>
42
- <div className="w-full md:w-1/2 space-y-6 text-center pt-12">
43
- <h4 className="text-4xl sm:text-5xl font-bold text-[#333] tracking-tight">
44
- {title}
45
- </h4>
46
- <p className="text-xl sm:text-2xl font-semibold text-[#333]">
47
- {description}
48
- </p>
49
- <ActionButton
50
- asChild
51
- >
52
- <Link href={ctaLink} className="flex items-center justify-center">
53
- {ctaText}
54
- <ArrowRight className="ml-2 h-6 w-6" />
55
- </Link>
56
- </ActionButton>
57
- </div>
58
- </div>
59
- </div>
60
- </div>
61
- </section>
62
- );
63
- }
@@ -1,50 +0,0 @@
1
- // src/pages/types.ts
2
- export interface ScheduleClass {
3
- name: string;
4
- startTime: string;
5
- endTime: string;
6
- id: number;
7
- documentId: string;
8
- daysOfWeek: number[];
9
- classDescription?: string;
10
- }
11
-
12
- export interface WeeklySchedule {
13
- [key: string]: ScheduleClass[];
14
- }
15
-
16
- export interface StrapiUser {
17
- id: number;
18
- username: string;
19
- email: string;
20
- businessAdminId?: string;
21
- userRole?: string;
22
- }
23
-
24
- export type Category =
25
- | "none"
26
- | "indoor"
27
- | "outdoor"
28
- | "commercial";
29
-
30
- export interface UploadedImage {
31
- id: number;
32
- documentId: string;
33
- title?: string;
34
- description?: string;
35
- url: string;
36
- createdAt: string;
37
- category?: Category;
38
- }
39
-
40
- export interface FormState {
41
- file: File | null;
42
- title: string;
43
- description: string;
44
- category: Category;
45
- }
46
-
47
- export interface EditFormState extends FormState {
48
- id: number;
49
- documentId: string;
50
- }
@@ -1,131 +0,0 @@
1
- "use client";
2
-
3
- import { createContext, useContext, useState, useEffect } from "react";
4
- import { useAuth as useClerkAuth, useUser } from "@clerk/nextjs";
5
-
6
- interface StrapiUser {
7
- id: number;
8
- username: string;
9
- email: string;
10
- businessAdminId?: string;
11
- isAdmin?: boolean;
12
- firstName?: string;
13
- lastName?: string;
14
- businessId?: string[];
15
- dateJoined?: string;
16
- businessOwner?: boolean;
17
- userStatus?: string;
18
- timezone?: string;
19
- language?: string;
20
- isVerified?: boolean;
21
- authProvider?: string;
22
- authId?: string;
23
- businessTitle?: string;
24
- userTitle?: string;
25
- number?: string;
26
- address?: {
27
- zip: string;
28
- city: string;
29
- state: string;
30
- street: string;
31
- country: string;
32
- };
33
- websiteUrl?: string;
34
- userRole?: string;
35
- primaryBusinessColor?: string;
36
- secondaryBusinessColor?: string;
37
- logoImage?: string;
38
- }
39
-
40
- interface AuthContextType {
41
- user: StrapiUser | null;
42
- authLoading: boolean;
43
- error: string | null;
44
- checkSession: () => Promise<void>;
45
- }
46
-
47
- const AuthContext = createContext<AuthContextType | undefined>(undefined);
48
-
49
- const userCache = new Map<string, StrapiUser>();
50
-
51
- export function AuthProvider({ children }: { children: React.ReactNode }) {
52
- const [user, setUser] = useState<StrapiUser | null>(null);
53
- const [authLoading, setAuthLoading] = useState(true);
54
- const [error, setError] = useState<string | null>(null); // Added error state
55
- const { isSignedIn, userId } = useClerkAuth();
56
- const { user: clerkUser } = useUser();
57
-
58
- const syncUserToStrapi = async (clerkUserData: any) => {
59
- if (!clerkUserData || !userId) {
60
- console.error("Missing clerkUserData or userId:", { clerkUserData, userId });
61
- setError("Missing user data for sync");
62
- return;
63
- }
64
-
65
- if (userCache.has(userId)) {
66
- setUser(userCache.get(userId)!);
67
- setError(null);
68
- return;
69
- }
70
-
71
- try {
72
- const response = await fetch("/api/sync-user", {
73
- method: "POST",
74
- headers: {
75
- "Content-Type": "application/json",
76
- },
77
- body: JSON.stringify(clerkUserData),
78
- });
79
-
80
- if (!response.ok) {
81
- const errorText = await response.text();
82
- console.error(`Failed to sync user: ${response.status}`, errorText);
83
- setError(`Failed to sync user: ${response.status}`);
84
- return;
85
- }
86
-
87
- const strapiUser = await response.json();
88
- userCache.set(userId, strapiUser);
89
- setUser(strapiUser);
90
- setError(null);
91
- } catch (error) {
92
- console.error("Error syncing user to Strapi:", error);
93
- setError(error instanceof Error ? error.message : "Failed to sync user");
94
- }
95
- };
96
-
97
- const checkSession = async () => {
98
- setAuthLoading(true);
99
- if (!isSignedIn || !userId || !clerkUser) {
100
- setUser(null);
101
- setError("User not signed in");
102
- setAuthLoading(false);
103
- return;
104
- }
105
- try {
106
- await syncUserToStrapi(clerkUser);
107
- } catch (err) {
108
- console.error("checkSession error:", err);
109
- setError(err instanceof Error ? err.message : "Session check failed");
110
- setUser(null);
111
- } finally {
112
- setAuthLoading(false);
113
- }
114
- };
115
-
116
- useEffect(() => {
117
- checkSession();
118
- }, [isSignedIn, userId, clerkUser]);
119
-
120
- return (
121
- <AuthContext.Provider value={{ user, authLoading, error, checkSession }}>
122
- {children}
123
- </AuthContext.Provider>
124
- );
125
- }
126
-
127
- export function useStrapiAuth() {
128
- const context = useContext(AuthContext);
129
- if (!context) throw new Error("useStrapiAuth must be used within an AuthProvider");
130
- return context;
131
- }
@@ -1,118 +0,0 @@
1
- import { getAuth } from "@clerk/nextjs/server";
2
- import { NextRequest } from "next/server";
3
-
4
- interface StrapiUser {
5
- id: number;
6
- username: string;
7
- email: string;
8
- businessAdminId?: string;
9
- isAdmin?: boolean;
10
- firstName?: string;
11
- lastName?: string;
12
- businessId?: string[];
13
- dateJoined?: string;
14
- businessOwner?: boolean;
15
- userStatus?: string;
16
- timezone?: string;
17
- language?: string;
18
- isVerified?: boolean;
19
- authProvider?: string;
20
- authId?: string;
21
- businessTitle?: string;
22
- userTitle?: string;
23
- number?: string;
24
- address?: {
25
- zip: string;
26
- city: string;
27
- state: string;
28
- street: string;
29
- country: string;
30
- };
31
- websiteUrl?: string;
32
- userRole?: string;
33
- primaryBusinessColor?: string;
34
- secondaryBusinessColor?: string;
35
- logoImage?: string;
36
- }
37
-
38
- export async function verifyUser(request: NextRequest): Promise<StrapiUser | null> {
39
- const { userId } = getAuth(request);
40
- if (!userId) {
41
- return null;
42
- }
43
-
44
- const maxRetries = 3;
45
- let attempt = 0;
46
-
47
- while (attempt < maxRetries) {
48
- try {
49
- const response = await fetch(
50
- `${process.env.STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${userId}&populate=*`,
51
- {
52
- headers: {
53
- Authorization: `Bearer ${process.env.STRAPI_API_TOKEN}`,
54
- "Content-Type": "application/json",
55
- },
56
- }
57
- );
58
-
59
- if (!response.ok) {
60
- if (response.status === 429) {
61
- attempt++;
62
- const delay = Math.pow(2, attempt) * 1000;
63
- await new Promise((resolve) => setTimeout(resolve, delay));
64
- continue;
65
- }
66
- console.error("User verification failed:", response.status);
67
- return null;
68
- }
69
-
70
- const users = await response.json();
71
- if (!users.data || users.data.length === 0) {
72
- console.error("No user found in user-list for authId:", userId);
73
- return null;
74
- }
75
-
76
- const userData = users.data[0].attributes;
77
- return {
78
- id: users.data[0].id,
79
- authId: userData.authId,
80
- authProvider: userData.authProvider,
81
- email: userData.email,
82
- username: userData.username,
83
- businessAdminId: userData.businessAdminId,
84
- isAdmin: userData.userRole === "admin" && userData.businessAdminId === process.env.ADMIN_BUSINESS_ID,
85
- firstName: userData.firstName,
86
- lastName: userData.lastName,
87
- dateJoined: userData.dateJoined,
88
- businessOwner: userData.businessOwner,
89
- userStatus: userData.userStatus,
90
- timezone: userData.timezone,
91
- language: userData.language,
92
- isVerified: userData.isVerified,
93
- businessTitle: userData.businessTitle,
94
- userTitle: userData.userTitle,
95
- number: userData.number,
96
- address: userData.address,
97
- websiteUrl: userData.websiteUrl,
98
- userRole: userData.userRole,
99
- primaryBusinessColor: userData.primaryBusinessColor,
100
- secondaryBusinessColor: userData.secondaryBusinessColor,
101
- logoImage: userData.logoImage?.data?.attributes?.url,
102
- businessId: userData.businessId,
103
- };
104
- } catch (error) {
105
- console.error(`Error verifying user (attempt ${attempt + 1}/${maxRetries}):`, error);
106
- attempt++;
107
- if (attempt < maxRetries) {
108
- const delay = Math.pow(2, attempt) * 1000;
109
- await new Promise((resolve) => setTimeout(resolve, delay));
110
- } else {
111
- console.error("Max retries reached for Strapi API");
112
- return null;
113
- }
114
- }
115
- }
116
-
117
- return null;
118
- }