@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
@@ -0,0 +1,225 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, createContext, useContext } from "react";
4
+ import { useAuth as useClerkAuth, useUser } from "@clerk/nextjs";
5
+ import { StrapiUser } from "../types";
6
+
7
+ // Define the shape of the authentication context, including the Strapi user data,
8
+ // loading state, error state, and functions for session checking and post-signup handling.
9
+ interface AuthContextType {
10
+ user: StrapiUser | null;
11
+ authLoading: boolean;
12
+ error: string | null;
13
+ checkSession: () => Promise<void>;
14
+ handlePostSignUp: (clerkUserId: string, email: string, username: string) => Promise<void>;
15
+ }
16
+
17
+ // Create the React context for sharing authentication state and methods across components.
18
+ const AuthContext = createContext<AuthContextType | undefined>(undefined);
19
+
20
+ // A simple in-memory cache to store Strapi user data by Clerk user ID, avoiding repeated API calls.
21
+ const userCache = new Map<string, StrapiUser>();
22
+
23
+ // The main AuthProvider component that wraps the app and manages authentication state.
24
+ export function AuthProvider({ children }: { children: React.ReactNode }) {
25
+ // Local state for the synced Strapi user, loading indicator, and any errors.
26
+ const [user, setUser] = useState<StrapiUser | null>(null);
27
+ const [authLoading, setAuthLoading] = useState(true);
28
+ const [error, setError] = useState<string | null>(null);
29
+
30
+ // Hooks from Clerk to get sign-in status, user ID, and user details.
31
+ const { isSignedIn, userId } = useClerkAuth();
32
+ const { user: clerkUser } = useUser();
33
+
34
+ // Function to synchronize a Clerk user with Strapi: check if the user exists, create if not, and cache the result.
35
+ const syncUserToStrapi = async (clerkUserData: { authId: string; email: string; username: string }) => {
36
+ // Early return if required data is missing.
37
+ if (!clerkUserData || !userId) {
38
+ console.warn("syncUserToStrapi: Missing clerkUserData or userId:", { clerkUserData, userId, timestamp: new Date().toISOString() });
39
+ return null;
40
+ }
41
+
42
+ // Check cache first for quick retrieval.
43
+ if (userCache.has(userId)) {
44
+ const cachedUser = userCache.get(userId)!;
45
+ setUser(cachedUser);
46
+ setError(null);
47
+ return cachedUser;
48
+ }
49
+
50
+ try {
51
+ // First, attempt to fetch the existing user from Strapi via the sync-user API.
52
+ const checkResponse = await fetch(`/api/sync-user?authId=${encodeURIComponent(clerkUserData.authId)}`, {
53
+ headers: {
54
+ "Content-Type": "application/json",
55
+ },
56
+ });
57
+
58
+ // If the user exists, cache and set it as the current user.
59
+ if (checkResponse.ok) {
60
+ const existingUser = await checkResponse.json();
61
+ userCache.set(userId, existingUser);
62
+ setUser(existingUser);
63
+ setError(null);
64
+ return existingUser;
65
+ }
66
+
67
+ // If no existing user, attempt to create a new one via the signup API.
68
+ const response = await fetch("/api/signup", {
69
+ method: "POST",
70
+ headers: {
71
+ "Content-Type": "application/json",
72
+ },
73
+ body: JSON.stringify(clerkUserData),
74
+ });
75
+
76
+ // Handle signup failure (e.g., duplicate user error) by retrying the check.
77
+ if (!response.ok) {
78
+ const errorData = await response.json();
79
+ console.warn("syncUserToStrapi: Failed to create user (likely duplicate):", { errorData, status: response.status, timestamp: new Date().toISOString() });
80
+
81
+ const retryResponse = await fetch(`/api/sync-user?authId=${encodeURIComponent(clerkUserData.authId)}`, {
82
+ headers: {
83
+ "Content-Type": "application/json",
84
+ },
85
+ });
86
+ if (retryResponse.ok) {
87
+ const existingUser = await retryResponse.json();
88
+ userCache.set(userId, existingUser);
89
+ setUser(existingUser);
90
+ setError(null);
91
+ return existingUser;
92
+ }
93
+ console.warn("syncUserToStrapi: Failed to sync user:", { errorData, status: response.status, timestamp: new Date().toISOString() });
94
+ setError(null);
95
+ setUser(null);
96
+ return null;
97
+ }
98
+
99
+ // On successful creation, cache and set the new Strapi user.
100
+ const strapiUser = await response.json();
101
+ userCache.set(userId, strapiUser);
102
+ setUser(strapiUser);
103
+ setError(null);
104
+ return strapiUser;
105
+ } catch (error) {
106
+ // Log any unexpected errors but clear error state and set user to null.
107
+ console.warn("syncUserToStrapi: Error:", { error: error instanceof Error ? error.message : "Unknown error", timestamp: new Date().toISOString() });
108
+ setError(null);
109
+ setUser(null);
110
+ return null;
111
+ }
112
+ };
113
+
114
+ // Handler called after Clerk signup to sync the new user to Strapi.
115
+ const handlePostSignUp = async (clerkUserId: string, email: string, username: string) => {
116
+ // Early return if required data is missing.
117
+ if (!email || !username) {
118
+ console.warn("handlePostSignUp: Missing email or username", { clerkUserId, email, username, timestamp: new Date().toISOString() });
119
+ setError(null);
120
+ return;
121
+ }
122
+
123
+ try {
124
+ // Delegate to the sync function.
125
+ const strapiUser = await syncUserToStrapi({
126
+ authId: clerkUserId,
127
+ email,
128
+ username,
129
+ });
130
+ if (!strapiUser) {
131
+ console.warn("handlePostSignUp: Failed to create Strapi user", { clerkUserId, timestamp: new Date().toISOString() });
132
+ setError(null);
133
+ }
134
+ } catch (error) {
135
+ // Log errors but clear error state.
136
+ console.warn("handlePostSignUp: Error:", {
137
+ error: error instanceof Error ? error.message : "Unknown error",
138
+ timestamp: new Date().toISOString(),
139
+ });
140
+ setError(null);
141
+ }
142
+ };
143
+
144
+ // Function to check and sync the current session on mount or changes.
145
+ const checkSession = async () => {
146
+ // Set loading state.
147
+ setAuthLoading(true);
148
+ // Early return if not signed in or missing user data.
149
+ if (!isSignedIn || !userId || !clerkUser) {
150
+ setUser(null);
151
+ setAuthLoading(false);
152
+ setError(null);
153
+ return;
154
+ }
155
+
156
+ // Check cache first.
157
+ if (userCache.has(userId)) {
158
+ const cachedUser = userCache.get(userId)!;
159
+ setUser(cachedUser);
160
+ setAuthLoading(false);
161
+ setError(null);
162
+ return;
163
+ }
164
+
165
+ try {
166
+ // Attempt to fetch existing user.
167
+ const checkResponse = await fetch(`/api/sync-user?authId=${encodeURIComponent(userId)}`, {
168
+ headers: {
169
+ "Content-Type": "application/json",
170
+ },
171
+ });
172
+
173
+ // If found, cache and set.
174
+ if (checkResponse.ok) {
175
+ const existingUser = await checkResponse.json();
176
+ userCache.set(userId, existingUser);
177
+ setUser(existingUser);
178
+ setAuthLoading(false);
179
+ setError(null);
180
+ return;
181
+ }
182
+
183
+ // Log and proceed to sync if not found.
184
+ console.warn("checkSession: No user found, proceeding to sync:", { userId, timestamp: new Date().toISOString() });
185
+
186
+ // Sync with fallback username generation if needed.
187
+ const strapiUser = await syncUserToStrapi({
188
+ authId: userId,
189
+ email: clerkUser.primaryEmailAddress?.emailAddress || "",
190
+ username: clerkUser.username || clerkUser.firstName || `user_${userId.slice(0, 8)}`,
191
+ });
192
+ if (!strapiUser) {
193
+ console.warn("checkSession: No Strapi user found, user set to null", { userId, timestamp: new Date().toISOString() });
194
+ setUser(null);
195
+ }
196
+ } catch (err) {
197
+ // Log errors and reset states.
198
+ console.warn("checkSession: Error:", { error: err instanceof Error ? err.message : "Unknown error", timestamp: new Date().toISOString() });
199
+ setUser(null);
200
+ setError(null);
201
+ } finally {
202
+ // Always clear loading state.
203
+ setAuthLoading(false);
204
+ }
205
+ };
206
+
207
+ // Effect to trigger session check whenever Clerk auth state changes.
208
+ useEffect(() => {
209
+ checkSession();
210
+ }, [isSignedIn, userId, clerkUser]);
211
+
212
+ // Provide the context value to children components.
213
+ return (
214
+ <AuthContext.Provider value={{ user, authLoading, error, checkSession, handlePostSignUp }}>
215
+ {children}
216
+ </AuthContext.Provider>
217
+ );
218
+ }
219
+
220
+ // Custom hook to access the auth context safely.
221
+ export function useStrapiAuth() {
222
+ const context = useContext(AuthContext);
223
+ if (!context) throw new Error("useStrapiAuth must be used within an AuthProvider");
224
+ return context;
225
+ }
@@ -0,0 +1,30 @@
1
+ import { StrapiUser } from "../types";
2
+
3
+ export async function isAdminUser(isSignedIn: boolean | undefined, user: StrapiUser | null): Promise<boolean> {
4
+ if (!isSignedIn || !user?.authId) {
5
+
6
+ return false;
7
+ }
8
+
9
+ try {
10
+
11
+ const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL || ''}/api/verify-admin`, {
12
+ method: "POST",
13
+ headers: { "Content-Type": "application/json" },
14
+ body: JSON.stringify({ authId: user.authId }),
15
+ });
16
+
17
+ if (!response.ok) {
18
+ const errorData = await response.json();
19
+ console.error("isAdminUser: Verify admin failed:", { error: errorData, status: response.status, timestamp: new Date().toISOString() });
20
+ return false;
21
+ }
22
+
23
+ const { isAdmin } = await response.json();
24
+
25
+ return isAdmin;
26
+ } catch (error) {
27
+ console.error("isAdminUser error:", { error: error instanceof Error ? error.message : "Unknown error", timestamp: new Date().toISOString() });
28
+ return false;
29
+ }
30
+ }
@@ -1,114 +1,257 @@
1
- // app/constants/adRequest.ts
2
- // Constants for hardcoded text in app/adRequest/page.tsx, organized by purpose
1
+ // constants.ts
2
+ export const ABOUT_PAGE = {
3
+ UI: {
4
+ MAIN_HEADING: "About Milton Supply",
5
+ SUCCESS_STORY_HEADING: "Lorem Ipsum",
6
+ SUCCESS_STORY_TEXT: `
7
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
8
+ `,
9
+ ADMIN_LOGGED_IN_MESSAGE: "You are logged in as an admin",
10
+ MODAL_HEADING: "Edit Bio Section",
11
+ TITLE_LABEL: "Bio Title",
12
+ TITLE_PLACEHOLDER: "Enter the bio title",
13
+ IMAGE_LABEL: "Update Image (optional)",
14
+ SELECTED_IMAGE_TEXT: "Selected: ${formImage.name}",
15
+ DESCRIPTION_LABEL: "Update Description",
16
+ DESCRIPTION_PLACEHOLDER: "Enter the bio description",
17
+ NO_IMAGE_AVAILABLE: "No image available",
18
+ NO_DESCRIPTION_AVAILABLE: "No bio description available",
19
+ },
20
+ BUTTONS: {
21
+ UPDATE_BUTTON: "Update",
22
+ SAVING_BUTTON: "Saving...",
23
+ CANCEL_BUTTON: "Cancel",
24
+ READ_MORE_BUTTON: "Read More",
25
+ READ_LESS_BUTTON: "Read Less",
26
+ },
27
+ ERRORS: {
28
+ FETCH_BIO_FAILED: "Failed to fetch bio data: ${response.status}",
29
+ BIO_NOT_FOUND: "Bio data not found",
30
+ FETCH_BIO_ERROR: "An error occurred while fetching bio data",
31
+ UNAUTHORIZED_UPDATE: "Unauthorized: Only admin can update bio data",
32
+ INVALID_IMAGE_TYPE: "Only JPEG, PNG, or GIF images are allowed",
33
+ NO_AUTH_TOKEN: "No authentication token available",
34
+ UPDATE_BIO_FAILED: "Failed to update bio data: ${response.status}",
35
+ UPDATE_BIO_ERROR: "Failed to update bio data",
36
+ },
37
+ };
38
+
39
+ export const ABOUT_SECTION = {
40
+ UI: {
41
+ FALLBACK_TITLE: "About Us",
42
+ FALLBACK_DESCRIPTION: "Welcome to Milton Supply Co. Learn more about our story and mission.",
43
+ NO_IMAGE_AVAILABLE: "No image available",
44
+ NO_DESCRIPTION_AVAILABLE: "No description available",
45
+ ADMIN_LOGGED_IN_MESSAGE: "You are logged in as an admin",
46
+ MODAL_HEADING: "Edit About Section",
47
+ TITLE_LABEL: "About Title",
48
+ TITLE_PLACEHOLDER: "Enter the about title",
49
+ IMAGE_LABEL: "Update Image (optional)",
50
+ SELECTED_IMAGE_TEXT: "Selected: ${formImage.name}",
51
+ DESCRIPTION_LABEL: "Update Description",
52
+ DESCRIPTION_PLACEHOLDER: "Enter the about description",
53
+ },
54
+ BUTTONS: {
55
+ UPDATE_BUTTON: "Update",
56
+ SAVING_BUTTON: "Saving...",
57
+ CANCEL_BUTTON: "Cancel",
58
+ READ_MORE_BUTTON: "Read More",
59
+ READ_LESS_BUTTON: "Read Less",
60
+ },
61
+ ERRORS: {
62
+ FETCH_FAILED: "Failed to fetch about data: ${response.status}",
63
+ FETCH_ERROR: "An error occurred while fetching about data",
64
+ UNAUTHORIZED_UPDATE: "Unauthorized: Only admin can update about data",
65
+ INVALID_IMAGE_TYPE: "Only JPEG, PNG, or GIF images are allowed",
66
+ NO_AUTH_TOKEN: "No authentication token available",
67
+ UPDATE_FAILED: "Failed to update about data: ${response.status}",
68
+ UPDATE_ERROR: "Failed to update about data",
69
+ },
70
+ };
71
+
72
+ export const GALLERY_PAGE = {
73
+ UI: {
74
+ MAIN_HEADING: "Project Gallery",
75
+ LOADING_MESSAGE: "Loading...",
76
+ ADMIN_LOGGED_IN_MESSAGE: "You are logged in as an admin.",
77
+ NO_IMAGES_MESSAGE: "No images available at this time.",
78
+ },
79
+ ERRORS: {
80
+ NO_AUTH_TOKEN: "No authentication token available",
81
+ FETCH_IMAGES_FAILED: "Failed to load images: ${errorText}",
82
+ PAGINATION_WARNING: "Warning: Only the first 100 images are displayed. Contact support to view all images.",
83
+ FETCH_ERROR: "An error occurred while fetching data",
84
+ NO_FILE_SELECTED: "No file selected",
85
+ INVALID_FILE_TYPE: "Only JPEG, PNG, or GIF images are allowed",
86
+ UNAUTHORIZED_UPLOAD: "Unauthorized: Only admins can upload images",
87
+ AUTHENTICATION_ERROR: "Authentication error. Please log in again.",
88
+ UPLOAD_IMAGE_FAILED: "Failed to upload image: ${response.status}",
89
+ UPLOAD_IMAGE_ERROR: "Failed to upload image",
90
+ UNAUTHORIZED_DELETE: "Unauthorized: Only admins can delete images",
91
+ DELETE_IMAGE_FAILED: "Failed to delete image: ${response.status}",
92
+ DELETE_IMAGE_ERROR: "Failed to delete image",
93
+ ADMIN_CHECK_FAILED: "Failed to verify admin status after multiple attempts",
94
+ },
95
+ };
96
+
97
+ export const GALLERY_COMPLEX = {
98
+ UI: {
99
+ PHOTOS_TAB: "Photos",
100
+ VIDEOS_TAB: "Videos",
101
+ ALL_SUB_TAB: "All",
102
+ CATEGORY_NONE: "None",
103
+ CATEGORY_INDOOR: "Indoor",
104
+ CATEGORY_OUTDOOR: "Outdoor",
105
+ CATEGORY_COMMERCIAL: "Commercial",
106
+ CATEGORY_LANDSCAPE_BOULDERS: "Landscape Boulders",
107
+ CATEGORY_STONE_VENEER: "Stone Veneer",
108
+ CATEGORY_STONE_CUTTING_TREADS: "Stone Cutting Treads",
109
+ CATEGORY_MASONRY_TOOLS: "Masonry Tools",
110
+ CATEGORY_PATIO_STONE: "Patio Stone",
111
+ CATEGORY_PAVERS_SLABS: "Pavers & Slabs",
112
+ CATEGORY_BAGGED_CEMENT: "Bagged Cement Products",
113
+ CATEGORY_SEALERS_ADDITIVES: "Sealers & Additives",
114
+ CATEGORY_LANDSCAPING_ACCESSORIES: "Landscaping Accessories",
115
+ CATEGORY_GRAVEL_SAND: "Gravel & More",
116
+ NO_IMAGES_MESSAGE: "No images available for this category.",
117
+ LOADING_MESSAGE: "Loading gallery data...",
118
+ ADMIN_LOGGED_IN_MESSAGE: "You are logged in as an admin",
119
+ DEFAULT_IMAGE_ALT: "Gallery Image",
120
+ UPLOAD_MODAL_HEADING: "Upload New Image",
121
+ EDIT_MODAL_HEADING: "Edit Image",
122
+ DELETE_MODAL_HEADING: "Confirm Delete",
123
+ DELETE_CONFIRMATION_MESSAGE: "Are you sure you want to delete this image? This action cannot be undone.",
124
+ CHOOSE_IMAGE_TEXT: "Choose an image",
125
+ TITLE_PLACEHOLDER: "Enter image title",
126
+ DESCRIPTION_PLACEHOLDER: "Enter image description",
127
+ },
128
+ BUTTONS: {
129
+ UPLOAD_IMAGE_BUTTON: "Upload Image",
130
+ LOAD_MORE_BUTTON: "Load More",
131
+ UPLOAD_BUTTON: "Upload",
132
+ UPLOADING_BUTTON: "Uploading...",
133
+ SAVE_BUTTON: "Save",
134
+ SAVING_BUTTON: "Saving...",
135
+ DELETE_BUTTON: "Delete",
136
+ DELETING_BUTTON: "Deleting...",
137
+ CANCEL_BUTTON: "Cancel",
138
+ },
139
+ ERRORS: {
140
+ UNAUTHORIZED_EDIT: "Unauthorized: Only admins can edit images",
141
+ UNAUTHORIZED_UPLOAD: "Unauthorized: Only admins can upload images",
142
+ UNAUTHORIZED_DELETE: "Unauthorized: Only admins can delete images",
143
+ AUTHENTICATION_ERROR: "Authentication failed. Please log in again.",
144
+ EDIT_FAILED: "Failed to edit image",
145
+ EDIT_FAILED_STATUS: "Failed to edit image: ${response.status}",
146
+ UPLOAD_FAILED: "Failed to upload image",
147
+ DELETE_FAILED: "Failed to delete image",
148
+ },
149
+ };
150
+
151
+ export const PRODUCT_DESC_CAROUSEL = {
152
+ UI: {
153
+ FALLBACK_TITLE: "Product Gallery",
154
+ NO_IMAGES_MESSAGE: "No images available",
155
+ NO_DESCRIPTION_MESSAGE: "No description available.",
156
+ NON_ADMIN_MESSAGE: "You are logged in but do not have admin privileges.",
157
+ DEFAULT_IMAGE_ALT: "Product Image",
158
+ UPLOAD_MODAL_HEADING: "Upload New Product Image",
159
+ EDIT_MODAL_HEADING: "Edit Product Image",
160
+ DELETE_MODAL_HEADING: "Confirm Deletion",
161
+ DELETE_CONFIRMATION_MESSAGE: "Are you sure you want to delete this product image?",
162
+ CHOOSE_IMAGE_LABEL: "Choose Image",
163
+ SELECTED_IMAGE_TEXT: "Selected: ${formImage.name}",
164
+ TITLE_LABEL: "Image Title",
165
+ TITLE_PLACEHOLDER: "Enter image title",
166
+ DESCRIPTION_LABEL: "Image Description",
167
+ DESCRIPTION_PLACEHOLDER: "Enter image description",
168
+ CATEGORY_LABEL: "Category",
169
+ CATEGORY_NONE: "None",
170
+ SUBCATEGORY_LABEL: "Subcategory",
171
+ SUBCATEGORY_PLACEHOLDER: "Enter subcategory (e.g., play-sand)",
172
+ },
173
+ BUTTONS: {
174
+ UPLOAD_IMAGE_BUTTON: "Upload New Product Image",
175
+ UPLOAD_BUTTON: "Upload",
176
+ UPLOADING_BUTTON: "Uploading...",
177
+ SAVE_BUTTON: "Save",
178
+ SAVING_BUTTON: "Saving...",
179
+ DELETE_BUTTON: "Delete",
180
+ DELETING_BUTTON: "Deleting...",
181
+ CANCEL_BUTTON: "Cancel",
182
+ },
183
+ ERRORS: {
184
+ NO_AUTH_TOKEN: "Unauthorized: No token provided",
185
+ AUTHENTICATION_ERROR: "Unauthorized: Session expired. Please log in again.",
186
+ UNAUTHORIZED_EDIT: "Unauthorized: Only admins can edit images",
187
+ UNAUTHORIZED_DELETE: "Unauthorized: Only admins can delete images",
188
+ EDIT_FAILED: "Failed to update image",
189
+ EDIT_FAILED_STATUS: "Failed to update image (Status: ${response.status})",
190
+ UPLOAD_FAILED: "Failed to upload image",
191
+ SUBCATEGORY_EMPTY: "Subcategory cannot be empty",
192
+ SUBCATEGORY_TOO_LONG: "Subcategory name is too long (max 50 characters)",
193
+ SUBCATEGORY_INVALID: "Subcategory can only contain letters, numbers, spaces, or hyphens",
194
+ ADMIN_CHECK_FAILED: "Failed to verify admin status after multiple attempts",
195
+ },
196
+ };
3
197
 
4
198
  export const AD_REQUEST_PAGE = {
5
- // UI Text for AdRequest page
6
199
  UI: {
7
- // Heading when user is not signed in
8
200
  NOT_AUTHENTICATED_HEADING: "Not authenticated",
9
- // Message when user is not signed in
10
201
  NOT_AUTHENTICATED_MESSAGE: "You must be logged in to view this page.",
11
- // Heading when user is not admin
12
202
  UNAUTHORIZED_HEADING: "Unauthorized",
13
- // Message when user is not admin
14
203
  UNAUTHORIZED_MESSAGE: "Only admins can access the Ad Request dashboard.",
15
- // Main dashboard heading
16
204
  DASHBOARD_HEADING: "Ad Request Dashboard",
17
- // Dashboard subheading
18
205
  DASHBOARD_SUBHEADING: "Create and manage your advertising campaigns",
19
- // Message indicating admin access
20
206
  ADMIN_ACCESS_MESSAGE: "Admin access granted",
21
- // Heading for credits section
22
207
  CREDITS_HEADING: "Available Credits",
23
- // Description for credits section
24
208
  CREDITS_DESCRIPTION: "Use credits to create ad requests",
25
- // Partial text for credits reset date
26
209
  CREDITS_RESET_TEXT: "Credits reset to 3 on ",
27
- // Heading for ad request form
28
210
  CREATE_AD_REQUEST_HEADING: "Create Ad Request",
29
- // Description for ad request form
30
211
  CREATE_AD_REQUEST_DESCRIPTION: "Specify your ad campaign details",
31
- // Label for platform select
32
212
  PLATFORM_LABEL: "Select Platform *",
33
- // Placeholder for platform select
34
213
  PLATFORM_PLACEHOLDER: "Choose a platform",
35
- // Platform option for Facebook
36
214
  FACEBOOK_ADS: "Facebook Ads",
37
- // Platform option for TikTok
38
215
  TIKTOK_ADS: "TikTok Ads",
39
- // Error message when platform is not selected
40
216
  PLATFORM_REQUIRED: "Please select a platform",
41
- // Label for ad type input
42
217
  AD_TYPE_LABEL: "Ad Type *",
43
- // Placeholder for ad type input
44
218
  AD_TYPE_PLACEHOLDER: "e.g., Video ad, Carousel ad, Story ad",
45
- // Error message when ad type is empty
46
219
  AD_TYPE_REQUIRED: "Please enter an ad type",
47
- // Label for description textarea
48
220
  DESCRIPTION_LABEL: "Additional Details",
49
- // Placeholder for description textarea
50
221
  DESCRIPTION_PLACEHOLDER: "Describe your target audience, goals, or specific requirements...",
51
- // Character count text for description
52
222
  DESCRIPTION_CHAR_COUNT: "{description.length}/500 characters",
53
- // Partial text for no credits alert
54
223
  NO_CREDITS_MESSAGE: "No credits available. ",
55
- // Message for admins when no credits
56
224
  NO_CREDITS_ADMIN: "Reset to continue.",
57
- // Message for non-admins when no credits
58
225
  NO_CREDITS_NON_ADMIN: "Await next reset.",
59
- // Heading for confirm popup
60
226
  CONFIRM_POPUP_HEADING: "Confirm Ad Request",
61
- // Partial message for confirm popup
62
227
  CONFIRM_POPUP_MESSAGE: "Are you sure you want to submit this ad request? This action cannot be undone unless you contact support at ",
63
- // Email for confirm popup
64
228
  SUPPORT_EMAIL: "devvistainfo@gmail.com",
65
- // Heading for request history
66
229
  REQUEST_HISTORY_HEADING: "Request History",
67
- // Description for request history
68
230
  REQUEST_HISTORY_DESCRIPTION: "View your recent ad requests",
69
- // Message when no requests exist
70
231
  NO_REQUESTS_MESSAGE: "No requests yet",
71
- // Message encouraging first request
72
232
  FIRST_REQUEST_MESSAGE: "Submit your first ad request to get started",
73
- // Footer text about credits
74
233
  FOOTER_TEXT: "Each ad request costs 1 credit • Credits reset monthly • Admin manual reset available",
75
- // Success message after submission
76
234
  SUCCESS_MESSAGE: "Ad request submitted successfully! Credit deducted.",
77
- // Badge text for Facebook platform
78
235
  FACEBOOK_BADGE: "Facebook",
79
- // Badge text for TikTok platform
80
236
  TIKTOK_BADGE: "TikTok",
81
237
  },
82
- // Button Text for AdRequest page
83
238
  BUTTONS: {
84
- // Reset credits button
85
239
  RESET_CREDITS: "Reset Credits",
86
- // Cancel button in confirm popup
87
240
  CANCEL: "Cancel",
88
- // Confirm button in confirm popup
89
241
  CONFIRM: "Confirm",
90
- // Submit button in non-loading state
91
242
  SUBMIT_REQUEST: "Submit Request (1 Credit)",
92
- // Submit button in loading state
93
243
  SUBMITTING: "Submitting...",
94
244
  },
95
- // Error Messages for AdRequest page
96
245
  ERRORS: {
97
- // Default error for fetch failure
246
+ NO_AUTH_TOKEN: "No authentication token available",
247
+ ADMIN_CHECK_FAILED: "Failed to verify admin status after multiple attempts",
98
248
  FETCH_AD_REQUESTS_FAILED: "Failed to fetch ad requests",
99
- // Generic error for fetch and submit failures
100
249
  UNKNOWN_ERROR: "An unknown error occurred",
101
- // Error when submitting without sign-in
102
250
  NOT_SIGNED_IN: "You must be signed in to submit an ad request",
103
- // Error when no credits available
104
251
  CREDIT_LIMIT_REACHED: "Monthly ad request limit reached (3 requests)",
105
- // Error when required fields are missing
106
252
  REQUIRED_FIELDS_MISSING: "Please fill in all required fields",
107
- // Default error for submit failure
108
253
  SUBMIT_AD_REQUEST_FAILED: "Failed to submit ad request",
109
- // Error when non-admin tries to reset credits
110
254
  UNAUTHORIZED_RESET: "Unauthorized: Only admins can reset credits",
111
- // Default error for reset credits failure
112
- RESET_CREDITS_FAILED: "Failed to reset credits!!",
255
+ RESET_CREDITS_FAILED: "Failed to reset credits",
113
256
  },
114
257
  };
@@ -0,0 +1,12 @@
1
+ export const ADMIN_PROFILE_PAGE = {
2
+ UI: {
3
+ NOT_AUTHENTICATED_HEADING: 'Not Authenticated',
4
+ NOT_AUTHENTICATED_MESSAGE: 'Please sign in to access this page.',
5
+ UNAUTHORIZED_HEADING: 'Unauthorized',
6
+ UNAUTHORIZED_MESSAGE: 'You do not have permission to access this page.',
7
+ DASHBOARD_HEADING: 'Admin Profile',
8
+ DASHBOARD_SUBHEADING: 'Manage your admin profile and settings.',
9
+ WELCOME_MESSAGE: 'Welcome, Admin! This is your profile page.',
10
+ CONTENT_MESSAGE: 'This page is under construction and will include profile management features soon.',
11
+ },
12
+ };
@@ -11,25 +11,25 @@ export const HOME_PAGE = {
11
11
  // HeroSection video aria label
12
12
  HERO_VIDEO_ARIA_LABEL: "Promotional video for DevVista Kit",
13
13
  // FeaturesSection title
14
- FEATURES_TITLE: "Zumba®",
14
+ FEATURES_TITLE: 'FEATURES_TITLE HERE',
15
15
  // FeaturesSection description
16
- FEATURES_DESCRIPTION: "Variety of Memberships Available",
16
+ FEATURES_DESCRIPTION: 'FEATURES_DESCRIPTION HERE',
17
17
  // Features for FeaturesSection
18
- FEATURE_1: "24 Hour Access",
19
- FEATURE_2: "Locker Rooms",
20
- FEATURE_3: "Strength, Cardio & Flexibility Classes",
21
- FEATURE_4: "Personal Training",
22
- FEATURE_5: "Partner and Small Group Training",
18
+ FEATURE_1: 'FEATURE_1 HERE',
19
+ FEATURE_2: 'FEATURE_2 HERE',
20
+ FEATURE_3: 'FEATURE_3 HERE',
21
+ FEATURE_4: 'FEATURE_4 HERE',
22
+ FEATURE_5: 'FEATURE_5 HERE',
23
23
  // Background images for ScheduleCarousel
24
- BACKGROUND_IMAGE_1: "/images/classes/zoomba.jpg",
25
- BACKGROUND_IMAGE_2: "/images/classes/cardioxtrain.jpg",
26
- BACKGROUND_IMAGE_3: "/images/classes/seniorstretch.jpg",
27
- BACKGROUND_IMAGE_4: "/images/classes/bodyblast.jpg",
28
- BACKGROUND_IMAGE_5: "/images/classes/flexiblefriday.jpg",
29
- BACKGROUND_IMAGE_6: "/images/classes/combo.jpg",
30
- BACKGROUND_IMAGE_7: "/images/classes/noclasses.jpg",
24
+ BACKGROUND_IMAGE_1: "/images/test.png",
25
+ BACKGROUND_IMAGE_2: "/images/test.png",
26
+ BACKGROUND_IMAGE_3: "/images/test.png",
27
+ BACKGROUND_IMAGE_4: "/images/test.png",
28
+ BACKGROUND_IMAGE_5: "/images/test.png",
29
+ BACKGROUND_IMAGE_6: "/images/test.png",
30
+ BACKGROUND_IMAGE_7: "/images/test.png",
31
31
  // Video source for HeroSection
32
- HERO_VIDEO_SRC: "/images/videos/TestLoopVideo.mp4",
32
+ HERO_VIDEO_SRC: "",
33
33
  },
34
34
  // Button Text for HomePage
35
35
  BUTTONS: {
@@ -0,0 +1,44 @@
1
+ // lib/google/google-analytics-tracking.tsx
2
+ "use client";
3
+
4
+ export default function GoogleAnalyticsTracking() {
5
+ const gaMeasurementId = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;
6
+
7
+ if (!gaMeasurementId) {
8
+ console.warn("Google Analytics measurement ID is not defined.");
9
+ return null;
10
+ }
11
+
12
+ if (!/^G-[A-Z0-9]{8,}$/.test(gaMeasurementId)) {
13
+ console.warn("Invalid Google Analytics measurement ID:", gaMeasurementId);
14
+ return null;
15
+ }
16
+
17
+ try {
18
+ return (
19
+ <>
20
+ <script
21
+ async
22
+ src={`https://www.googletagmanager.com/gtag/js?id=${gaMeasurementId}`}
23
+ />
24
+ <script
25
+ dangerouslySetInnerHTML={{
26
+ __html: `
27
+ try {
28
+ window.dataLayer = window.dataLayer || [];
29
+ function gtag(){dataLayer.push(arguments);}
30
+ gtag('js', new Date());
31
+ gtag('config', '${gaMeasurementId}');
32
+ } catch (e) {
33
+ console.warn('Google Analytics script error:', e);
34
+ }
35
+ `,
36
+ }}
37
+ />
38
+ </>
39
+ );
40
+ } catch (error) {
41
+ console.warn("Failed to render Google Analytics tracking:", error);
42
+ return null;
43
+ }
44
+ }