@onexapis/cli 1.1.34 → 1.1.37
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/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +5 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/preview/preview-app.tsx +13 -5
- package/package.json +1 -1
- package/templates/default/AUTH_AND_PROFILE.md +167 -0
- package/templates/default/CLAUDE.md +78 -24
- package/templates/default/LAYOUT.md +195 -0
- package/templates/default/bundle-entry.ts +5 -0
- package/templates/default/hooks/index.ts +26 -0
- package/templates/default/hooks/use-forgot-password-form.ts +90 -0
- package/templates/default/hooks/use-login-form.ts +102 -0
- package/templates/default/hooks/use-profile-form.ts +255 -0
- package/templates/default/hooks/use-register-form.ts +154 -0
- package/templates/default/hooks/use-verify-code-form.ts +224 -0
- package/templates/default/index.ts +21 -1
- package/templates/default/pages/forgot-password.ts +41 -0
- package/templates/default/pages/login.ts +41 -0
- package/templates/default/pages/profile.ts +39 -0
- package/templates/default/pages/register.ts +41 -0
- package/templates/default/pages/verify-code.ts +41 -0
- package/templates/default/sections/auth-forgot-password/auth-forgot-password-default.tsx +192 -0
- package/templates/default/sections/auth-forgot-password/auth-forgot-password.schema.ts +150 -0
- package/templates/default/sections/auth-forgot-password/index.ts +14 -0
- package/templates/default/sections/auth-login/auth-login-default.tsx +238 -0
- package/templates/default/sections/auth-login/auth-login.schema.ts +171 -0
- package/templates/default/sections/auth-login/index.ts +14 -0
- package/templates/default/sections/auth-register/auth-register-default.tsx +327 -0
- package/templates/default/sections/auth-register/auth-register.schema.ts +188 -0
- package/templates/default/sections/auth-register/index.ts +14 -0
- package/templates/default/sections/auth-verify-code/auth-verify-code-default.tsx +209 -0
- package/templates/default/sections/auth-verify-code/auth-verify-code.schema.ts +150 -0
- package/templates/default/sections/auth-verify-code/index.ts +14 -0
- package/templates/default/sections/footer/footer-default.tsx +214 -0
- package/templates/default/sections/footer/footer.schema.ts +170 -0
- package/templates/default/sections/footer/index.ts +14 -0
- package/templates/default/sections/header/header-default.tsx +322 -0
- package/templates/default/sections/header/header.schema.ts +168 -0
- package/templates/default/sections/header/index.ts +14 -0
- package/templates/default/sections/profile/index.ts +14 -0
- package/templates/default/sections/profile/profile-default.tsx +522 -0
- package/templates/default/sections/profile/profile.schema.ts +228 -0
- package/templates/default/sections-registry.ts +28 -0
- package/templates/default/theme.layout.ts +53 -2
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Code Form Hook
|
|
3
|
+
* Manages OTP input state, countdown timer, and submission via useAuth
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
9
|
+
import { useAuth } from "@onexapis/core/hooks";
|
|
10
|
+
|
|
11
|
+
export interface UseVerifyCodeFormReturn {
|
|
12
|
+
code: string[];
|
|
13
|
+
error: string | null;
|
|
14
|
+
countdown: number;
|
|
15
|
+
email: string;
|
|
16
|
+
username: string;
|
|
17
|
+
isVerifying: boolean;
|
|
18
|
+
isResending: boolean;
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
inputRefs: React.MutableRefObject<(HTMLInputElement | null)[]>;
|
|
21
|
+
handleCodeChange: (index: number, value: string) => void;
|
|
22
|
+
handleKeyDown: (
|
|
23
|
+
index: number,
|
|
24
|
+
e: React.KeyboardEvent<HTMLInputElement>
|
|
25
|
+
) => void;
|
|
26
|
+
handlePaste: (e: React.ClipboardEvent) => void;
|
|
27
|
+
handleSubmit: (e: React.FormEvent) => void;
|
|
28
|
+
handleResend: () => void;
|
|
29
|
+
setError: (error: string | null) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useVerifyCodeForm(): UseVerifyCodeFormReturn {
|
|
33
|
+
const [code, setCode] = useState<string[]>(["", "", "", "", "", ""]);
|
|
34
|
+
const [error, setError] = useState<string | null>(null);
|
|
35
|
+
const [countdown, setCountdown] = useState(0);
|
|
36
|
+
const [email, setEmail] = useState("");
|
|
37
|
+
const [username, setUsername] = useState("");
|
|
38
|
+
const [isVerifying, setIsVerifying] = useState(false);
|
|
39
|
+
const [isResending, setIsResending] = useState(false);
|
|
40
|
+
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
|
|
41
|
+
|
|
42
|
+
const { verifyCode, verifyCodeReset, resendCode } = useAuth();
|
|
43
|
+
|
|
44
|
+
// Extract username from URL search params
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (typeof window !== "undefined") {
|
|
47
|
+
const params = new URLSearchParams(window.location.search);
|
|
48
|
+
const u = params.get("username") || "";
|
|
49
|
+
setUsername(u);
|
|
50
|
+
|
|
51
|
+
if (u) {
|
|
52
|
+
const storedEmail = localStorage.getItem(`register_email_${u}`);
|
|
53
|
+
if (storedEmail) setEmail(storedEmail);
|
|
54
|
+
|
|
55
|
+
const storedValue = localStorage.getItem(`resend_countdown_${u}`);
|
|
56
|
+
if (storedValue) {
|
|
57
|
+
const expiryTimestamp = parseInt(storedValue, 10);
|
|
58
|
+
const remaining = Math.max(
|
|
59
|
+
0,
|
|
60
|
+
Math.floor((expiryTimestamp - Date.now()) / 1000)
|
|
61
|
+
);
|
|
62
|
+
if (remaining > 0) setCountdown(remaining);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
// Countdown timer
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (countdown > 0) {
|
|
71
|
+
const timer = setInterval(() => {
|
|
72
|
+
setCountdown((prev) => prev - 1);
|
|
73
|
+
}, 1000);
|
|
74
|
+
return () => clearInterval(timer);
|
|
75
|
+
}
|
|
76
|
+
}, [countdown]);
|
|
77
|
+
|
|
78
|
+
const handleSubmitCode = useCallback(
|
|
79
|
+
async (fullCode: string) => {
|
|
80
|
+
if (!username) {
|
|
81
|
+
setError("Account information not found");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setIsVerifying(true);
|
|
86
|
+
setError(null);
|
|
87
|
+
try {
|
|
88
|
+
const params = new URLSearchParams(window.location.search);
|
|
89
|
+
const mode = params.get("mode");
|
|
90
|
+
|
|
91
|
+
if (mode === "reset") {
|
|
92
|
+
const result = await verifyCodeReset({ username, code: fullCode });
|
|
93
|
+
// Clean up and redirect to reset-password
|
|
94
|
+
localStorage.removeItem(`register_email_${username}`);
|
|
95
|
+
localStorage.removeItem(`resend_countdown_${username}`);
|
|
96
|
+
const session = result?.session;
|
|
97
|
+
window.location.href = session
|
|
98
|
+
? `/reset-password?session=${encodeURIComponent(session)}`
|
|
99
|
+
: "/reset-password";
|
|
100
|
+
} else {
|
|
101
|
+
await verifyCode({ username, code: fullCode });
|
|
102
|
+
// Clean up and redirect to login
|
|
103
|
+
localStorage.removeItem(`register_email_${username}`);
|
|
104
|
+
localStorage.removeItem(`resend_countdown_${username}`);
|
|
105
|
+
window.location.href = "/login";
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const message =
|
|
109
|
+
err instanceof Error ? err.message : "Verification failed";
|
|
110
|
+
setError(message);
|
|
111
|
+
} finally {
|
|
112
|
+
setIsVerifying(false);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
[username, verifyCode, verifyCodeReset]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const handleCodeChange = useCallback(
|
|
119
|
+
(index: number, value: string) => {
|
|
120
|
+
const digit = value.replace(/\D/g, "").slice(-1);
|
|
121
|
+
const newCode = [...code];
|
|
122
|
+
newCode[index] = digit;
|
|
123
|
+
setCode(newCode);
|
|
124
|
+
setError(null);
|
|
125
|
+
|
|
126
|
+
if (digit && index < 5) {
|
|
127
|
+
inputRefs.current[index + 1]?.focus();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (digit && index === 5) {
|
|
131
|
+
const fullCode = newCode.join("");
|
|
132
|
+
if (fullCode.length === 6) {
|
|
133
|
+
handleSubmitCode(fullCode);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
[code, handleSubmitCode]
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const handleKeyDown = useCallback(
|
|
141
|
+
(index: number, e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
142
|
+
if (e.key === "Backspace" && !code[index] && index > 0) {
|
|
143
|
+
inputRefs.current[index - 1]?.focus();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
[code]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const handlePaste = useCallback(
|
|
150
|
+
(e: React.ClipboardEvent) => {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
const pastedData = e.clipboardData.getData("text");
|
|
153
|
+
const digits = pastedData.replace(/\D/g, "").slice(0, 6);
|
|
154
|
+
|
|
155
|
+
if (digits.length > 0) {
|
|
156
|
+
const newCode = [...code];
|
|
157
|
+
for (let i = 0; i < 6; i++) {
|
|
158
|
+
newCode[i] = digits[i] || "";
|
|
159
|
+
}
|
|
160
|
+
setCode(newCode);
|
|
161
|
+
|
|
162
|
+
const lastIndex = Math.min(digits.length - 1, 5);
|
|
163
|
+
inputRefs.current[lastIndex]?.focus();
|
|
164
|
+
|
|
165
|
+
if (digits.length === 6) {
|
|
166
|
+
handleSubmitCode(digits);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
[code, handleSubmitCode]
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const handleSubmit = useCallback(
|
|
174
|
+
(e: React.FormEvent) => {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
const fullCode = code.join("");
|
|
177
|
+
if (fullCode.length !== 6) {
|
|
178
|
+
setError("Please enter the full 6-digit code");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
handleSubmitCode(fullCode);
|
|
182
|
+
},
|
|
183
|
+
[code, handleSubmitCode]
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const handleResend = useCallback(async () => {
|
|
187
|
+
if (countdown > 0 || !username) return;
|
|
188
|
+
|
|
189
|
+
setIsResending(true);
|
|
190
|
+
try {
|
|
191
|
+
await resendCode({ username });
|
|
192
|
+
const expiryTimestamp = Date.now() + 60 * 1000;
|
|
193
|
+
localStorage.setItem(
|
|
194
|
+
`resend_countdown_${username}`,
|
|
195
|
+
expiryTimestamp.toString()
|
|
196
|
+
);
|
|
197
|
+
setCountdown(60);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
const message =
|
|
200
|
+
err instanceof Error ? err.message : "Failed to resend code";
|
|
201
|
+
setError(message);
|
|
202
|
+
} finally {
|
|
203
|
+
setIsResending(false);
|
|
204
|
+
}
|
|
205
|
+
}, [countdown, username, resendCode]);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
code,
|
|
209
|
+
error,
|
|
210
|
+
countdown,
|
|
211
|
+
email,
|
|
212
|
+
username,
|
|
213
|
+
isVerifying,
|
|
214
|
+
isResending,
|
|
215
|
+
isLoading: isVerifying || isResending,
|
|
216
|
+
inputRefs,
|
|
217
|
+
handleCodeChange,
|
|
218
|
+
handleKeyDown,
|
|
219
|
+
handlePaste,
|
|
220
|
+
handleSubmit,
|
|
221
|
+
handleResend,
|
|
222
|
+
setError,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -14,13 +14,33 @@ export { default as layoutConfig, simpleLayoutConfig } from "./theme.layout";
|
|
|
14
14
|
export { default as homePageConfig } from "./pages/home";
|
|
15
15
|
export { default as aboutPageConfig } from "./pages/about";
|
|
16
16
|
export { default as showcasePageConfig } from "./pages/showcase";
|
|
17
|
+
export { default as loginPageConfig } from "./pages/login";
|
|
18
|
+
export { default as registerPageConfig } from "./pages/register";
|
|
19
|
+
export { default as forgotPasswordPageConfig } from "./pages/forgot-password";
|
|
20
|
+
export { default as verifyCodePageConfig } from "./pages/verify-code";
|
|
21
|
+
export { default as profilePageConfig } from "./pages/profile";
|
|
17
22
|
|
|
18
23
|
// Page map for dynamic access
|
|
19
24
|
export const pages = {
|
|
20
25
|
home: () => import("./pages/home").then((m) => m.default),
|
|
21
26
|
about: () => import("./pages/about").then((m) => m.default),
|
|
22
27
|
showcase: () => import("./pages/showcase").then((m) => m.default),
|
|
28
|
+
login: () => import("./pages/login").then((m) => m.default),
|
|
29
|
+
register: () => import("./pages/register").then((m) => m.default),
|
|
30
|
+
"forgot-password": () =>
|
|
31
|
+
import("./pages/forgot-password").then((m) => m.default),
|
|
32
|
+
"verify-code": () => import("./pages/verify-code").then((m) => m.default),
|
|
33
|
+
profile: () => import("./pages/profile").then((m) => m.default),
|
|
23
34
|
};
|
|
24
35
|
|
|
25
36
|
// Page slugs (for discovery)
|
|
26
|
-
export const pageList = [
|
|
37
|
+
export const pageList = [
|
|
38
|
+
"home",
|
|
39
|
+
"about",
|
|
40
|
+
"showcase",
|
|
41
|
+
"login",
|
|
42
|
+
"register",
|
|
43
|
+
"forgot-password",
|
|
44
|
+
"verify-code",
|
|
45
|
+
"profile",
|
|
46
|
+
] as const;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* My Simple Theme - Forgot Password Page Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PageConfig } from "@onexapis/core/types";
|
|
6
|
+
|
|
7
|
+
export const forgotPasswordPageConfig: Omit<
|
|
8
|
+
PageConfig,
|
|
9
|
+
"id" | "createdAt" | "updatedAt"
|
|
10
|
+
> = {
|
|
11
|
+
title: "Forgot Password",
|
|
12
|
+
handle: "forgot-password",
|
|
13
|
+
path: "/forgot-password",
|
|
14
|
+
type: "auth",
|
|
15
|
+
renderMode: "sections",
|
|
16
|
+
themeId: "my-simple",
|
|
17
|
+
editable: true,
|
|
18
|
+
published: true,
|
|
19
|
+
hideHeader: true,
|
|
20
|
+
hideFooter: true,
|
|
21
|
+
|
|
22
|
+
seo: {
|
|
23
|
+
title: "Forgot Password",
|
|
24
|
+
description: "Reset your password",
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
sections: [
|
|
28
|
+
{
|
|
29
|
+
id: "auth-forgot-password-1",
|
|
30
|
+
type: "my-simple-auth-forgot-password",
|
|
31
|
+
template: "default",
|
|
32
|
+
order: 0,
|
|
33
|
+
enabled: true,
|
|
34
|
+
settings: {},
|
|
35
|
+
components: [],
|
|
36
|
+
blocks: [],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default forgotPasswordPageConfig;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* My Simple Theme - Login Page Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PageConfig } from "@onexapis/core/types";
|
|
6
|
+
|
|
7
|
+
export const loginPageConfig: Omit<
|
|
8
|
+
PageConfig,
|
|
9
|
+
"id" | "createdAt" | "updatedAt"
|
|
10
|
+
> = {
|
|
11
|
+
title: "Login",
|
|
12
|
+
handle: "login",
|
|
13
|
+
path: "/login",
|
|
14
|
+
type: "auth",
|
|
15
|
+
renderMode: "sections",
|
|
16
|
+
themeId: "my-simple",
|
|
17
|
+
editable: true,
|
|
18
|
+
published: true,
|
|
19
|
+
hideHeader: true,
|
|
20
|
+
hideFooter: true,
|
|
21
|
+
|
|
22
|
+
seo: {
|
|
23
|
+
title: "Sign In",
|
|
24
|
+
description: "Sign in to your account",
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
sections: [
|
|
28
|
+
{
|
|
29
|
+
id: "auth-login-1",
|
|
30
|
+
type: "my-simple-auth-login",
|
|
31
|
+
template: "default",
|
|
32
|
+
order: 0,
|
|
33
|
+
enabled: true,
|
|
34
|
+
settings: {},
|
|
35
|
+
components: [],
|
|
36
|
+
blocks: [],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default loginPageConfig;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* My Simple Theme - Profile Page Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PageConfig } from "@onexapis/core/types";
|
|
6
|
+
|
|
7
|
+
export const profilePageConfig: Omit<
|
|
8
|
+
PageConfig,
|
|
9
|
+
"id" | "createdAt" | "updatedAt"
|
|
10
|
+
> = {
|
|
11
|
+
title: "Profile",
|
|
12
|
+
handle: "profile",
|
|
13
|
+
path: "/profile",
|
|
14
|
+
type: "account",
|
|
15
|
+
renderMode: "sections",
|
|
16
|
+
themeId: "my-simple",
|
|
17
|
+
editable: true,
|
|
18
|
+
published: true,
|
|
19
|
+
|
|
20
|
+
seo: {
|
|
21
|
+
title: "My Profile",
|
|
22
|
+
description: "Manage your account profile",
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
sections: [
|
|
26
|
+
{
|
|
27
|
+
id: "profile-1",
|
|
28
|
+
type: "my-simple-profile",
|
|
29
|
+
template: "default",
|
|
30
|
+
order: 0,
|
|
31
|
+
enabled: true,
|
|
32
|
+
settings: {},
|
|
33
|
+
components: [],
|
|
34
|
+
blocks: [],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default profilePageConfig;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* My Simple Theme - Register Page Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PageConfig } from "@onexapis/core/types";
|
|
6
|
+
|
|
7
|
+
export const registerPageConfig: Omit<
|
|
8
|
+
PageConfig,
|
|
9
|
+
"id" | "createdAt" | "updatedAt"
|
|
10
|
+
> = {
|
|
11
|
+
title: "Register",
|
|
12
|
+
handle: "register",
|
|
13
|
+
path: "/register",
|
|
14
|
+
type: "auth",
|
|
15
|
+
renderMode: "sections",
|
|
16
|
+
themeId: "my-simple",
|
|
17
|
+
editable: true,
|
|
18
|
+
published: true,
|
|
19
|
+
hideHeader: true,
|
|
20
|
+
hideFooter: true,
|
|
21
|
+
|
|
22
|
+
seo: {
|
|
23
|
+
title: "Create Account",
|
|
24
|
+
description: "Create a new account",
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
sections: [
|
|
28
|
+
{
|
|
29
|
+
id: "auth-register-1",
|
|
30
|
+
type: "my-simple-auth-register",
|
|
31
|
+
template: "default",
|
|
32
|
+
order: 0,
|
|
33
|
+
enabled: true,
|
|
34
|
+
settings: {},
|
|
35
|
+
components: [],
|
|
36
|
+
blocks: [],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default registerPageConfig;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* My Simple Theme - Verify Code Page Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PageConfig } from "@onexapis/core/types";
|
|
6
|
+
|
|
7
|
+
export const verifyCodePageConfig: Omit<
|
|
8
|
+
PageConfig,
|
|
9
|
+
"id" | "createdAt" | "updatedAt"
|
|
10
|
+
> = {
|
|
11
|
+
title: "Verify Code",
|
|
12
|
+
handle: "verify-code",
|
|
13
|
+
path: "/verify-code",
|
|
14
|
+
type: "auth",
|
|
15
|
+
renderMode: "sections",
|
|
16
|
+
themeId: "my-simple",
|
|
17
|
+
editable: true,
|
|
18
|
+
published: true,
|
|
19
|
+
hideHeader: true,
|
|
20
|
+
hideFooter: true,
|
|
21
|
+
|
|
22
|
+
seo: {
|
|
23
|
+
title: "Verify Your Account",
|
|
24
|
+
description: "Enter the verification code sent to your email",
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
sections: [
|
|
28
|
+
{
|
|
29
|
+
id: "auth-verify-code-1",
|
|
30
|
+
type: "my-simple-auth-verify-code",
|
|
31
|
+
template: "default",
|
|
32
|
+
order: 0,
|
|
33
|
+
enabled: true,
|
|
34
|
+
settings: {},
|
|
35
|
+
components: [],
|
|
36
|
+
blocks: [],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default verifyCodePageConfig;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Forgot Password - Default Template
|
|
3
|
+
* Clean forgot password form with customizable settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import type { SectionComponentProps } from "@onexapis/core/types";
|
|
9
|
+
import coreUtils from "@onexapis/core/utils";
|
|
10
|
+
import { useForgotPasswordForm } from "../../hooks/use-forgot-password-form";
|
|
11
|
+
|
|
12
|
+
const { getSectionValues } = coreUtils;
|
|
13
|
+
|
|
14
|
+
export function AuthForgotPasswordDefault({
|
|
15
|
+
section,
|
|
16
|
+
schema,
|
|
17
|
+
isEditing,
|
|
18
|
+
}: SectionComponentProps) {
|
|
19
|
+
const { settings } = getSectionValues(section, schema);
|
|
20
|
+
const {
|
|
21
|
+
heading,
|
|
22
|
+
subheading,
|
|
23
|
+
usernameLabel,
|
|
24
|
+
usernamePlaceholder,
|
|
25
|
+
submitButtonText,
|
|
26
|
+
loadingText,
|
|
27
|
+
backToLoginText,
|
|
28
|
+
loginUrl,
|
|
29
|
+
registerPromptText,
|
|
30
|
+
registerLinkText,
|
|
31
|
+
registerUrl,
|
|
32
|
+
backgroundColor,
|
|
33
|
+
cardBackground,
|
|
34
|
+
primaryColor,
|
|
35
|
+
textColor,
|
|
36
|
+
cardBorderRadius,
|
|
37
|
+
} = settings;
|
|
38
|
+
|
|
39
|
+
const { username, error, isPending, handleUsernameChange, handleSubmit } =
|
|
40
|
+
useForgotPasswordForm();
|
|
41
|
+
|
|
42
|
+
const bgColor = String(backgroundColor || "#F9FAFB");
|
|
43
|
+
const cardBg = String(cardBackground || "#FFFFFF");
|
|
44
|
+
const btnColor = String(primaryColor || "#2563EB");
|
|
45
|
+
const headingColor = String(textColor || "#111827");
|
|
46
|
+
const radiusClass = `rounded-${String(cardBorderRadius || "2xl")}`;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<section
|
|
50
|
+
className="min-h-screen w-full flex items-center justify-center p-4"
|
|
51
|
+
style={{ backgroundColor: bgColor }}
|
|
52
|
+
data-section-id={section.id}
|
|
53
|
+
data-section-type={section.type}
|
|
54
|
+
data-section-template="default"
|
|
55
|
+
>
|
|
56
|
+
<div
|
|
57
|
+
className={`w-full max-w-md shadow-lg p-8 ${radiusClass}`}
|
|
58
|
+
style={{ backgroundColor: cardBg }}
|
|
59
|
+
>
|
|
60
|
+
{/* Header */}
|
|
61
|
+
<div className="text-center mb-8">
|
|
62
|
+
<h1 className="text-2xl font-bold" style={{ color: headingColor }}>
|
|
63
|
+
{String(heading)}
|
|
64
|
+
</h1>
|
|
65
|
+
{subheading && (
|
|
66
|
+
<p className="mt-2 text-sm text-gray-500">{String(subheading)}</p>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
{/* Error Message */}
|
|
71
|
+
{error && (
|
|
72
|
+
<div className="mb-4 rounded-lg bg-red-50 border border-red-200 p-3 text-sm text-red-600">
|
|
73
|
+
{error}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
|
|
77
|
+
{/* Form */}
|
|
78
|
+
<form onSubmit={handleSubmit} className="space-y-5">
|
|
79
|
+
{/* Username / Email */}
|
|
80
|
+
<div className="flex flex-col gap-1.5">
|
|
81
|
+
<label
|
|
82
|
+
htmlFor="username"
|
|
83
|
+
className="text-sm font-medium text-gray-700"
|
|
84
|
+
>
|
|
85
|
+
{String(usernameLabel)} <span className="text-red-500">*</span>
|
|
86
|
+
</label>
|
|
87
|
+
<input
|
|
88
|
+
id="username"
|
|
89
|
+
name="username"
|
|
90
|
+
type="text"
|
|
91
|
+
value={username}
|
|
92
|
+
onChange={handleUsernameChange}
|
|
93
|
+
placeholder={String(usernamePlaceholder)}
|
|
94
|
+
disabled={isPending || isEditing}
|
|
95
|
+
className="h-11 w-full rounded-lg border border-gray-300 px-4 text-sm text-gray-900 placeholder:text-gray-400 outline-none transition-colors focus:border-blue-500 focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{/* Submit Button */}
|
|
100
|
+
<button
|
|
101
|
+
type="submit"
|
|
102
|
+
disabled={isPending || isEditing}
|
|
103
|
+
className="h-11 w-full rounded-lg text-sm font-medium text-white transition-colors hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
104
|
+
style={{ backgroundColor: btnColor }}
|
|
105
|
+
>
|
|
106
|
+
{isPending && (
|
|
107
|
+
<svg
|
|
108
|
+
className="h-4 w-4 animate-spin"
|
|
109
|
+
fill="none"
|
|
110
|
+
viewBox="0 0 24 24"
|
|
111
|
+
>
|
|
112
|
+
<circle
|
|
113
|
+
className="opacity-25"
|
|
114
|
+
cx="12"
|
|
115
|
+
cy="12"
|
|
116
|
+
r="10"
|
|
117
|
+
stroke="currentColor"
|
|
118
|
+
strokeWidth="4"
|
|
119
|
+
/>
|
|
120
|
+
<path
|
|
121
|
+
className="opacity-75"
|
|
122
|
+
fill="currentColor"
|
|
123
|
+
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"
|
|
124
|
+
/>
|
|
125
|
+
</svg>
|
|
126
|
+
)}
|
|
127
|
+
{isPending ? String(loadingText) : String(submitButtonText)}
|
|
128
|
+
</button>
|
|
129
|
+
</form>
|
|
130
|
+
|
|
131
|
+
{/* Back to Login */}
|
|
132
|
+
<div className="mt-6 text-center">
|
|
133
|
+
{isEditing ? (
|
|
134
|
+
<span className="inline-flex items-center gap-2 text-sm text-gray-500">
|
|
135
|
+
<svg
|
|
136
|
+
className="h-4 w-4"
|
|
137
|
+
fill="none"
|
|
138
|
+
viewBox="0 0 24 24"
|
|
139
|
+
stroke="currentColor"
|
|
140
|
+
strokeWidth={2}
|
|
141
|
+
>
|
|
142
|
+
<path d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
143
|
+
</svg>
|
|
144
|
+
{String(backToLoginText)}
|
|
145
|
+
</span>
|
|
146
|
+
) : (
|
|
147
|
+
<a
|
|
148
|
+
href={String(loginUrl)}
|
|
149
|
+
className="inline-flex items-center gap-2 text-sm text-gray-500 hover:text-gray-700 transition-colors"
|
|
150
|
+
>
|
|
151
|
+
<svg
|
|
152
|
+
className="h-4 w-4"
|
|
153
|
+
fill="none"
|
|
154
|
+
viewBox="0 0 24 24"
|
|
155
|
+
stroke="currentColor"
|
|
156
|
+
strokeWidth={2}
|
|
157
|
+
>
|
|
158
|
+
<path d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
159
|
+
</svg>
|
|
160
|
+
{String(backToLoginText)}
|
|
161
|
+
</a>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
{/* Register Link */}
|
|
166
|
+
<div className="mt-4 text-center">
|
|
167
|
+
<p className="text-sm text-gray-500">
|
|
168
|
+
{String(registerPromptText)}{" "}
|
|
169
|
+
{isEditing ? (
|
|
170
|
+
<span
|
|
171
|
+
className="font-medium hover:opacity-80"
|
|
172
|
+
style={{ color: btnColor }}
|
|
173
|
+
>
|
|
174
|
+
{String(registerLinkText)}
|
|
175
|
+
</span>
|
|
176
|
+
) : (
|
|
177
|
+
<a
|
|
178
|
+
href={String(registerUrl)}
|
|
179
|
+
className="font-medium hover:opacity-80 transition-colors"
|
|
180
|
+
style={{ color: btnColor }}
|
|
181
|
+
>
|
|
182
|
+
{String(registerLinkText)}
|
|
183
|
+
</a>
|
|
184
|
+
)}
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</section>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default AuthForgotPasswordDefault;
|