@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,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login Form Hook
|
|
3
|
+
* Manages form state, validation, and submission via useAuth
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback } from "react";
|
|
9
|
+
import { useAuth } from "@onexapis/core/hooks";
|
|
10
|
+
|
|
11
|
+
export interface LoginFormData {
|
|
12
|
+
username: string;
|
|
13
|
+
password: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseLoginFormReturn {
|
|
17
|
+
formData: LoginFormData;
|
|
18
|
+
errors: Record<string, string>;
|
|
19
|
+
showPassword: boolean;
|
|
20
|
+
isPending: boolean;
|
|
21
|
+
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
22
|
+
handleSubmit: (e: React.FormEvent) => void;
|
|
23
|
+
toggleShowPassword: () => void;
|
|
24
|
+
clearError: (field: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useLoginForm(): UseLoginFormReturn {
|
|
28
|
+
const [formData, setFormData] = useState<LoginFormData>({
|
|
29
|
+
username: "",
|
|
30
|
+
password: "",
|
|
31
|
+
});
|
|
32
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
33
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
34
|
+
const [isPending, setIsPending] = useState(false);
|
|
35
|
+
|
|
36
|
+
const { login } = useAuth();
|
|
37
|
+
|
|
38
|
+
const handleInputChange = useCallback(
|
|
39
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
40
|
+
const { name, value } = e.target;
|
|
41
|
+
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
42
|
+
if (errors[name]) {
|
|
43
|
+
setErrors((prev) => ({ ...prev, [name]: "" }));
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[errors]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const validateForm = useCallback((): boolean => {
|
|
50
|
+
const newErrors: Record<string, string> = {};
|
|
51
|
+
|
|
52
|
+
if (!formData.username) {
|
|
53
|
+
newErrors.username = "Please enter your username or email";
|
|
54
|
+
}
|
|
55
|
+
if (!formData.password) {
|
|
56
|
+
newErrors.password = "Please enter your password";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setErrors(newErrors);
|
|
60
|
+
return Object.keys(newErrors).length === 0;
|
|
61
|
+
}, [formData]);
|
|
62
|
+
|
|
63
|
+
const handleSubmit = useCallback(
|
|
64
|
+
async (e: React.FormEvent) => {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
if (!validateForm()) return;
|
|
67
|
+
|
|
68
|
+
setIsPending(true);
|
|
69
|
+
try {
|
|
70
|
+
await login({
|
|
71
|
+
username: formData.username,
|
|
72
|
+
password: formData.password,
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const message = error instanceof Error ? error.message : "Login failed";
|
|
76
|
+
setErrors({ form: message });
|
|
77
|
+
} finally {
|
|
78
|
+
setIsPending(false);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
[validateForm, login, formData]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const toggleShowPassword = useCallback(() => {
|
|
85
|
+
setShowPassword((prev) => !prev);
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
const clearError = useCallback((field: string) => {
|
|
89
|
+
setErrors((prev) => ({ ...prev, [field]: "" }));
|
|
90
|
+
}, []);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
formData,
|
|
94
|
+
errors,
|
|
95
|
+
showPassword,
|
|
96
|
+
isPending,
|
|
97
|
+
handleInputChange,
|
|
98
|
+
handleSubmit,
|
|
99
|
+
toggleShowPassword,
|
|
100
|
+
clearError,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile Form Hook
|
|
3
|
+
* Manages profile form state, dirty tracking, and submission via useAuth
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
9
|
+
import { useAuth } from "@onexapis/core/hooks";
|
|
10
|
+
|
|
11
|
+
export interface ProfileFormData {
|
|
12
|
+
name: string;
|
|
13
|
+
email: string;
|
|
14
|
+
phone: string;
|
|
15
|
+
address: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ChangePasswordData {
|
|
19
|
+
currentPassword: string;
|
|
20
|
+
newPassword: string;
|
|
21
|
+
confirmPassword: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseProfileFormReturn {
|
|
25
|
+
// User state
|
|
26
|
+
isAuthenticated: boolean;
|
|
27
|
+
isLoading: boolean;
|
|
28
|
+
|
|
29
|
+
// Form state
|
|
30
|
+
formData: ProfileFormData;
|
|
31
|
+
handleFieldChange: (field: keyof ProfileFormData, value: string) => void;
|
|
32
|
+
isDirty: boolean;
|
|
33
|
+
isSubmitting: boolean;
|
|
34
|
+
submitError: string | null;
|
|
35
|
+
handleSubmit: (e?: React.FormEvent) => Promise<void>;
|
|
36
|
+
|
|
37
|
+
// Change password
|
|
38
|
+
passwordData: ChangePasswordData;
|
|
39
|
+
handlePasswordFieldChange: (
|
|
40
|
+
field: keyof ChangePasswordData,
|
|
41
|
+
value: string
|
|
42
|
+
) => void;
|
|
43
|
+
showCurrentPassword: boolean;
|
|
44
|
+
showNewPassword: boolean;
|
|
45
|
+
showConfirmPassword: boolean;
|
|
46
|
+
toggleCurrentPassword: () => void;
|
|
47
|
+
toggleNewPassword: () => void;
|
|
48
|
+
toggleConfirmPassword: () => void;
|
|
49
|
+
passwordErrors: Record<string, string>;
|
|
50
|
+
isChangingPassword: boolean;
|
|
51
|
+
showPasswordForm: boolean;
|
|
52
|
+
setShowPasswordForm: (show: boolean) => void;
|
|
53
|
+
handlePasswordSubmit: (e?: React.FormEvent) => Promise<void>;
|
|
54
|
+
|
|
55
|
+
// Logout
|
|
56
|
+
handleLogout: () => Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function useProfileForm(): UseProfileFormReturn {
|
|
60
|
+
const {
|
|
61
|
+
user,
|
|
62
|
+
isAuthenticated,
|
|
63
|
+
isLoading,
|
|
64
|
+
updateProfile,
|
|
65
|
+
changePassword,
|
|
66
|
+
logout,
|
|
67
|
+
initialize,
|
|
68
|
+
} = useAuth();
|
|
69
|
+
|
|
70
|
+
const [formData, setFormData] = useState<ProfileFormData>({
|
|
71
|
+
name: "",
|
|
72
|
+
email: "",
|
|
73
|
+
phone: "",
|
|
74
|
+
address: "",
|
|
75
|
+
});
|
|
76
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
77
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
78
|
+
const initialDataRef = useRef<ProfileFormData | null>(null);
|
|
79
|
+
|
|
80
|
+
// Password form
|
|
81
|
+
const [passwordData, setPasswordData] = useState<ChangePasswordData>({
|
|
82
|
+
currentPassword: "",
|
|
83
|
+
newPassword: "",
|
|
84
|
+
confirmPassword: "",
|
|
85
|
+
});
|
|
86
|
+
const [showCurrentPassword, setShowCurrentPassword] = useState(false);
|
|
87
|
+
const [showNewPassword, setShowNewPassword] = useState(false);
|
|
88
|
+
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
89
|
+
const [passwordErrors, setPasswordErrors] = useState<Record<string, string>>(
|
|
90
|
+
{}
|
|
91
|
+
);
|
|
92
|
+
const [isChangingPassword, setIsChangingPassword] = useState(false);
|
|
93
|
+
const [showPasswordForm, setShowPasswordForm] = useState(false);
|
|
94
|
+
|
|
95
|
+
// Initialize auth on mount
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
initialize();
|
|
98
|
+
}, [initialize]);
|
|
99
|
+
|
|
100
|
+
// Populate form from user data
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (user) {
|
|
103
|
+
const data: ProfileFormData = {
|
|
104
|
+
name: user.name || "",
|
|
105
|
+
email: user.email || "",
|
|
106
|
+
phone: user.phone_number || "",
|
|
107
|
+
address: user.address || "",
|
|
108
|
+
};
|
|
109
|
+
setFormData(data);
|
|
110
|
+
initialDataRef.current = data;
|
|
111
|
+
}
|
|
112
|
+
}, [user]);
|
|
113
|
+
|
|
114
|
+
const handleFieldChange = useCallback(
|
|
115
|
+
(field: keyof ProfileFormData, value: string) => {
|
|
116
|
+
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
117
|
+
setSubmitError(null);
|
|
118
|
+
},
|
|
119
|
+
[]
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const isDirty =
|
|
123
|
+
initialDataRef.current !== null &&
|
|
124
|
+
(formData.name !== initialDataRef.current.name ||
|
|
125
|
+
formData.phone !== initialDataRef.current.phone ||
|
|
126
|
+
formData.address !== initialDataRef.current.address);
|
|
127
|
+
|
|
128
|
+
const handleSubmit = useCallback(
|
|
129
|
+
async (e?: React.FormEvent) => {
|
|
130
|
+
e?.preventDefault();
|
|
131
|
+
if (!isDirty) return;
|
|
132
|
+
|
|
133
|
+
setIsSubmitting(true);
|
|
134
|
+
setSubmitError(null);
|
|
135
|
+
try {
|
|
136
|
+
await updateProfile({
|
|
137
|
+
name: formData.name,
|
|
138
|
+
phone_number: formData.phone,
|
|
139
|
+
address: formData.address,
|
|
140
|
+
});
|
|
141
|
+
// Update initial data ref to new values
|
|
142
|
+
initialDataRef.current = { ...formData };
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const message =
|
|
145
|
+
error instanceof Error ? error.message : "Failed to update profile";
|
|
146
|
+
setSubmitError(message);
|
|
147
|
+
} finally {
|
|
148
|
+
setIsSubmitting(false);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
[isDirty, formData, updateProfile]
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Password handlers
|
|
155
|
+
const handlePasswordFieldChange = useCallback(
|
|
156
|
+
(field: keyof ChangePasswordData, value: string) => {
|
|
157
|
+
setPasswordData((prev) => ({ ...prev, [field]: value }));
|
|
158
|
+
if (passwordErrors[field]) {
|
|
159
|
+
setPasswordErrors((prev) => ({ ...prev, [field]: "" }));
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
[passwordErrors]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const validatePassword = useCallback((): boolean => {
|
|
166
|
+
const errors: Record<string, string> = {};
|
|
167
|
+
|
|
168
|
+
if (!passwordData.currentPassword) {
|
|
169
|
+
errors.currentPassword = "Please enter your current password";
|
|
170
|
+
}
|
|
171
|
+
if (!passwordData.newPassword) {
|
|
172
|
+
errors.newPassword = "Please enter a new password";
|
|
173
|
+
} else if (passwordData.newPassword.length < 8) {
|
|
174
|
+
errors.newPassword = "Password must be at least 8 characters";
|
|
175
|
+
} else if (
|
|
176
|
+
!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(passwordData.newPassword)
|
|
177
|
+
) {
|
|
178
|
+
errors.newPassword =
|
|
179
|
+
"Password must contain uppercase, lowercase, and a number";
|
|
180
|
+
} else if (passwordData.newPassword === passwordData.currentPassword) {
|
|
181
|
+
errors.newPassword =
|
|
182
|
+
"New password must be different from current password";
|
|
183
|
+
}
|
|
184
|
+
if (!passwordData.confirmPassword) {
|
|
185
|
+
errors.confirmPassword = "Please confirm your new password";
|
|
186
|
+
} else if (passwordData.newPassword !== passwordData.confirmPassword) {
|
|
187
|
+
errors.confirmPassword = "Passwords do not match";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
setPasswordErrors(errors);
|
|
191
|
+
return Object.keys(errors).length === 0;
|
|
192
|
+
}, [passwordData]);
|
|
193
|
+
|
|
194
|
+
const handlePasswordSubmit = useCallback(
|
|
195
|
+
async (e?: React.FormEvent) => {
|
|
196
|
+
e?.preventDefault();
|
|
197
|
+
if (!validatePassword()) return;
|
|
198
|
+
|
|
199
|
+
setIsChangingPassword(true);
|
|
200
|
+
try {
|
|
201
|
+
await changePassword({
|
|
202
|
+
oldPassword: passwordData.currentPassword,
|
|
203
|
+
newPassword: passwordData.newPassword,
|
|
204
|
+
});
|
|
205
|
+
// Reset form on success
|
|
206
|
+
setPasswordData({
|
|
207
|
+
currentPassword: "",
|
|
208
|
+
newPassword: "",
|
|
209
|
+
confirmPassword: "",
|
|
210
|
+
});
|
|
211
|
+
setShowPasswordForm(false);
|
|
212
|
+
setPasswordErrors({});
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const message =
|
|
215
|
+
error instanceof Error ? error.message : "Failed to change password";
|
|
216
|
+
setPasswordErrors({ form: message });
|
|
217
|
+
} finally {
|
|
218
|
+
setIsChangingPassword(false);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
[validatePassword, changePassword, passwordData]
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const handleLogout = useCallback(async () => {
|
|
225
|
+
await logout();
|
|
226
|
+
if (typeof window !== "undefined") {
|
|
227
|
+
window.location.href = "/login";
|
|
228
|
+
}
|
|
229
|
+
}, [logout]);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
isAuthenticated,
|
|
233
|
+
isLoading,
|
|
234
|
+
formData,
|
|
235
|
+
handleFieldChange,
|
|
236
|
+
isDirty,
|
|
237
|
+
isSubmitting,
|
|
238
|
+
submitError,
|
|
239
|
+
handleSubmit,
|
|
240
|
+
passwordData,
|
|
241
|
+
handlePasswordFieldChange,
|
|
242
|
+
showCurrentPassword,
|
|
243
|
+
showNewPassword,
|
|
244
|
+
showConfirmPassword,
|
|
245
|
+
toggleCurrentPassword: () => setShowCurrentPassword((p) => !p),
|
|
246
|
+
toggleNewPassword: () => setShowNewPassword((p) => !p),
|
|
247
|
+
toggleConfirmPassword: () => setShowConfirmPassword((p) => !p),
|
|
248
|
+
passwordErrors,
|
|
249
|
+
isChangingPassword,
|
|
250
|
+
showPasswordForm,
|
|
251
|
+
setShowPasswordForm,
|
|
252
|
+
handlePasswordSubmit,
|
|
253
|
+
handleLogout,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register Form Hook
|
|
3
|
+
* Manages form state, validation, and submission via useAuth
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback } from "react";
|
|
9
|
+
import { useAuth } from "@onexapis/core/hooks";
|
|
10
|
+
|
|
11
|
+
export interface RegisterFormData {
|
|
12
|
+
email: string;
|
|
13
|
+
username: string;
|
|
14
|
+
password: string;
|
|
15
|
+
confirmPassword: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UseRegisterFormReturn {
|
|
19
|
+
formData: RegisterFormData;
|
|
20
|
+
errors: Record<string, string>;
|
|
21
|
+
showPassword: boolean;
|
|
22
|
+
showConfirmPassword: boolean;
|
|
23
|
+
isPending: boolean;
|
|
24
|
+
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
25
|
+
handleSubmit: (e: React.FormEvent) => void;
|
|
26
|
+
toggleShowPassword: () => void;
|
|
27
|
+
toggleShowConfirmPassword: () => void;
|
|
28
|
+
clearError: (field: string) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useRegisterForm(): UseRegisterFormReturn {
|
|
32
|
+
const [formData, setFormData] = useState<RegisterFormData>({
|
|
33
|
+
email: "",
|
|
34
|
+
username: "",
|
|
35
|
+
password: "",
|
|
36
|
+
confirmPassword: "",
|
|
37
|
+
});
|
|
38
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
39
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
40
|
+
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
41
|
+
const [isPending, setIsPending] = useState(false);
|
|
42
|
+
|
|
43
|
+
const { signup } = useAuth();
|
|
44
|
+
|
|
45
|
+
const handleInputChange = useCallback(
|
|
46
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
47
|
+
const { name, value } = e.target;
|
|
48
|
+
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
49
|
+
if (errors[name]) {
|
|
50
|
+
setErrors((prev) => ({ ...prev, [name]: "" }));
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[errors]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const validateForm = useCallback((): boolean => {
|
|
57
|
+
const newErrors: Record<string, string> = {};
|
|
58
|
+
|
|
59
|
+
if (!formData.email) {
|
|
60
|
+
newErrors.email = "Please enter your email";
|
|
61
|
+
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
|
62
|
+
newErrors.email = "Invalid email format";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!formData.username) {
|
|
66
|
+
newErrors.username = "Please enter a username";
|
|
67
|
+
} else if (formData.username.length < 3) {
|
|
68
|
+
newErrors.username = "Username must be at least 3 characters";
|
|
69
|
+
} else if (!/^[a-zA-Z0-9_]+$/.test(formData.username)) {
|
|
70
|
+
newErrors.username =
|
|
71
|
+
"Username can only contain letters, numbers, and underscores";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!formData.password) {
|
|
75
|
+
newErrors.password = "Please enter a password";
|
|
76
|
+
} else if (formData.password.length < 8) {
|
|
77
|
+
newErrors.password = "Password must be at least 8 characters";
|
|
78
|
+
} else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(formData.password)) {
|
|
79
|
+
newErrors.password =
|
|
80
|
+
"Password must contain uppercase, lowercase, and a number";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!formData.confirmPassword) {
|
|
84
|
+
newErrors.confirmPassword = "Please confirm your password";
|
|
85
|
+
} else if (formData.password !== formData.confirmPassword) {
|
|
86
|
+
newErrors.confirmPassword = "Passwords do not match";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setErrors(newErrors);
|
|
90
|
+
return Object.keys(newErrors).length === 0;
|
|
91
|
+
}, [formData]);
|
|
92
|
+
|
|
93
|
+
const handleSubmit = useCallback(
|
|
94
|
+
async (e: React.FormEvent) => {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
if (!validateForm()) return;
|
|
97
|
+
|
|
98
|
+
setIsPending(true);
|
|
99
|
+
try {
|
|
100
|
+
await signup({
|
|
101
|
+
email: formData.email,
|
|
102
|
+
username: formData.username,
|
|
103
|
+
password: formData.password,
|
|
104
|
+
name: formData.username,
|
|
105
|
+
});
|
|
106
|
+
// Store email for verify-code page
|
|
107
|
+
if (typeof window !== "undefined") {
|
|
108
|
+
const expiryTimestamp = Date.now() + 60 * 1000;
|
|
109
|
+
localStorage.setItem(
|
|
110
|
+
`resend_countdown_${formData.username}`,
|
|
111
|
+
expiryTimestamp.toString()
|
|
112
|
+
);
|
|
113
|
+
localStorage.setItem(
|
|
114
|
+
`register_email_${formData.username}`,
|
|
115
|
+
formData.email
|
|
116
|
+
);
|
|
117
|
+
window.location.href = `/verify-code?username=${encodeURIComponent(formData.username)}`;
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
const message =
|
|
121
|
+
error instanceof Error ? error.message : "Registration failed";
|
|
122
|
+
setErrors({ form: message });
|
|
123
|
+
} finally {
|
|
124
|
+
setIsPending(false);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
[validateForm, signup, formData]
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const toggleShowPassword = useCallback(() => {
|
|
131
|
+
setShowPassword((prev) => !prev);
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
const toggleShowConfirmPassword = useCallback(() => {
|
|
135
|
+
setShowConfirmPassword((prev) => !prev);
|
|
136
|
+
}, []);
|
|
137
|
+
|
|
138
|
+
const clearError = useCallback((field: string) => {
|
|
139
|
+
setErrors((prev) => ({ ...prev, [field]: "" }));
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
formData,
|
|
144
|
+
errors,
|
|
145
|
+
showPassword,
|
|
146
|
+
showConfirmPassword,
|
|
147
|
+
isPending,
|
|
148
|
+
handleInputChange,
|
|
149
|
+
handleSubmit,
|
|
150
|
+
toggleShowPassword,
|
|
151
|
+
toggleShowConfirmPassword,
|
|
152
|
+
clearError,
|
|
153
|
+
};
|
|
154
|
+
}
|