@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
|
@@ -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
|
-
//
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/lib/constants/page.ts
CHANGED
|
@@ -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:
|
|
14
|
+
FEATURES_TITLE: 'FEATURES_TITLE HERE',
|
|
15
15
|
// FeaturesSection description
|
|
16
|
-
FEATURES_DESCRIPTION:
|
|
16
|
+
FEATURES_DESCRIPTION: 'FEATURES_DESCRIPTION HERE',
|
|
17
17
|
// Features for FeaturesSection
|
|
18
|
-
FEATURE_1:
|
|
19
|
-
FEATURE_2:
|
|
20
|
-
FEATURE_3:
|
|
21
|
-
FEATURE_4:
|
|
22
|
-
FEATURE_5:
|
|
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/
|
|
25
|
-
BACKGROUND_IMAGE_2: "/images/
|
|
26
|
-
BACKGROUND_IMAGE_3: "/images/
|
|
27
|
-
BACKGROUND_IMAGE_4: "/images/
|
|
28
|
-
BACKGROUND_IMAGE_5: "/images/
|
|
29
|
-
BACKGROUND_IMAGE_6: "/images/
|
|
30
|
-
BACKGROUND_IMAGE_7: "/images/
|
|
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: "
|
|
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
|
+
}
|