@payez/next-mvp 4.1.8 → 4.1.9
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 +792 -792
- package/dist/auth/better-auth.js +15 -3
- package/dist/components/account/MobileNavDrawer.js +18 -18
- package/dist/components/account/UserAvatarMenu.js +1 -1
- package/dist/components/admin/AlertSettingsTab.js +40 -40
- package/dist/components/admin/LoggingSettingsTab.js +28 -28
- package/dist/lib/idp-fetch.js +9 -25
- package/dist/pages/login/page.js +28 -28
- package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
- package/package.json +1 -1
- package/scripts/dev-broker.ps1 +35 -35
- package/scripts/dev-local.ps1 +45 -45
- package/src/api/auth-handler.ts +548 -548
- package/src/api-handlers/account/change-password.ts +142 -142
- package/src/api-handlers/account/update-phone.ts +79 -79
- package/src/api-handlers/account/verify-email.ts +125 -125
- package/src/api-handlers/account/verify-sms.ts +125 -125
- package/src/api-handlers/admin/analytics.ts +443 -443
- package/src/api-handlers/admin/audit.ts +223 -223
- package/src/api-handlers/admin/index.ts +63 -63
- package/src/api-handlers/admin/redis-sessions.ts +251 -251
- package/src/api-handlers/admin/sessions.ts +318 -318
- package/src/api-handlers/admin/site-logs.ts +365 -365
- package/src/api-handlers/admin/users.ts +242 -242
- package/src/api-handlers/admin/vibe-data.ts +324 -324
- package/src/api-handlers/auth/refresh.ts +682 -682
- package/src/api-handlers/auth/signout.ts +199 -199
- package/src/api-handlers/auth/status.ts +20 -20
- package/src/api-handlers/auth/update-session.ts +110 -110
- package/src/api-handlers/auth/verify-code.ts +112 -112
- package/src/api-handlers/session/refresh-viability.ts +36 -36
- package/src/api-handlers/test/force-expire.ts +60 -60
- package/src/auth/auth-decision.ts +229 -229
- package/src/auth/better-auth.ts +15 -3
- package/src/auth/route-config.ts +219 -219
- package/src/auth/types/auth-types.ts +555 -555
- package/src/auth/types/index.ts +7 -7
- package/src/auth/utils/idp-client.ts +445 -445
- package/src/auth/utils/index.ts +6 -6
- package/src/auth/utils/token-utils.ts +243 -243
- package/src/client/AuthContext.tsx +143 -143
- package/src/client/fetch-with-auth.ts +47 -47
- package/src/components/SessionSync.tsx +138 -138
- package/src/components/SignalRHealthCheck.tsx +131 -131
- package/src/components/account/MobileNavDrawer.tsx +305 -305
- package/src/components/account/UserAvatarMenu.tsx +266 -266
- package/src/components/account/index.ts +13 -13
- package/src/components/admin/AlertSettingsTab.tsx +728 -728
- package/src/components/admin/AnalyticsTab.tsx +703 -703
- package/src/components/admin/DataBrowserTab.tsx +505 -505
- package/src/components/admin/LoggingSettingsTab.tsx +665 -665
- package/src/components/admin/SessionsTab.tsx +414 -414
- package/src/components/admin/StatsTab.tsx +379 -379
- package/src/components/admin/VibeAdminLayout.tsx +187 -187
- package/src/components/admin/index.ts +59 -59
- package/src/components/auth/FederatedAuthSection.tsx +95 -95
- package/src/components/auth/ModeAwareLoginPage.tsx +135 -135
- package/src/components/auth/ModeAwareSignupPage.tsx +267 -267
- package/src/components/auth/TraditionalAuthSection.tsx +99 -99
- package/src/components/recovery/CompleteStep.tsx +36 -36
- package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
- package/src/components/recovery/SelectMethodStep.tsx +73 -73
- package/src/components/recovery/SetPasswordStep.tsx +97 -97
- package/src/components/recovery/VerifyCodeStep.tsx +90 -90
- package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
- package/src/components/reserved/ReservedStatusBox.tsx +118 -118
- package/src/components/ui/BetaBadge.tsx +58 -58
- package/src/components/ui/Footer.tsx +93 -93
- package/src/config/env.ts +57 -57
- package/src/config/logger.ts +62 -62
- package/src/config/vibe-log-transport.ts +264 -264
- package/src/hooks/useAuth.ts +117 -117
- package/src/hooks/useAuthSettings.ts +97 -97
- package/src/hooks/useAvailableProviders.ts +120 -120
- package/src/hooks/usePasswordValidation.ts +127 -127
- package/src/hooks/usePublicAuthSettings.ts +149 -149
- package/src/hooks/useSessionExpiration.ts +101 -101
- package/src/hooks/useViabilitySession.ts +336 -336
- package/src/index.ts +60 -60
- package/src/lib/api-handler.ts +643 -643
- package/src/lib/app-slug.ts +178 -178
- package/src/lib/geolocation.ts +265 -265
- package/src/lib/idp-fetch.ts +10 -26
- package/src/lib/jwt-decode.ts +82 -82
- package/src/lib/roles.ts +177 -177
- package/src/lib/site-logger.ts +245 -245
- package/src/lib/standardized-client-api.ts +901 -901
- package/src/lib/test-aware-get-token.ts +41 -41
- package/src/lib/user-agent-parser.ts +252 -252
- package/src/logging/api/admin-analytics.ts +51 -51
- package/src/logging/api/audit-log.ts +53 -53
- package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
- package/src/logging/components/AuditLogViewer.tsx +125 -125
- package/src/logging/components/ErrorMetricsCard.tsx +98 -98
- package/src/logging/components/HealthMetricsCard.tsx +70 -70
- package/src/logging/hooks/useAdminAnalytics.ts +22 -22
- package/src/logging/hooks/useAuditLog.ts +24 -24
- package/src/logging/hooks/useErrorMetrics.ts +40 -40
- package/src/logging/hooks/useHealthMetrics.ts +44 -44
- package/src/logging/index.ts +18 -18
- package/src/logging/types/analytics.ts +81 -81
- package/src/logging/types/audit.ts +31 -31
- package/src/logging/types/index.ts +3 -3
- package/src/middleware/create-middleware.ts +663 -663
- package/src/middleware/rbac-check.ts +279 -279
- package/src/middleware/twofa-presets.ts +224 -224
- package/src/pages/admin-login/page.tsx +221 -221
- package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
- package/src/pages/admin-page-permissions/index.ts +7 -7
- package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
- package/src/pages/admin-roles/modals.tsx +469 -469
- package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
- package/src/pages/client-admin/index.ts +33 -33
- package/src/pages/coming-soon/page.tsx +83 -83
- package/src/pages/login/page.tsx +456 -456
- package/src/pages/profile/EnhancedProfilePage.tsx +485 -485
- package/src/pages/profile/index.ts +9 -9
- package/src/pages/profile/page.tsx +166 -166
- package/src/pages/recovery/page.tsx +234 -234
- package/src/pages/roles/MyRolesPage.tsx +211 -211
- package/src/pages/roles/components.tsx +294 -294
- package/src/pages/roles/index.ts +17 -17
- package/src/pages/security/EnhancedSecurityPage.tsx +565 -565
- package/src/pages/security/index.ts +9 -9
- package/src/pages/security/page.tsx +507 -507
- package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
- package/src/pages/settings/index.ts +9 -9
- package/src/pages/settings/page.tsx +47 -47
- package/src/pages/showcase/ShowcasePage.tsx +532 -532
- package/src/pages/showcase/index.ts +13 -13
- package/src/pages/test-env/EmergencyLogoutPage.tsx +180 -180
- package/src/pages/test-env/JwtInspectPage.tsx +456 -456
- package/src/pages/test-env/RefreshTokenPage.tsx +157 -157
- package/src/pages/test-env/TestEnvPage.tsx +118 -118
- package/src/pages/test-env/index.ts +25 -25
- package/src/pages/verify-code/page.tsx +652 -652
- package/src/routes/account/update-phone.ts +13 -13
- package/src/routes/auth/logout.ts +108 -108
- package/src/routes/auth/nextauth.ts +45 -45
- package/src/routes/auth/settings.ts +75 -75
- package/src/routes/auth/viability.ts +224 -224
- package/src/server/auth-guard.ts +170 -170
- package/src/server/slim-middleware.ts +117 -117
- package/src/server/with-auth.ts +93 -93
- package/src/stores/authStore.ts +1899 -1899
- package/src/templates/instrumentation.ts +41 -41
- package/src/utils/logout.ts +30 -30
- package/src/vibe/client.ts +590 -590
- package/src/vibe/enterprise-auth.ts +208 -208
- package/src/vibe/errors.ts +185 -185
- package/src/vibe/generic.ts +431 -431
- package/src/vibe/hooks/index.ts +367 -367
- package/src/vibe/index.ts +132 -132
- package/src/vibe/sessions.ts +551 -551
- package/src/vibe/types.ts +577 -577
|
@@ -1,221 +1,221 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Admin Login Page for @payez/next-mvp
|
|
3
|
-
*
|
|
4
|
-
* A standalone username/password login page for admin access.
|
|
5
|
-
* NOT linked from any navigation - only accessible via direct URL.
|
|
6
|
-
*
|
|
7
|
-
* USAGE:
|
|
8
|
-
* 1. Create app/account-auth/admin-login/page.tsx in your Next.js app
|
|
9
|
-
* 2. Re-export this component:
|
|
10
|
-
* export { default } from '@payez/next-mvp/pages/admin-login';
|
|
11
|
-
*
|
|
12
|
-
* CUSTOMIZATION:
|
|
13
|
-
* - Override styles via CSS variables or wrap with your own component
|
|
14
|
-
* - Provide custom branding via ThemeProvider
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
'use client';
|
|
18
|
-
|
|
19
|
-
import React, { useState } from 'react';
|
|
20
|
-
import { authClient } from '../../client/better-auth-client';
|
|
21
|
-
import { useSearchParams } from 'next/navigation';
|
|
22
|
-
import { Suspense } from 'react';
|
|
23
|
-
import { useBranding, useColors } from '../../theme/useTheme';
|
|
24
|
-
|
|
25
|
-
interface AdminLoginFormProps {
|
|
26
|
-
/** Optional custom title (default: "Admin Login") */
|
|
27
|
-
title?: string;
|
|
28
|
-
/** Optional custom subtitle (default: "Authorized personnel only") */
|
|
29
|
-
subtitle?: string;
|
|
30
|
-
/** Optional callback URL override */
|
|
31
|
-
callbackUrl?: string;
|
|
32
|
-
/** Optional logo component to render */
|
|
33
|
-
logo?: React.ReactNode;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function AdminLoginForm({
|
|
37
|
-
title = 'Admin Login',
|
|
38
|
-
subtitle = 'Authorized personnel only',
|
|
39
|
-
callbackUrl: propCallbackUrl,
|
|
40
|
-
logo,
|
|
41
|
-
}: AdminLoginFormProps) {
|
|
42
|
-
const searchParams = useSearchParams();
|
|
43
|
-
const callbackUrl = propCallbackUrl || searchParams?.get('callbackUrl') || '/dashboard';
|
|
44
|
-
|
|
45
|
-
const branding = useBranding();
|
|
46
|
-
const colors = useColors();
|
|
47
|
-
|
|
48
|
-
const [email, setEmail] = useState('');
|
|
49
|
-
const [password, setPassword] = useState('');
|
|
50
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
51
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
52
|
-
const [error, setError] = useState<string | null>(null);
|
|
53
|
-
|
|
54
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
55
|
-
e.preventDefault();
|
|
56
|
-
setIsLoading(true);
|
|
57
|
-
setError(null);
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const result = await authClient.signIn.email({
|
|
61
|
-
email,
|
|
62
|
-
password,
|
|
63
|
-
callbackURL: callbackUrl,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
if ((result as any)?.error) {
|
|
67
|
-
const errorMsg = typeof (result as any).error === 'object'
|
|
68
|
-
? ((result as any).error as any).message || 'Invalid credentials'
|
|
69
|
-
: String((result as any).error);
|
|
70
|
-
setError(errorMsg);
|
|
71
|
-
} else if (result?.data) {
|
|
72
|
-
// Redirect to verify-code for 2FA or directly to callback
|
|
73
|
-
window.location.href = `/account-auth/verify-code?callbackUrl=${encodeURIComponent(callbackUrl)}`;
|
|
74
|
-
}
|
|
75
|
-
} catch (err) {
|
|
76
|
-
setError('An unexpected error occurred');
|
|
77
|
-
} finally {
|
|
78
|
-
setIsLoading(false);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<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">
|
|
84
|
-
<div className="bg-white rounded-2xl shadow-2xl p-8 max-w-md w-full">
|
|
85
|
-
{/* Logo */}
|
|
86
|
-
{logo && (
|
|
87
|
-
<div className="flex justify-center mb-6">
|
|
88
|
-
{logo}
|
|
89
|
-
</div>
|
|
90
|
-
)}
|
|
91
|
-
|
|
92
|
-
{/* Title */}
|
|
93
|
-
<h1 className="text-2xl font-bold text-center mb-2 text-slate-900">
|
|
94
|
-
{title}
|
|
95
|
-
</h1>
|
|
96
|
-
<p className="text-center mb-8 text-slate-600">
|
|
97
|
-
{subtitle}
|
|
98
|
-
</p>
|
|
99
|
-
|
|
100
|
-
{/* Error */}
|
|
101
|
-
{error && (
|
|
102
|
-
<div className="mb-6 px-4 py-3 rounded-lg bg-red-500 text-white text-center text-sm">
|
|
103
|
-
{error}
|
|
104
|
-
</div>
|
|
105
|
-
)}
|
|
106
|
-
|
|
107
|
-
{/* Login Form */}
|
|
108
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
109
|
-
<div>
|
|
110
|
-
<label
|
|
111
|
-
htmlFor="admin-email"
|
|
112
|
-
className="block text-sm font-medium mb-2 text-slate-700"
|
|
113
|
-
>
|
|
114
|
-
Email
|
|
115
|
-
</label>
|
|
116
|
-
<input
|
|
117
|
-
id="admin-email"
|
|
118
|
-
type="email"
|
|
119
|
-
value={email}
|
|
120
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
121
|
-
required
|
|
122
|
-
disabled={isLoading}
|
|
123
|
-
autoComplete="email"
|
|
124
|
-
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"
|
|
125
|
-
placeholder="admin@example.com"
|
|
126
|
-
/>
|
|
127
|
-
</div>
|
|
128
|
-
|
|
129
|
-
<div>
|
|
130
|
-
<label
|
|
131
|
-
htmlFor="admin-password"
|
|
132
|
-
className="block text-sm font-medium mb-2 text-slate-700"
|
|
133
|
-
>
|
|
134
|
-
Password
|
|
135
|
-
</label>
|
|
136
|
-
<div className="relative">
|
|
137
|
-
<input
|
|
138
|
-
id="admin-password"
|
|
139
|
-
type={showPassword ? 'text' : 'password'}
|
|
140
|
-
value={password}
|
|
141
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
142
|
-
required
|
|
143
|
-
disabled={isLoading}
|
|
144
|
-
autoComplete="current-password"
|
|
145
|
-
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"
|
|
146
|
-
placeholder="Enter your password"
|
|
147
|
-
/>
|
|
148
|
-
<button
|
|
149
|
-
type="button"
|
|
150
|
-
onClick={() => setShowPassword(!showPassword)}
|
|
151
|
-
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-slate-500 hover:text-slate-700"
|
|
152
|
-
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
|
153
|
-
>
|
|
154
|
-
{showPassword ? (
|
|
155
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" className="w-5 h-5">
|
|
156
|
-
<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" />
|
|
157
|
-
</svg>
|
|
158
|
-
) : (
|
|
159
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" className="w-5 h-5">
|
|
160
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
161
|
-
<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" />
|
|
162
|
-
</svg>
|
|
163
|
-
)}
|
|
164
|
-
</button>
|
|
165
|
-
</div>
|
|
166
|
-
</div>
|
|
167
|
-
|
|
168
|
-
<button
|
|
169
|
-
type="submit"
|
|
170
|
-
disabled={isLoading}
|
|
171
|
-
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"
|
|
172
|
-
>
|
|
173
|
-
{isLoading ? (
|
|
174
|
-
<span className="flex items-center justify-center">
|
|
175
|
-
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" fill="none" viewBox="0 0 24 24">
|
|
176
|
-
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
177
|
-
<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" />
|
|
178
|
-
</svg>
|
|
179
|
-
Signing in...
|
|
180
|
-
</span>
|
|
181
|
-
) : (
|
|
182
|
-
'Sign In'
|
|
183
|
-
)}
|
|
184
|
-
</button>
|
|
185
|
-
</form>
|
|
186
|
-
|
|
187
|
-
{/* Security notice */}
|
|
188
|
-
<p className="mt-6 text-center text-xs text-slate-500">
|
|
189
|
-
This login is for authorized administrators only.
|
|
190
|
-
<br />
|
|
191
|
-
All access attempts are logged.
|
|
192
|
-
</p>
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function AdminLoginFallback() {
|
|
199
|
-
return (
|
|
200
|
-
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-700 via-slate-800 to-slate-900">
|
|
201
|
-
<div className="text-center">
|
|
202
|
-
<svg className="animate-spin h-10 w-10 mx-auto text-white" fill="none" viewBox="0 0 24 24">
|
|
203
|
-
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
204
|
-
<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" />
|
|
205
|
-
</svg>
|
|
206
|
-
<p className="mt-4 text-slate-400">Loading...</p>
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export default function AdminLoginPage(props: AdminLoginFormProps) {
|
|
213
|
-
return (
|
|
214
|
-
<Suspense fallback={<AdminLoginFallback />}>
|
|
215
|
-
<AdminLoginForm {...props} />
|
|
216
|
-
</Suspense>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export { AdminLoginForm, AdminLoginFallback };
|
|
221
|
-
export type { AdminLoginFormProps };
|
|
1
|
+
/**
|
|
2
|
+
* Admin Login Page for @payez/next-mvp
|
|
3
|
+
*
|
|
4
|
+
* A standalone username/password login page for admin access.
|
|
5
|
+
* NOT linked from any navigation - only accessible via direct URL.
|
|
6
|
+
*
|
|
7
|
+
* USAGE:
|
|
8
|
+
* 1. Create app/account-auth/admin-login/page.tsx in your Next.js app
|
|
9
|
+
* 2. Re-export this component:
|
|
10
|
+
* export { default } from '@payez/next-mvp/pages/admin-login';
|
|
11
|
+
*
|
|
12
|
+
* CUSTOMIZATION:
|
|
13
|
+
* - Override styles via CSS variables or wrap with your own component
|
|
14
|
+
* - Provide custom branding via ThemeProvider
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use client';
|
|
18
|
+
|
|
19
|
+
import React, { useState } from 'react';
|
|
20
|
+
import { authClient } from '../../client/better-auth-client';
|
|
21
|
+
import { useSearchParams } from 'next/navigation';
|
|
22
|
+
import { Suspense } from 'react';
|
|
23
|
+
import { useBranding, useColors } from '../../theme/useTheme';
|
|
24
|
+
|
|
25
|
+
interface AdminLoginFormProps {
|
|
26
|
+
/** Optional custom title (default: "Admin Login") */
|
|
27
|
+
title?: string;
|
|
28
|
+
/** Optional custom subtitle (default: "Authorized personnel only") */
|
|
29
|
+
subtitle?: string;
|
|
30
|
+
/** Optional callback URL override */
|
|
31
|
+
callbackUrl?: string;
|
|
32
|
+
/** Optional logo component to render */
|
|
33
|
+
logo?: React.ReactNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function AdminLoginForm({
|
|
37
|
+
title = 'Admin Login',
|
|
38
|
+
subtitle = 'Authorized personnel only',
|
|
39
|
+
callbackUrl: propCallbackUrl,
|
|
40
|
+
logo,
|
|
41
|
+
}: AdminLoginFormProps) {
|
|
42
|
+
const searchParams = useSearchParams();
|
|
43
|
+
const callbackUrl = propCallbackUrl || searchParams?.get('callbackUrl') || '/dashboard';
|
|
44
|
+
|
|
45
|
+
const branding = useBranding();
|
|
46
|
+
const colors = useColors();
|
|
47
|
+
|
|
48
|
+
const [email, setEmail] = useState('');
|
|
49
|
+
const [password, setPassword] = useState('');
|
|
50
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
51
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
52
|
+
const [error, setError] = useState<string | null>(null);
|
|
53
|
+
|
|
54
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
setIsLoading(true);
|
|
57
|
+
setError(null);
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const result = await authClient.signIn.email({
|
|
61
|
+
email,
|
|
62
|
+
password,
|
|
63
|
+
callbackURL: callbackUrl,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if ((result as any)?.error) {
|
|
67
|
+
const errorMsg = typeof (result as any).error === 'object'
|
|
68
|
+
? ((result as any).error as any).message || 'Invalid credentials'
|
|
69
|
+
: String((result as any).error);
|
|
70
|
+
setError(errorMsg);
|
|
71
|
+
} else if (result?.data) {
|
|
72
|
+
// Redirect to verify-code for 2FA or directly to callback
|
|
73
|
+
window.location.href = `/account-auth/verify-code?callbackUrl=${encodeURIComponent(callbackUrl)}`;
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
setError('An unexpected error occurred');
|
|
77
|
+
} finally {
|
|
78
|
+
setIsLoading(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<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">
|
|
84
|
+
<div className="bg-white rounded-2xl shadow-2xl p-8 max-w-md w-full">
|
|
85
|
+
{/* Logo */}
|
|
86
|
+
{logo && (
|
|
87
|
+
<div className="flex justify-center mb-6">
|
|
88
|
+
{logo}
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{/* Title */}
|
|
93
|
+
<h1 className="text-2xl font-bold text-center mb-2 text-slate-900">
|
|
94
|
+
{title}
|
|
95
|
+
</h1>
|
|
96
|
+
<p className="text-center mb-8 text-slate-600">
|
|
97
|
+
{subtitle}
|
|
98
|
+
</p>
|
|
99
|
+
|
|
100
|
+
{/* Error */}
|
|
101
|
+
{error && (
|
|
102
|
+
<div className="mb-6 px-4 py-3 rounded-lg bg-red-500 text-white text-center text-sm">
|
|
103
|
+
{error}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{/* Login Form */}
|
|
108
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
109
|
+
<div>
|
|
110
|
+
<label
|
|
111
|
+
htmlFor="admin-email"
|
|
112
|
+
className="block text-sm font-medium mb-2 text-slate-700"
|
|
113
|
+
>
|
|
114
|
+
Email
|
|
115
|
+
</label>
|
|
116
|
+
<input
|
|
117
|
+
id="admin-email"
|
|
118
|
+
type="email"
|
|
119
|
+
value={email}
|
|
120
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
121
|
+
required
|
|
122
|
+
disabled={isLoading}
|
|
123
|
+
autoComplete="email"
|
|
124
|
+
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"
|
|
125
|
+
placeholder="admin@example.com"
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div>
|
|
130
|
+
<label
|
|
131
|
+
htmlFor="admin-password"
|
|
132
|
+
className="block text-sm font-medium mb-2 text-slate-700"
|
|
133
|
+
>
|
|
134
|
+
Password
|
|
135
|
+
</label>
|
|
136
|
+
<div className="relative">
|
|
137
|
+
<input
|
|
138
|
+
id="admin-password"
|
|
139
|
+
type={showPassword ? 'text' : 'password'}
|
|
140
|
+
value={password}
|
|
141
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
142
|
+
required
|
|
143
|
+
disabled={isLoading}
|
|
144
|
+
autoComplete="current-password"
|
|
145
|
+
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"
|
|
146
|
+
placeholder="Enter your password"
|
|
147
|
+
/>
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
151
|
+
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-slate-500 hover:text-slate-700"
|
|
152
|
+
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
|
153
|
+
>
|
|
154
|
+
{showPassword ? (
|
|
155
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" className="w-5 h-5">
|
|
156
|
+
<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" />
|
|
157
|
+
</svg>
|
|
158
|
+
) : (
|
|
159
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" className="w-5 h-5">
|
|
160
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
161
|
+
<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" />
|
|
162
|
+
</svg>
|
|
163
|
+
)}
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<button
|
|
169
|
+
type="submit"
|
|
170
|
+
disabled={isLoading}
|
|
171
|
+
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"
|
|
172
|
+
>
|
|
173
|
+
{isLoading ? (
|
|
174
|
+
<span className="flex items-center justify-center">
|
|
175
|
+
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" fill="none" viewBox="0 0 24 24">
|
|
176
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
177
|
+
<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" />
|
|
178
|
+
</svg>
|
|
179
|
+
Signing in...
|
|
180
|
+
</span>
|
|
181
|
+
) : (
|
|
182
|
+
'Sign In'
|
|
183
|
+
)}
|
|
184
|
+
</button>
|
|
185
|
+
</form>
|
|
186
|
+
|
|
187
|
+
{/* Security notice */}
|
|
188
|
+
<p className="mt-6 text-center text-xs text-slate-500">
|
|
189
|
+
This login is for authorized administrators only.
|
|
190
|
+
<br />
|
|
191
|
+
All access attempts are logged.
|
|
192
|
+
</p>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function AdminLoginFallback() {
|
|
199
|
+
return (
|
|
200
|
+
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-700 via-slate-800 to-slate-900">
|
|
201
|
+
<div className="text-center">
|
|
202
|
+
<svg className="animate-spin h-10 w-10 mx-auto text-white" fill="none" viewBox="0 0 24 24">
|
|
203
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
204
|
+
<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" />
|
|
205
|
+
</svg>
|
|
206
|
+
<p className="mt-4 text-slate-400">Loading...</p>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export default function AdminLoginPage(props: AdminLoginFormProps) {
|
|
213
|
+
return (
|
|
214
|
+
<Suspense fallback={<AdminLoginFallback />}>
|
|
215
|
+
<AdminLoginForm {...props} />
|
|
216
|
+
</Suspense>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export { AdminLoginForm, AdminLoginFallback };
|
|
221
|
+
export type { AdminLoginFormProps };
|