@payez/next-mvp 3.9.1 → 4.0.0
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/dist/api/auth-handler.d.ts +1 -2
- package/dist/api/auth-handler.js +9 -9
- package/dist/api-handlers/account/change-password.js +110 -112
- package/dist/api-handlers/admin/analytics.d.ts +19 -20
- package/dist/api-handlers/admin/analytics.js +378 -379
- package/dist/api-handlers/admin/audit.d.ts +19 -20
- package/dist/api-handlers/admin/audit.js +213 -214
- package/dist/api-handlers/admin/index.d.ts +21 -22
- package/dist/api-handlers/admin/index.js +42 -43
- package/dist/api-handlers/admin/redis-sessions.d.ts +35 -36
- package/dist/api-handlers/admin/redis-sessions.js +203 -204
- package/dist/api-handlers/admin/sessions.d.ts +20 -21
- package/dist/api-handlers/admin/sessions.js +283 -284
- package/dist/api-handlers/admin/site-logs.d.ts +45 -46
- package/dist/api-handlers/admin/site-logs.js +317 -318
- package/dist/api-handlers/admin/stats.d.ts +20 -21
- package/dist/api-handlers/admin/stats.js +239 -240
- package/dist/api-handlers/admin/users.d.ts +19 -20
- package/dist/api-handlers/admin/users.js +221 -222
- package/dist/api-handlers/admin/vibe-data.d.ts +79 -80
- package/dist/api-handlers/admin/vibe-data.js +267 -268
- package/dist/api-handlers/auth/refresh.js +633 -635
- package/dist/api-handlers/auth/signout.js +186 -187
- package/dist/api-handlers/auth/status.js +4 -7
- package/dist/api-handlers/auth/update-session.d.ts +1 -1
- package/dist/api-handlers/auth/update-session.js +12 -14
- package/dist/api-handlers/auth/verify-code.d.ts +43 -43
- package/dist/api-handlers/auth/verify-code.js +90 -94
- package/dist/api-handlers/session/viability.js +114 -146
- package/dist/api-handlers/test/force-expire.js +59 -65
- package/dist/auth/auth-decision.js +182 -182
- package/dist/auth/better-auth.d.ts +3 -6
- package/dist/auth/better-auth.js +3 -6
- package/dist/auth/route-config.js +2 -2
- package/dist/auth/utils/token-utils.d.ts +83 -84
- package/dist/auth/utils/token-utils.js +218 -219
- package/dist/client/AuthContext.js +115 -112
- package/dist/client/better-auth-client.d.ts +1020 -1020
- package/dist/client/fetch-with-auth.js +2 -2
- package/dist/components/SessionSync.js +121 -119
- package/dist/components/account/MobileNavDrawer.js +64 -64
- package/dist/components/account/UserAvatarMenu.js +91 -88
- package/dist/components/admin/VibeAdminLayout.js +71 -69
- package/dist/hooks/useAuth.js +9 -7
- package/dist/hooks/useAuthSettings.js +93 -93
- package/dist/hooks/useAvailableProviders.d.ts +43 -45
- package/dist/hooks/useAvailableProviders.js +112 -108
- package/dist/hooks/useSessionExpiration.d.ts +2 -3
- package/dist/hooks/useSessionExpiration.js +2 -2
- package/dist/hooks/useViabilitySession.js +3 -2
- package/dist/index.js +4 -6
- package/dist/lib/app-slug.d.ts +95 -95
- package/dist/lib/app-slug.js +172 -172
- package/dist/lib/standardized-client-api.js +10 -5
- package/dist/lib/startup-init.js +21 -25
- package/dist/lib/test-aware-get-token.js +86 -81
- package/dist/lib/token-lifecycle.d.ts +78 -52
- package/dist/lib/token-lifecycle.js +360 -398
- package/dist/pages/admin-login/page.js +73 -83
- package/dist/pages/client-admin/ClientSiteAdminPage.js +179 -177
- package/dist/pages/login/page.js +202 -211
- package/dist/pages/showcase/ShowcasePage.js +142 -140
- package/dist/pages/test-env/EmergencyLogoutPage.js +99 -98
- package/dist/pages/test-env/JwtInspectPage.js +116 -114
- package/dist/pages/test-env/RefreshTokenPage.js +4 -2
- package/dist/pages/test-env/TestEnvPage.js +51 -49
- package/dist/pages/verify-code/page.js +412 -408
- package/dist/routes/auth/logout.d.ts +31 -31
- package/dist/routes/auth/logout.js +98 -113
- package/dist/routes/auth/nextauth.d.ts +14 -11
- package/dist/routes/auth/nextauth.js +25 -57
- package/dist/routes/auth/session.js +157 -179
- package/dist/routes/auth/viability.js +190 -201
- package/dist/server/auth.d.ts +50 -0
- package/dist/server/auth.js +62 -0
- package/dist/stores/authStore.js +19 -23
- package/dist/utils/logout.js +5 -5
- package/package.json +1 -3
- package/src/api/auth-handler.ts +550 -549
- package/src/api-handlers/account/change-password.ts +5 -8
- package/src/api-handlers/admin/analytics.ts +4 -6
- package/src/api-handlers/admin/audit.ts +5 -7
- package/src/api-handlers/admin/index.ts +1 -2
- package/src/api-handlers/admin/redis-sessions.ts +6 -8
- package/src/api-handlers/admin/sessions.ts +5 -7
- package/src/api-handlers/admin/site-logs.ts +8 -10
- package/src/api-handlers/admin/stats.ts +4 -6
- package/src/api-handlers/admin/users.ts +5 -7
- package/src/api-handlers/admin/vibe-data.ts +10 -12
- package/src/api-handlers/auth/refresh.ts +5 -7
- package/src/api-handlers/auth/signout.ts +5 -6
- package/src/api-handlers/auth/status.ts +4 -7
- package/src/api-handlers/auth/update-session.ts +123 -125
- package/src/api-handlers/auth/verify-code.ts +9 -13
- package/src/api-handlers/session/viability.ts +10 -47
- package/src/api-handlers/test/force-expire.ts +4 -11
- package/src/auth/auth-decision.ts +1 -1
- package/src/auth/better-auth.ts +138 -141
- package/src/auth/route-config.ts +219 -219
- package/src/auth/utils/token-utils.ts +0 -1
- package/src/client/AuthContext.tsx +6 -2
- package/src/client/fetch-with-auth.ts +47 -47
- package/src/components/SessionSync.tsx +6 -5
- package/src/components/account/MobileNavDrawer.tsx +3 -3
- package/src/components/account/UserAvatarMenu.tsx +6 -3
- package/src/components/admin/VibeAdminLayout.tsx +4 -2
- package/src/config/logger.ts +1 -1
- package/src/hooks/useAuth.ts +117 -115
- package/src/hooks/useAuthSettings.ts +2 -2
- package/src/hooks/useAvailableProviders.ts +9 -5
- package/src/hooks/useSessionExpiration.ts +101 -102
- package/src/hooks/useViabilitySession.ts +336 -335
- package/src/index.ts +60 -63
- package/src/lib/api-handler.ts +0 -1
- package/src/lib/app-slug.ts +6 -6
- package/src/lib/standardized-client-api.ts +901 -895
- package/src/lib/startup-init.ts +243 -247
- package/src/lib/test-aware-get-token.ts +22 -12
- package/src/lib/token-lifecycle.ts +12 -53
- package/src/pages/admin-login/page.tsx +9 -17
- package/src/pages/client-admin/ClientSiteAdminPage.tsx +4 -2
- package/src/pages/login/page.tsx +21 -28
- package/src/pages/showcase/ShowcasePage.tsx +4 -2
- package/src/pages/test-env/EmergencyLogoutPage.tsx +7 -6
- package/src/pages/test-env/JwtInspectPage.tsx +5 -3
- package/src/pages/test-env/RefreshTokenPage.tsx +157 -155
- package/src/pages/test-env/TestEnvPage.tsx +4 -2
- package/src/pages/verify-code/page.tsx +10 -6
- package/src/routes/auth/logout.ts +7 -25
- package/src/routes/auth/nextauth.ts +45 -71
- package/src/routes/auth/session.ts +25 -50
- package/src/routes/auth/viability.ts +7 -19
- package/src/server/auth.ts +60 -0
- package/src/stores/authStore.ts +1899 -1904
- package/src/utils/logout.ts +30 -30
- package/src/auth/auth-options.ts +0 -237
- package/src/auth/callbacks/index.ts +0 -7
- package/src/auth/callbacks/jwt.ts +0 -382
- package/src/auth/callbacks/session.ts +0 -243
- package/src/auth/callbacks/signin.ts +0 -56
- package/src/auth/events/index.ts +0 -5
- package/src/auth/events/signout.ts +0 -33
- package/src/auth/providers/credentials.ts +0 -256
- package/src/auth/providers/index.ts +0 -6
- package/src/auth/providers/oauth.ts +0 -114
- package/src/lib/nextauth-secret.ts +0 -121
- package/src/types/next-auth.d.ts +0 -15
|
@@ -1,83 +1,73 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Admin Login Page for @payez/next-mvp
|
|
4
|
-
*
|
|
5
|
-
* A standalone username/password login page for admin access.
|
|
6
|
-
* NOT linked from any navigation - only accessible via direct URL.
|
|
7
|
-
*
|
|
8
|
-
* USAGE:
|
|
9
|
-
* 1. Create app/account-auth/admin-login/page.tsx in your Next.js app
|
|
10
|
-
* 2. Re-export this component:
|
|
11
|
-
* export { default } from '@payez/next-mvp/pages/admin-login';
|
|
12
|
-
*
|
|
13
|
-
* CUSTOMIZATION:
|
|
14
|
-
* - Override styles via CSS variables or wrap with your own component
|
|
15
|
-
* - Provide custom branding via ThemeProvider
|
|
16
|
-
*/
|
|
17
|
-
'use client';
|
|
18
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.default = AdminLoginPage;
|
|
20
|
-
exports.AdminLoginForm = AdminLoginForm;
|
|
21
|
-
exports.AdminLoginFallback = AdminLoginFallback;
|
|
22
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
23
|
-
const react_1 = require("react");
|
|
24
|
-
const
|
|
25
|
-
const navigation_1 = require("next/navigation");
|
|
26
|
-
const
|
|
27
|
-
const useTheme_1 = require("../../theme/useTheme");
|
|
28
|
-
function AdminLoginForm({ title = 'Admin Login', subtitle = 'Authorized personnel only', callbackUrl: propCallbackUrl, logo, }) {
|
|
29
|
-
const searchParams = (0, navigation_1.useSearchParams)();
|
|
30
|
-
const callbackUrl = propCallbackUrl || searchParams?.get('callbackUrl') || '/dashboard';
|
|
31
|
-
const branding = (0, useTheme_1.useBranding)();
|
|
32
|
-
const colors = (0, useTheme_1.useColors)();
|
|
33
|
-
const [email, setEmail] = (0, react_1.useState)('');
|
|
34
|
-
const [password, setPassword] = (0, react_1.useState)('');
|
|
35
|
-
const [showPassword, setShowPassword] = (0, react_1.useState)(false);
|
|
36
|
-
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
37
|
-
const [error, setError] = (0, react_1.useState)(null);
|
|
38
|
-
const handleSubmit = async (e) => {
|
|
39
|
-
e.preventDefault();
|
|
40
|
-
setIsLoading(true);
|
|
41
|
-
setError(null);
|
|
42
|
-
try {
|
|
43
|
-
const result = await
|
|
44
|
-
email,
|
|
45
|
-
password,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: "min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-700 via-slate-800 to-slate-900 p-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-white rounded-2xl shadow-2xl p-8 max-w-md w-full", children: [logo && ((0, jsx_runtime_1.jsx)("div", { className: "flex justify-center mb-6", children: logo })), (0, jsx_runtime_1.jsx)("h1", { className: "text-2xl font-bold text-center mb-2 text-slate-900", children: title }), (0, jsx_runtime_1.jsx)("p", { className: "text-center mb-8 text-slate-600", children: subtitle }), error && ((0, jsx_runtime_1.jsx)("div", { className: "mb-6 px-4 py-3 rounded-lg bg-red-500 text-white text-center text-sm", children: error })), (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "admin-email", className: "block text-sm font-medium mb-2 text-slate-700", children: "Email" }), (0, jsx_runtime_1.jsx)("input", { id: "admin-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: isLoading, autoComplete: "email", className: "w-full px-4 py-3 rounded-lg border border-slate-300 bg-white text-slate-900 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed", placeholder: "admin@example.com" })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "admin-password", className: "block text-sm font-medium mb-2 text-slate-700", children: "Password" }), (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)("input", { id: "admin-password", type: showPassword ? 'text' : 'password', value: password, onChange: (e) => setPassword(e.target.value), required: true, disabled: isLoading, autoComplete: "current-password", className: "w-full px-4 py-3 pr-12 rounded-lg border border-slate-300 bg-white text-slate-900 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed", placeholder: "Enter your password" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setShowPassword(!showPassword), className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-slate-500 hover:text-slate-700", "aria-label": showPassword ? 'Hide password' : 'Show password', children: showPassword ? ((0, jsx_runtime_1.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", className: "w-5 h-5", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L6.464 6.464m7.535 7.535l3.415 3.414M3 3l3.464 3.464M21 21l-3.415-3.414" }) })) : ((0, jsx_runtime_1.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", className: "w-5 h-5", children: [(0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" }), (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" })] })) })] })] }), (0, jsx_runtime_1.jsx)("button", { type: "submit", disabled: isLoading, className: "w-full py-3 px-4 rounded-lg font-semibold text-white transition-colors bg-slate-700 hover:bg-slate-800 disabled:bg-slate-400 disabled:cursor-not-allowed", children: isLoading ? ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center justify-center", children: [(0, jsx_runtime_1.jsxs)("svg", { className: "animate-spin -ml-1 mr-3 h-5 w-5 text-white", fill: "none", viewBox: "0 0 24 24", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), "Signing in..."] })) : ('Sign In') })] }), (0, jsx_runtime_1.jsxs)("p", { className: "mt-6 text-center text-xs text-slate-500", children: ["This login is for authorized administrators only.", (0, jsx_runtime_1.jsx)("br", {}), "All access attempts are logged."] })] }) }));
|
|
77
|
-
}
|
|
78
|
-
function AdminLoginFallback() {
|
|
79
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: "min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-700 via-slate-800 to-slate-900", children: (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsxs)("svg", { className: "animate-spin h-10 w-10 mx-auto text-white", fill: "none", viewBox: "0 0 24 24", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), (0, jsx_runtime_1.jsx)("p", { className: "mt-4 text-slate-400", children: "Loading..." })] }) }));
|
|
80
|
-
}
|
|
81
|
-
function AdminLoginPage(props) {
|
|
82
|
-
return ((0, jsx_runtime_1.jsx)(react_3.Suspense, { fallback: (0, jsx_runtime_1.jsx)(AdminLoginFallback, {}), children: (0, jsx_runtime_1.jsx)(AdminLoginForm, { ...props }) }));
|
|
83
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Admin Login Page for @payez/next-mvp
|
|
4
|
+
*
|
|
5
|
+
* A standalone username/password login page for admin access.
|
|
6
|
+
* NOT linked from any navigation - only accessible via direct URL.
|
|
7
|
+
*
|
|
8
|
+
* USAGE:
|
|
9
|
+
* 1. Create app/account-auth/admin-login/page.tsx in your Next.js app
|
|
10
|
+
* 2. Re-export this component:
|
|
11
|
+
* export { default } from '@payez/next-mvp/pages/admin-login';
|
|
12
|
+
*
|
|
13
|
+
* CUSTOMIZATION:
|
|
14
|
+
* - Override styles via CSS variables or wrap with your own component
|
|
15
|
+
* - Provide custom branding via ThemeProvider
|
|
16
|
+
*/
|
|
17
|
+
'use client';
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.default = AdminLoginPage;
|
|
20
|
+
exports.AdminLoginForm = AdminLoginForm;
|
|
21
|
+
exports.AdminLoginFallback = AdminLoginFallback;
|
|
22
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
23
|
+
const react_1 = require("react");
|
|
24
|
+
const better_auth_client_1 = require("../../client/better-auth-client");
|
|
25
|
+
const navigation_1 = require("next/navigation");
|
|
26
|
+
const react_2 = require("react");
|
|
27
|
+
const useTheme_1 = require("../../theme/useTheme");
|
|
28
|
+
function AdminLoginForm({ title = 'Admin Login', subtitle = 'Authorized personnel only', callbackUrl: propCallbackUrl, logo, }) {
|
|
29
|
+
const searchParams = (0, navigation_1.useSearchParams)();
|
|
30
|
+
const callbackUrl = propCallbackUrl || searchParams?.get('callbackUrl') || '/dashboard';
|
|
31
|
+
const branding = (0, useTheme_1.useBranding)();
|
|
32
|
+
const colors = (0, useTheme_1.useColors)();
|
|
33
|
+
const [email, setEmail] = (0, react_1.useState)('');
|
|
34
|
+
const [password, setPassword] = (0, react_1.useState)('');
|
|
35
|
+
const [showPassword, setShowPassword] = (0, react_1.useState)(false);
|
|
36
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
37
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
38
|
+
const handleSubmit = async (e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
setIsLoading(true);
|
|
41
|
+
setError(null);
|
|
42
|
+
try {
|
|
43
|
+
const result = await better_auth_client_1.authClient.signIn.email({
|
|
44
|
+
email,
|
|
45
|
+
password,
|
|
46
|
+
callbackURL: callbackUrl,
|
|
47
|
+
});
|
|
48
|
+
if (result?.error) {
|
|
49
|
+
const errorMsg = typeof result.error === 'object'
|
|
50
|
+
? result.error.message || 'Invalid credentials'
|
|
51
|
+
: String(result.error);
|
|
52
|
+
setError(errorMsg);
|
|
53
|
+
}
|
|
54
|
+
else if (result?.data) {
|
|
55
|
+
// Redirect to verify-code for 2FA or directly to callback
|
|
56
|
+
window.location.href = `/account-auth/verify-code?callbackUrl=${encodeURIComponent(callbackUrl)}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
setError('An unexpected error occurred');
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
setIsLoading(false);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-700 via-slate-800 to-slate-900 p-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-white rounded-2xl shadow-2xl p-8 max-w-md w-full", children: [logo && ((0, jsx_runtime_1.jsx)("div", { className: "flex justify-center mb-6", children: logo })), (0, jsx_runtime_1.jsx)("h1", { className: "text-2xl font-bold text-center mb-2 text-slate-900", children: title }), (0, jsx_runtime_1.jsx)("p", { className: "text-center mb-8 text-slate-600", children: subtitle }), error && ((0, jsx_runtime_1.jsx)("div", { className: "mb-6 px-4 py-3 rounded-lg bg-red-500 text-white text-center text-sm", children: error })), (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "admin-email", className: "block text-sm font-medium mb-2 text-slate-700", children: "Email" }), (0, jsx_runtime_1.jsx)("input", { id: "admin-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: isLoading, autoComplete: "email", className: "w-full px-4 py-3 rounded-lg border border-slate-300 bg-white text-slate-900 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed", placeholder: "admin@example.com" })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "admin-password", className: "block text-sm font-medium mb-2 text-slate-700", children: "Password" }), (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)("input", { id: "admin-password", type: showPassword ? 'text' : 'password', value: password, onChange: (e) => setPassword(e.target.value), required: true, disabled: isLoading, autoComplete: "current-password", className: "w-full px-4 py-3 pr-12 rounded-lg border border-slate-300 bg-white text-slate-900 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed", placeholder: "Enter your password" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setShowPassword(!showPassword), className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-slate-500 hover:text-slate-700", "aria-label": showPassword ? 'Hide password' : 'Show password', children: showPassword ? ((0, jsx_runtime_1.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", className: "w-5 h-5", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L6.464 6.464m7.535 7.535l3.415 3.414M3 3l3.464 3.464M21 21l-3.415-3.414" }) })) : ((0, jsx_runtime_1.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", className: "w-5 h-5", children: [(0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" }), (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" })] })) })] })] }), (0, jsx_runtime_1.jsx)("button", { type: "submit", disabled: isLoading, className: "w-full py-3 px-4 rounded-lg font-semibold text-white transition-colors bg-slate-700 hover:bg-slate-800 disabled:bg-slate-400 disabled:cursor-not-allowed", children: isLoading ? ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center justify-center", children: [(0, jsx_runtime_1.jsxs)("svg", { className: "animate-spin -ml-1 mr-3 h-5 w-5 text-white", fill: "none", viewBox: "0 0 24 24", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), "Signing in..."] })) : ('Sign In') })] }), (0, jsx_runtime_1.jsxs)("p", { className: "mt-6 text-center text-xs text-slate-500", children: ["This login is for authorized administrators only.", (0, jsx_runtime_1.jsx)("br", {}), "All access attempts are logged."] })] }) }));
|
|
67
|
+
}
|
|
68
|
+
function AdminLoginFallback() {
|
|
69
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-700 via-slate-800 to-slate-900", children: (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsxs)("svg", { className: "animate-spin h-10 w-10 mx-auto text-white", fill: "none", viewBox: "0 0 24 24", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), (0, jsx_runtime_1.jsx)("p", { className: "mt-4 text-slate-400", children: "Loading..." })] }) }));
|
|
70
|
+
}
|
|
71
|
+
function AdminLoginPage(props) {
|
|
72
|
+
return ((0, jsx_runtime_1.jsx)(react_2.Suspense, { fallback: (0, jsx_runtime_1.jsx)(AdminLoginFallback, {}), children: (0, jsx_runtime_1.jsx)(AdminLoginForm, { ...props }) }));
|
|
73
|
+
}
|
|
@@ -1,177 +1,179 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
'use client';
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.ClientSiteAdminPage = ClientSiteAdminPage;
|
|
8
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
-
/**
|
|
10
|
-
* =============================================================================
|
|
11
|
-
* CLIENT SITE ADMIN - Your App's Admin Dashboard
|
|
12
|
-
* =============================================================================
|
|
13
|
-
*
|
|
14
|
-
* This is the foundation for YOUR app-specific admin area.
|
|
15
|
-
* The MVP admin (/admin) handles membership - this handles YOUR data.
|
|
16
|
-
*
|
|
17
|
-
* SEPARATION OF CONCERNS:
|
|
18
|
-
* ┌─────────────────────────────────────────────────────────────────────┐
|
|
19
|
-
* │ /admin (MVP Admin) │ /admin/[your-area] (This) │
|
|
20
|
-
* │ ───────────────── │ ──────────────────────── │
|
|
21
|
-
* │ • User authentication │ • Your app's collections │
|
|
22
|
-
* │ • Sessions management │ • Your app's features │
|
|
23
|
-
* │ • Role assignments │ • Content moderation │
|
|
24
|
-
* │ • Tier/subscription mgmt │ • App-specific analytics │
|
|
25
|
-
* │ • Site-wide analytics │ • Custom admin tools │
|
|
26
|
-
* │ • vibe_app collection │ • your_app collection │
|
|
27
|
-
* └─────────────────────────────────────────────────────────────────────┘
|
|
28
|
-
*
|
|
29
|
-
* USAGE:
|
|
30
|
-
* ```tsx
|
|
31
|
-
* // app/admin/your-area/page.tsx
|
|
32
|
-
* import { ClientSiteAdminPage } from '@payez/next-mvp/pages/client-admin';
|
|
33
|
-
*
|
|
34
|
-
* export default function YourAdminPage() {
|
|
35
|
-
* return (
|
|
36
|
-
* <ClientSiteAdminPage
|
|
37
|
-
* title="Your Admin"
|
|
38
|
-
* subtitle="Manage your app data"
|
|
39
|
-
* collectionName="your_collection"
|
|
40
|
-
* tabs={[
|
|
41
|
-
* { id: 'overview', label: 'Overview', icon: '📊' },
|
|
42
|
-
* { id: 'items', label: 'Items', icon: '📦', table: 'items' },
|
|
43
|
-
* ]}
|
|
44
|
-
* renderTabContent={(tab, data) => <YourCustomContent />}
|
|
45
|
-
* />
|
|
46
|
-
* );
|
|
47
|
-
* }
|
|
48
|
-
* ```
|
|
49
|
-
*
|
|
50
|
-
* =============================================================================
|
|
51
|
-
*/
|
|
52
|
-
const react_1 = require("react");
|
|
53
|
-
const
|
|
54
|
-
const navigation_1 = require("next/navigation");
|
|
55
|
-
const link_1 = __importDefault(require("next/link"));
|
|
56
|
-
// Icons - using basic SVGs to avoid lucide dependency issues
|
|
57
|
-
const IconRefresh = () => ((0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }));
|
|
58
|
-
const IconArrowLeft = () => ((0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 19l-7-7m0 0l7-7m-7 7h18" }) }));
|
|
59
|
-
const IconShield = () => ((0, jsx_runtime_1.jsx)("svg", { className: "w-16 h-16", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" }) }));
|
|
60
|
-
const IconChart = ({ className = "w-5 h-5" }) => ((0, jsx_runtime_1.jsx)("svg", { className: className, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" }) }));
|
|
61
|
-
function ClientSiteAdminPage({ title, subtitle = 'Manage your app data', collectionName, tabs, allowedRoles = ['vibe_app_admin'], renderOverview, renderTabContent, isDark: isDarkProp, backUrl = '/', backLabel = 'Back to Site', }) {
|
|
62
|
-
const { data:
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const [
|
|
83
|
-
const [
|
|
84
|
-
const [
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// Default
|
|
175
|
-
(0, jsx_runtime_1.jsxs)("div", {
|
|
176
|
-
|
|
177
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ClientSiteAdminPage = ClientSiteAdminPage;
|
|
8
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
+
/**
|
|
10
|
+
* =============================================================================
|
|
11
|
+
* CLIENT SITE ADMIN - Your App's Admin Dashboard
|
|
12
|
+
* =============================================================================
|
|
13
|
+
*
|
|
14
|
+
* This is the foundation for YOUR app-specific admin area.
|
|
15
|
+
* The MVP admin (/admin) handles membership - this handles YOUR data.
|
|
16
|
+
*
|
|
17
|
+
* SEPARATION OF CONCERNS:
|
|
18
|
+
* ┌─────────────────────────────────────────────────────────────────────┐
|
|
19
|
+
* │ /admin (MVP Admin) │ /admin/[your-area] (This) │
|
|
20
|
+
* │ ───────────────── │ ──────────────────────── │
|
|
21
|
+
* │ • User authentication │ • Your app's collections │
|
|
22
|
+
* │ • Sessions management │ • Your app's features │
|
|
23
|
+
* │ • Role assignments │ • Content moderation │
|
|
24
|
+
* │ • Tier/subscription mgmt │ • App-specific analytics │
|
|
25
|
+
* │ • Site-wide analytics │ • Custom admin tools │
|
|
26
|
+
* │ • vibe_app collection │ • your_app collection │
|
|
27
|
+
* └─────────────────────────────────────────────────────────────────────┘
|
|
28
|
+
*
|
|
29
|
+
* USAGE:
|
|
30
|
+
* ```tsx
|
|
31
|
+
* // app/admin/your-area/page.tsx
|
|
32
|
+
* import { ClientSiteAdminPage } from '@payez/next-mvp/pages/client-admin';
|
|
33
|
+
*
|
|
34
|
+
* export default function YourAdminPage() {
|
|
35
|
+
* return (
|
|
36
|
+
* <ClientSiteAdminPage
|
|
37
|
+
* title="Your Admin"
|
|
38
|
+
* subtitle="Manage your app data"
|
|
39
|
+
* collectionName="your_collection"
|
|
40
|
+
* tabs={[
|
|
41
|
+
* { id: 'overview', label: 'Overview', icon: '📊' },
|
|
42
|
+
* { id: 'items', label: 'Items', icon: '📦', table: 'items' },
|
|
43
|
+
* ]}
|
|
44
|
+
* renderTabContent={(tab, data) => <YourCustomContent />}
|
|
45
|
+
* />
|
|
46
|
+
* );
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* =============================================================================
|
|
51
|
+
*/
|
|
52
|
+
const react_1 = require("react");
|
|
53
|
+
const better_auth_client_1 = require("../../client/better-auth-client");
|
|
54
|
+
const navigation_1 = require("next/navigation");
|
|
55
|
+
const link_1 = __importDefault(require("next/link"));
|
|
56
|
+
// Icons - using basic SVGs to avoid lucide dependency issues
|
|
57
|
+
const IconRefresh = () => ((0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }));
|
|
58
|
+
const IconArrowLeft = () => ((0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 19l-7-7m0 0l7-7m-7 7h18" }) }));
|
|
59
|
+
const IconShield = () => ((0, jsx_runtime_1.jsx)("svg", { className: "w-16 h-16", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" }) }));
|
|
60
|
+
const IconChart = ({ className = "w-5 h-5" }) => ((0, jsx_runtime_1.jsx)("svg", { className: className, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" }) }));
|
|
61
|
+
function ClientSiteAdminPage({ title, subtitle = 'Manage your app data', collectionName, tabs, allowedRoles = ['vibe_app_admin'], renderOverview, renderTabContent, isDark: isDarkProp, backUrl = '/', backLabel = 'Back to Site', }) {
|
|
62
|
+
const { data: sessionData, isPending } = better_auth_client_1.authClient.useSession();
|
|
63
|
+
const session = sessionData;
|
|
64
|
+
const status = isPending ? 'loading' : session ? 'authenticated' : 'unauthenticated';
|
|
65
|
+
const router = (0, navigation_1.useRouter)();
|
|
66
|
+
// Theme detection
|
|
67
|
+
const [isDarkState, setIsDarkState] = (0, react_1.useState)(false);
|
|
68
|
+
(0, react_1.useEffect)(() => {
|
|
69
|
+
if (isDarkProp === undefined) {
|
|
70
|
+
const checkDark = () => {
|
|
71
|
+
const isDark = document.documentElement.classList.contains('dark') ||
|
|
72
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
73
|
+
setIsDarkState(isDark);
|
|
74
|
+
};
|
|
75
|
+
checkDark();
|
|
76
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
77
|
+
mq.addEventListener('change', checkDark);
|
|
78
|
+
return () => mq.removeEventListener('change', checkDark);
|
|
79
|
+
}
|
|
80
|
+
}, [isDarkProp]);
|
|
81
|
+
const isDark = isDarkProp ?? isDarkState;
|
|
82
|
+
const [activeTab, setActiveTab] = (0, react_1.useState)(tabs[0]?.id || 'overview');
|
|
83
|
+
const [tableData, setTableData] = (0, react_1.useState)([]);
|
|
84
|
+
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
85
|
+
const [stats, setStats] = (0, react_1.useState)([]);
|
|
86
|
+
const [statsLoading, setStatsLoading] = (0, react_1.useState)(false);
|
|
87
|
+
const userRoles = session?.user?.roles || [];
|
|
88
|
+
const hasAccess = allowedRoles.some(role => userRoles.includes(role));
|
|
89
|
+
const themeClasses = {
|
|
90
|
+
bg: isDark ? 'bg-slate-950' : 'bg-gray-50',
|
|
91
|
+
cardBg: isDark ? 'bg-slate-800 border-slate-700' : 'bg-white border-gray-200',
|
|
92
|
+
headerBg: isDark ? 'bg-slate-900 border-slate-800' : 'bg-white border-gray-200',
|
|
93
|
+
text: isDark ? 'text-white' : 'text-gray-900',
|
|
94
|
+
textMuted: isDark ? 'text-slate-400' : 'text-gray-600',
|
|
95
|
+
tabBg: isDark ? 'bg-slate-800' : 'bg-gray-100',
|
|
96
|
+
tabActive: 'bg-amber-600 text-white shadow-lg',
|
|
97
|
+
tabInactive: isDark ? 'text-slate-400 hover:bg-slate-700' : 'text-gray-600 hover:bg-gray-200',
|
|
98
|
+
};
|
|
99
|
+
// Fetch collection stats
|
|
100
|
+
const fetchStats = (0, react_1.useCallback)(async () => {
|
|
101
|
+
setStatsLoading(true);
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch(`/api/vibe/collections/${collectionName}/tables`);
|
|
104
|
+
if (res.ok) {
|
|
105
|
+
const data = await res.json();
|
|
106
|
+
const tables = data.tables || data || [];
|
|
107
|
+
setStats(tables.map((tbl) => ({
|
|
108
|
+
table: typeof tbl === 'string' ? tbl : tbl.name,
|
|
109
|
+
count: typeof tbl === 'object' ? (tbl.document_count || tbl.count || 0) : 0,
|
|
110
|
+
label: (typeof tbl === 'string' ? tbl : tbl.name)
|
|
111
|
+
.replace(/_/g, ' ')
|
|
112
|
+
.replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
113
|
+
})));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.error('Failed to fetch stats:', err);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
setStatsLoading(false);
|
|
121
|
+
}
|
|
122
|
+
}, [collectionName]);
|
|
123
|
+
// Fetch table data
|
|
124
|
+
const fetchTableData = (0, react_1.useCallback)(async (tableName) => {
|
|
125
|
+
setLoading(true);
|
|
126
|
+
try {
|
|
127
|
+
const res = await fetch(`/api/vibe/data/${collectionName}/${tableName}?limit=100`);
|
|
128
|
+
if (res.ok) {
|
|
129
|
+
const data = await res.json();
|
|
130
|
+
setTableData(data.data || []);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.error('Failed to fetch table:', err);
|
|
135
|
+
setTableData([]);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
setLoading(false);
|
|
139
|
+
}
|
|
140
|
+
}, [collectionName]);
|
|
141
|
+
// Handle tab change
|
|
142
|
+
const handleTabChange = (0, react_1.useCallback)((tabId) => {
|
|
143
|
+
setActiveTab(tabId);
|
|
144
|
+
const tab = tabs.find(t => t.id === tabId);
|
|
145
|
+
if (tab?.table) {
|
|
146
|
+
fetchTableData(tab.table);
|
|
147
|
+
}
|
|
148
|
+
}, [tabs, fetchTableData]);
|
|
149
|
+
// Initial load
|
|
150
|
+
(0, react_1.useEffect)(() => {
|
|
151
|
+
if (status === 'unauthenticated') {
|
|
152
|
+
router.push(`/account-auth/login?callbackUrl=${encodeURIComponent(window.location.pathname)}`);
|
|
153
|
+
}
|
|
154
|
+
else if (status === 'authenticated' && !hasAccess) {
|
|
155
|
+
router.push('/?error=unauthorized');
|
|
156
|
+
}
|
|
157
|
+
else if (status === 'authenticated' && hasAccess) {
|
|
158
|
+
fetchStats();
|
|
159
|
+
}
|
|
160
|
+
}, [status, hasAccess, router, fetchStats]);
|
|
161
|
+
// Loading state
|
|
162
|
+
if (status === 'loading') {
|
|
163
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: `min-h-screen flex items-center justify-center ${themeClasses.bg}`, children: (0, jsx_runtime_1.jsx)("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-amber-500" }) }));
|
|
164
|
+
}
|
|
165
|
+
// Access denied
|
|
166
|
+
if (status === 'authenticated' && !hasAccess) {
|
|
167
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: `min-h-screen flex items-center justify-center ${themeClasses.bg}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: isDark ? 'text-red-400' : 'text-red-500', children: (0, jsx_runtime_1.jsx)(IconShield, {}) }), (0, jsx_runtime_1.jsx)("h1", { className: `text-2xl font-bold mb-2 ${themeClasses.text}`, children: "Access Denied" }), (0, jsx_runtime_1.jsx)("p", { className: `mb-6 ${themeClasses.textMuted}`, children: "You need admin access for this area." }), (0, jsx_runtime_1.jsxs)(link_1.default, { href: "/", className: "inline-flex items-center gap-2 px-4 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700", children: [(0, jsx_runtime_1.jsx)(IconArrowLeft, {}), " Return to Home"] })] }) }));
|
|
168
|
+
}
|
|
169
|
+
if (status === 'unauthenticated')
|
|
170
|
+
return null;
|
|
171
|
+
const currentTab = tabs.find(t => t.id === activeTab);
|
|
172
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `min-h-screen ${themeClasses.bg}`, children: [(0, jsx_runtime_1.jsx)("header", { className: `sticky top-0 z-40 border-b ${themeClasses.headerBg}`, children: (0, jsx_runtime_1.jsx)("div", { className: "container mx-auto px-4 py-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: `p-2 rounded-lg ${isDark ? 'bg-amber-900/30' : 'bg-amber-100'}`, children: (0, jsx_runtime_1.jsx)(IconChart, { className: `w-5 h-5 ${isDark ? 'text-amber-400' : 'text-amber-600'}` }) }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { className: `text-xl font-bold ${themeClasses.text}`, children: title }), (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${themeClasses.textMuted}`, children: subtitle })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-4", children: [(0, jsx_runtime_1.jsxs)("button", { onClick: () => { fetchStats(); if (currentTab?.table)
|
|
173
|
+
fetchTableData(currentTab.table); }, disabled: loading || statsLoading, className: `flex items-center gap-2 px-3 py-2 rounded-lg transition-colors ${themeClasses.textMuted} ${isDark ? 'hover:bg-slate-800' : 'hover:bg-gray-100'} ${(loading || statsLoading) ? 'opacity-50' : ''}`, children: [(0, jsx_runtime_1.jsx)("span", { className: (loading || statsLoading) ? 'animate-spin' : '', children: (0, jsx_runtime_1.jsx)(IconRefresh, {}) }), "Refresh"] }), (0, jsx_runtime_1.jsxs)(link_1.default, { href: backUrl, className: `flex items-center gap-2 px-3 py-2 rounded-lg transition-colors ${themeClasses.textMuted} ${isDark ? 'hover:bg-slate-800' : 'hover:bg-gray-100'}`, children: [(0, jsx_runtime_1.jsx)(IconArrowLeft, {}), " ", backLabel] })] })] }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "container mx-auto px-4 py-6 space-y-6", children: [(0, jsx_runtime_1.jsx)("div", { className: `flex flex-wrap gap-1 p-1 rounded-xl ${themeClasses.tabBg}`, children: tabs.map((tab) => ((0, jsx_runtime_1.jsxs)("button", { onClick: () => handleTabChange(tab.id), className: `py-2 px-4 rounded-lg text-sm font-medium transition-all ${activeTab === tab.id ? themeClasses.tabActive : themeClasses.tabInactive}`, children: [(0, jsx_runtime_1.jsx)("span", { className: "mr-2", children: tab.icon }), tab.label] }, tab.id))) }), renderTabContent ? (renderTabContent(currentTab, tableData, loading)) : activeTab === 'overview' || !currentTab?.table ? (
|
|
174
|
+
// Default Overview
|
|
175
|
+
(0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { className: `text-lg font-semibold mb-4 ${themeClasses.text}`, children: "Data Overview" }), (0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4", children: stats.map((stat) => ((0, jsx_runtime_1.jsxs)(link_1.default, { href: `/admin/data?collection=${collectionName}&table=${stat.table}`, className: `p-4 rounded-xl border transition-all hover:scale-[1.02] ${themeClasses.cardBg}`, children: [(0, jsx_runtime_1.jsx)("p", { className: `text-xs font-medium ${themeClasses.textMuted}`, children: stat.label }), (0, jsx_runtime_1.jsx)("p", { className: `text-2xl font-bold ${themeClasses.text}`, children: statsLoading ? '...' : stat.count.toLocaleString() })] }, stat.table))) })] }), renderOverview && renderOverview(stats)] })) : (
|
|
176
|
+
// Default Table View
|
|
177
|
+
(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("h2", { className: `text-lg font-semibold mb-4 ${themeClasses.text}`, children: [currentTab.label, " (", tableData.length, ")"] }), loading ? ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center justify-center py-12", children: (0, jsx_runtime_1.jsx)("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-amber-500" }) })) : ((0, jsx_runtime_1.jsx)("div", { className: `rounded-lg border overflow-hidden ${themeClasses.cardBg}`, children: (0, jsx_runtime_1.jsx)("div", { className: "overflow-x-auto", children: (0, jsx_runtime_1.jsxs)("table", { className: "w-full text-sm", children: [(0, jsx_runtime_1.jsx)("thead", { className: isDark ? 'bg-slate-800' : 'bg-gray-50', children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { className: `px-4 py-3 text-left font-medium ${themeClasses.textMuted}`, children: "ID" }), (0, jsx_runtime_1.jsx)("th", { className: `px-4 py-3 text-left font-medium ${themeClasses.textMuted}`, children: "User" }), (0, jsx_runtime_1.jsx)("th", { className: `px-4 py-3 text-left font-medium ${themeClasses.textMuted}`, children: "Data" }), (0, jsx_runtime_1.jsx)("th", { className: `px-4 py-3 text-left font-medium ${themeClasses.textMuted}`, children: "Created" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { className: `divide-y ${isDark ? 'divide-slate-700' : 'divide-gray-200'}`, children: tableData.length === 0 ? ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsx)("td", { colSpan: 4, className: `px-4 py-8 text-center ${themeClasses.textMuted}`, children: "No data found" }) })) : tableData.map((row) => ((0, jsx_runtime_1.jsxs)("tr", { className: isDark ? 'hover:bg-slate-800/50' : 'hover:bg-gray-50', children: [(0, jsx_runtime_1.jsx)("td", { className: `px-4 py-3 ${themeClasses.text}`, children: row.document_id }), (0, jsx_runtime_1.jsx)("td", { className: `px-4 py-3 ${themeClasses.text}`, children: row.user_id }), (0, jsx_runtime_1.jsxs)("td", { className: `px-4 py-3 ${themeClasses.textMuted} max-w-xs truncate`, children: [JSON.stringify(row.data).substring(0, 50), "..."] }), (0, jsx_runtime_1.jsx)("td", { className: `px-4 py-3 ${themeClasses.textMuted}`, children: new Date(row.created_at).toLocaleDateString() })] }, row.document_id))) })] }) }) }))] }))] })] }));
|
|
178
|
+
}
|
|
179
|
+
exports.default = ClientSiteAdminPage;
|