@payez/next-mvp 4.1.7 → 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/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 +110 -110
- 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,97 +1,97 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState } from 'react';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
onSubmit: (password: string, confirmPassword: string) => void;
|
|
7
|
-
loading: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function SetPasswordStep({ onSubmit, loading }: Props) {
|
|
11
|
-
const [password, setPassword] = useState('');
|
|
12
|
-
const [confirmPassword, setConfirmPassword] = useState('');
|
|
13
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
14
|
-
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
15
|
-
|
|
16
|
-
const passwordsMatch = password === confirmPassword && password !== '';
|
|
17
|
-
|
|
18
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
19
|
-
e.preventDefault();
|
|
20
|
-
if (passwordsMatch) {
|
|
21
|
-
onSubmit(password, confirmPassword);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div className="bg-white border border-gray-300 rounded p-8">
|
|
27
|
-
<h2 className="text-xl font-semibold text-gray-900 mb-2">Set New Password</h2>
|
|
28
|
-
<p className="text-sm text-gray-600 mb-6">
|
|
29
|
-
Choose a strong password for your account.
|
|
30
|
-
</p>
|
|
31
|
-
|
|
32
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
33
|
-
<div>
|
|
34
|
-
<label htmlFor="password" className="block text-sm font-medium text-gray-900 mb-1">
|
|
35
|
-
New Password
|
|
36
|
-
</label>
|
|
37
|
-
<div className="relative">
|
|
38
|
-
<input
|
|
39
|
-
type={showPassword ? 'text' : 'password'}
|
|
40
|
-
id="password"
|
|
41
|
-
value={password}
|
|
42
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
43
|
-
required
|
|
44
|
-
disabled={loading}
|
|
45
|
-
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10"
|
|
46
|
-
/>
|
|
47
|
-
<button
|
|
48
|
-
type="button"
|
|
49
|
-
onClick={() => setShowPassword(!showPassword)}
|
|
50
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900"
|
|
51
|
-
>
|
|
52
|
-
{showPassword ? '👁️' : '👁️🗨️'}
|
|
53
|
-
</button>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<div>
|
|
58
|
-
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-900 mb-1">
|
|
59
|
-
Confirm New Password
|
|
60
|
-
</label>
|
|
61
|
-
<div className="relative">
|
|
62
|
-
<input
|
|
63
|
-
type={showConfirmPassword ? 'text' : 'password'}
|
|
64
|
-
id="confirmPassword"
|
|
65
|
-
value={confirmPassword}
|
|
66
|
-
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
67
|
-
required
|
|
68
|
-
disabled={loading}
|
|
69
|
-
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10"
|
|
70
|
-
/>
|
|
71
|
-
<button
|
|
72
|
-
type="button"
|
|
73
|
-
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
74
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900"
|
|
75
|
-
>
|
|
76
|
-
{showConfirmPassword ? '👁️' : '👁️🗨️'}
|
|
77
|
-
</button>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
{!passwordsMatch && confirmPassword && (
|
|
82
|
-
<div className="bg-red-50 border border-red-200 rounded p-3">
|
|
83
|
-
<p className="text-red-700 text-sm">Passwords do not match</p>
|
|
84
|
-
</div>
|
|
85
|
-
)}
|
|
86
|
-
|
|
87
|
-
<button
|
|
88
|
-
type="submit"
|
|
89
|
-
disabled={!passwordsMatch || loading}
|
|
90
|
-
className="w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
91
|
-
>
|
|
92
|
-
{loading ? 'Resetting Password...' : 'Reset Password'}
|
|
93
|
-
</button>
|
|
94
|
-
</form>
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
onSubmit: (password: string, confirmPassword: string) => void;
|
|
7
|
+
loading: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function SetPasswordStep({ onSubmit, loading }: Props) {
|
|
11
|
+
const [password, setPassword] = useState('');
|
|
12
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
13
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
14
|
+
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
15
|
+
|
|
16
|
+
const passwordsMatch = password === confirmPassword && password !== '';
|
|
17
|
+
|
|
18
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
if (passwordsMatch) {
|
|
21
|
+
onSubmit(password, confirmPassword);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="bg-white border border-gray-300 rounded p-8">
|
|
27
|
+
<h2 className="text-xl font-semibold text-gray-900 mb-2">Set New Password</h2>
|
|
28
|
+
<p className="text-sm text-gray-600 mb-6">
|
|
29
|
+
Choose a strong password for your account.
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
33
|
+
<div>
|
|
34
|
+
<label htmlFor="password" className="block text-sm font-medium text-gray-900 mb-1">
|
|
35
|
+
New Password
|
|
36
|
+
</label>
|
|
37
|
+
<div className="relative">
|
|
38
|
+
<input
|
|
39
|
+
type={showPassword ? 'text' : 'password'}
|
|
40
|
+
id="password"
|
|
41
|
+
value={password}
|
|
42
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
43
|
+
required
|
|
44
|
+
disabled={loading}
|
|
45
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10"
|
|
46
|
+
/>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
50
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900"
|
|
51
|
+
>
|
|
52
|
+
{showPassword ? '👁️' : '👁️🗨️'}
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div>
|
|
58
|
+
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-900 mb-1">
|
|
59
|
+
Confirm New Password
|
|
60
|
+
</label>
|
|
61
|
+
<div className="relative">
|
|
62
|
+
<input
|
|
63
|
+
type={showConfirmPassword ? 'text' : 'password'}
|
|
64
|
+
id="confirmPassword"
|
|
65
|
+
value={confirmPassword}
|
|
66
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
67
|
+
required
|
|
68
|
+
disabled={loading}
|
|
69
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10"
|
|
70
|
+
/>
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
74
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900"
|
|
75
|
+
>
|
|
76
|
+
{showConfirmPassword ? '👁️' : '👁️🗨️'}
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{!passwordsMatch && confirmPassword && (
|
|
82
|
+
<div className="bg-red-50 border border-red-200 rounded p-3">
|
|
83
|
+
<p className="text-red-700 text-sm">Passwords do not match</p>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
<button
|
|
88
|
+
type="submit"
|
|
89
|
+
disabled={!passwordsMatch || loading}
|
|
90
|
+
className="w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
91
|
+
>
|
|
92
|
+
{loading ? 'Resetting Password...' : 'Reset Password'}
|
|
93
|
+
</button>
|
|
94
|
+
</form>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useState, useEffect } from 'react';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
code: string;
|
|
7
|
-
setCode: (code: string) => void;
|
|
8
|
-
onSubmit: () => void;
|
|
9
|
-
onResend: () => void;
|
|
10
|
-
loading: boolean;
|
|
11
|
-
maskedDestination?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function VerifyCodeStep({
|
|
15
|
-
code,
|
|
16
|
-
setCode,
|
|
17
|
-
onSubmit,
|
|
18
|
-
onResend,
|
|
19
|
-
loading,
|
|
20
|
-
maskedDestination
|
|
21
|
-
}: Props) {
|
|
22
|
-
const [resendCooldown, setResendCooldown] = useState(0);
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (resendCooldown > 0) {
|
|
26
|
-
const timer = setTimeout(() => setResendCooldown(resendCooldown - 1), 1000);
|
|
27
|
-
return () => clearTimeout(timer);
|
|
28
|
-
}
|
|
29
|
-
}, [resendCooldown]);
|
|
30
|
-
|
|
31
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
32
|
-
e.preventDefault();
|
|
33
|
-
onSubmit();
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const handleResend = () => {
|
|
37
|
-
onResend();
|
|
38
|
-
setResendCooldown(30);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<div className="bg-white border border-gray-300 rounded p-8">
|
|
43
|
-
<h2 className="text-xl font-semibold text-gray-900 mb-2">Enter Verification Code</h2>
|
|
44
|
-
{maskedDestination && (
|
|
45
|
-
<p className="text-sm text-gray-600 mb-6">
|
|
46
|
-
Enter the 6-digit code sent to <span className="font-medium">{maskedDestination}</span>
|
|
47
|
-
</p>
|
|
48
|
-
)}
|
|
49
|
-
|
|
50
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
51
|
-
<div>
|
|
52
|
-
<label htmlFor="code" className="block text-sm font-medium text-gray-900 mb-1">
|
|
53
|
-
Verification Code
|
|
54
|
-
</label>
|
|
55
|
-
<input
|
|
56
|
-
type="text"
|
|
57
|
-
id="code"
|
|
58
|
-
value={code}
|
|
59
|
-
onChange={(e) => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
|
|
60
|
-
required
|
|
61
|
-
disabled={loading}
|
|
62
|
-
placeholder="000000"
|
|
63
|
-
maxLength={6}
|
|
64
|
-
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 text-center text-2xl tracking-widest font-mono"
|
|
65
|
-
/>
|
|
66
|
-
<p className="text-xs text-gray-600 mt-1">Code expires in 5 minutes</p>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<button
|
|
70
|
-
type="submit"
|
|
71
|
-
disabled={loading || code.length !== 6}
|
|
72
|
-
className="w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
73
|
-
>
|
|
74
|
-
{loading ? 'Verifying...' : 'Verify Code'}
|
|
75
|
-
</button>
|
|
76
|
-
|
|
77
|
-
<div className="text-center">
|
|
78
|
-
<button
|
|
79
|
-
type="button"
|
|
80
|
-
onClick={handleResend}
|
|
81
|
-
disabled={loading || resendCooldown > 0}
|
|
82
|
-
className="text-sm text-gray-900 hover:underline font-medium disabled:opacity-50 disabled:cursor-not-allowed disabled:no-underline"
|
|
83
|
-
>
|
|
84
|
-
{resendCooldown > 0 ? `Resend Code (${resendCooldown}s)` : 'Resend Code'}
|
|
85
|
-
</button>
|
|
86
|
-
</div>
|
|
87
|
-
</form>
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
code: string;
|
|
7
|
+
setCode: (code: string) => void;
|
|
8
|
+
onSubmit: () => void;
|
|
9
|
+
onResend: () => void;
|
|
10
|
+
loading: boolean;
|
|
11
|
+
maskedDestination?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function VerifyCodeStep({
|
|
15
|
+
code,
|
|
16
|
+
setCode,
|
|
17
|
+
onSubmit,
|
|
18
|
+
onResend,
|
|
19
|
+
loading,
|
|
20
|
+
maskedDestination
|
|
21
|
+
}: Props) {
|
|
22
|
+
const [resendCooldown, setResendCooldown] = useState(0);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (resendCooldown > 0) {
|
|
26
|
+
const timer = setTimeout(() => setResendCooldown(resendCooldown - 1), 1000);
|
|
27
|
+
return () => clearTimeout(timer);
|
|
28
|
+
}
|
|
29
|
+
}, [resendCooldown]);
|
|
30
|
+
|
|
31
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
onSubmit();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleResend = () => {
|
|
37
|
+
onResend();
|
|
38
|
+
setResendCooldown(30);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="bg-white border border-gray-300 rounded p-8">
|
|
43
|
+
<h2 className="text-xl font-semibold text-gray-900 mb-2">Enter Verification Code</h2>
|
|
44
|
+
{maskedDestination && (
|
|
45
|
+
<p className="text-sm text-gray-600 mb-6">
|
|
46
|
+
Enter the 6-digit code sent to <span className="font-medium">{maskedDestination}</span>
|
|
47
|
+
</p>
|
|
48
|
+
)}
|
|
49
|
+
|
|
50
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
51
|
+
<div>
|
|
52
|
+
<label htmlFor="code" className="block text-sm font-medium text-gray-900 mb-1">
|
|
53
|
+
Verification Code
|
|
54
|
+
</label>
|
|
55
|
+
<input
|
|
56
|
+
type="text"
|
|
57
|
+
id="code"
|
|
58
|
+
value={code}
|
|
59
|
+
onChange={(e) => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
|
|
60
|
+
required
|
|
61
|
+
disabled={loading}
|
|
62
|
+
placeholder="000000"
|
|
63
|
+
maxLength={6}
|
|
64
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 text-center text-2xl tracking-widest font-mono"
|
|
65
|
+
/>
|
|
66
|
+
<p className="text-xs text-gray-600 mt-1">Code expires in 5 minutes</p>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<button
|
|
70
|
+
type="submit"
|
|
71
|
+
disabled={loading || code.length !== 6}
|
|
72
|
+
className="w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
73
|
+
>
|
|
74
|
+
{loading ? 'Verifying...' : 'Verify Code'}
|
|
75
|
+
</button>
|
|
76
|
+
|
|
77
|
+
<div className="text-center">
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
onClick={handleResend}
|
|
81
|
+
disabled={loading || resendCooldown > 0}
|
|
82
|
+
className="text-sm text-gray-900 hover:underline font-medium disabled:opacity-50 disabled:cursor-not-allowed disabled:no-underline"
|
|
83
|
+
>
|
|
84
|
+
{resendCooldown > 0 ? `Resend Code (${resendCooldown}s)` : 'Resend Code'}
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
</form>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|