@onexapis/cli 1.1.38 → 1.1.39
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 +384 -380
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +381 -376
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +258 -293
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +255 -289
- package/dist/index.mjs.map +1 -1
- package/dist/preview/preview-app.tsx +13 -5
- package/package.json +1 -3
- package/templates/default/AUTH_AND_PROFILE.md +167 -0
- package/templates/default/CLAUDE.md +334 -1
- package/templates/default/LAYOUT.md +195 -0
- package/templates/default/bundle-entry.ts +5 -0
- package/templates/default/esbuild.config.js +20 -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/about.ts +2 -2
- package/templates/default/pages/forgot-password.ts +39 -0
- package/templates/default/pages/home.ts +4 -4
- package/templates/default/pages/login.ts +39 -0
- package/templates/default/pages/profile.ts +39 -0
- package/templates/default/pages/register.ts +39 -0
- package/templates/default/pages/showcase.ts +7 -7
- package/templates/default/pages/verify-code.ts +39 -0
- package/templates/default/sections/about/about.schema.ts +1 -1
- 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/cta/cta.schema.ts +1 -1
- package/templates/default/sections/features/features.schema.ts +1 -1
- 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/gallery/gallery.schema.ts +1 -1
- 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/hero/hero.schema.ts +1 -1
- 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/stats/stats.schema.ts +1 -1
- package/templates/default/sections/testimonials/testimonials.schema.ts +1 -1
- package/templates/default/sections-registry.ts +28 -0
- package/templates/default/theme.layout.ts +71 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile Section
|
|
3
|
+
* Exports schema and component templates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { SectionComponentProps } from "@onexapis/core/types";
|
|
7
|
+
import { ProfileDefault } from "./profile-default";
|
|
8
|
+
|
|
9
|
+
export const profileComponents: Record<
|
|
10
|
+
string,
|
|
11
|
+
React.ComponentType<SectionComponentProps>
|
|
12
|
+
> = { default: ProfileDefault };
|
|
13
|
+
|
|
14
|
+
export { profileSchema } from "./profile.schema";
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile - Default Template
|
|
3
|
+
* User profile page with form fields and change password
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import type { SectionComponentProps } from "@onexapis/core/types";
|
|
9
|
+
import coreUtils from "@onexapis/core/utils";
|
|
10
|
+
import { useProfileForm } from "../../hooks/use-profile-form";
|
|
11
|
+
|
|
12
|
+
const { getSectionValues } = coreUtils;
|
|
13
|
+
|
|
14
|
+
export function ProfileDefault({
|
|
15
|
+
section,
|
|
16
|
+
schema,
|
|
17
|
+
isEditing,
|
|
18
|
+
}: SectionComponentProps) {
|
|
19
|
+
const { settings } = getSectionValues(section, schema);
|
|
20
|
+
const {
|
|
21
|
+
heading,
|
|
22
|
+
nameLabel,
|
|
23
|
+
namePlaceholder,
|
|
24
|
+
emailLabel,
|
|
25
|
+
phoneLabel,
|
|
26
|
+
phonePlaceholder,
|
|
27
|
+
addressLabel,
|
|
28
|
+
addressPlaceholder,
|
|
29
|
+
changePasswordText,
|
|
30
|
+
updateButtonText,
|
|
31
|
+
updatingText,
|
|
32
|
+
currentPasswordLabel,
|
|
33
|
+
currentPasswordPlaceholder,
|
|
34
|
+
newPasswordLabel,
|
|
35
|
+
newPasswordPlaceholder,
|
|
36
|
+
confirmPasswordLabel,
|
|
37
|
+
confirmPasswordPlaceholder,
|
|
38
|
+
changePasswordButtonText,
|
|
39
|
+
changingPasswordText,
|
|
40
|
+
cancelText,
|
|
41
|
+
passwordRequirementText,
|
|
42
|
+
logoutText,
|
|
43
|
+
backgroundColor,
|
|
44
|
+
cardBackground,
|
|
45
|
+
primaryColor,
|
|
46
|
+
textColor,
|
|
47
|
+
cardBorderRadius,
|
|
48
|
+
} = settings;
|
|
49
|
+
|
|
50
|
+
const profile = useProfileForm();
|
|
51
|
+
|
|
52
|
+
const bgColor = String(backgroundColor || "#F9FAFB");
|
|
53
|
+
const cardBg = String(cardBackground || "#FFFFFF");
|
|
54
|
+
const btnColor = String(primaryColor || "#2563EB");
|
|
55
|
+
const headingColor = String(textColor || "#111827");
|
|
56
|
+
const radiusClass = `rounded-${String(cardBorderRadius || "2xl")}`;
|
|
57
|
+
|
|
58
|
+
const { passwordErrors } = profile;
|
|
59
|
+
|
|
60
|
+
// Loading skeleton
|
|
61
|
+
if (profile.isLoading && !isEditing) {
|
|
62
|
+
return (
|
|
63
|
+
<section
|
|
64
|
+
className="py-12"
|
|
65
|
+
style={{ backgroundColor: bgColor }}
|
|
66
|
+
data-section-id={section.id}
|
|
67
|
+
data-section-type={section.type}
|
|
68
|
+
data-section-template="default"
|
|
69
|
+
>
|
|
70
|
+
<div className="container mx-auto px-4 max-w-2xl">
|
|
71
|
+
<div
|
|
72
|
+
className={`shadow-lg p-8 animate-pulse ${radiusClass}`}
|
|
73
|
+
style={{ backgroundColor: cardBg }}
|
|
74
|
+
>
|
|
75
|
+
<div className="h-8 bg-gray-200 rounded w-48 mb-8" />
|
|
76
|
+
<div className="space-y-6">
|
|
77
|
+
{[1, 2, 3, 4].map((i) => (
|
|
78
|
+
<div key={i}>
|
|
79
|
+
<div className="h-4 bg-gray-200 rounded w-24 mb-2" />
|
|
80
|
+
<div className="h-11 bg-gray-200 rounded-lg" />
|
|
81
|
+
</div>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</section>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Not authenticated
|
|
91
|
+
if (!profile.isAuthenticated && !isEditing) {
|
|
92
|
+
return (
|
|
93
|
+
<section
|
|
94
|
+
className="py-12"
|
|
95
|
+
style={{ backgroundColor: bgColor }}
|
|
96
|
+
data-section-id={section.id}
|
|
97
|
+
data-section-type={section.type}
|
|
98
|
+
data-section-template="default"
|
|
99
|
+
>
|
|
100
|
+
<div className="container mx-auto px-4 max-w-2xl">
|
|
101
|
+
<div
|
|
102
|
+
className={`shadow-lg p-8 text-center ${radiusClass}`}
|
|
103
|
+
style={{ backgroundColor: cardBg }}
|
|
104
|
+
>
|
|
105
|
+
<h2
|
|
106
|
+
className="text-xl font-semibold mb-4"
|
|
107
|
+
style={{ color: headingColor }}
|
|
108
|
+
>
|
|
109
|
+
Please sign in to view your profile
|
|
110
|
+
</h2>
|
|
111
|
+
<a
|
|
112
|
+
href="/login"
|
|
113
|
+
className="inline-block px-6 py-2.5 text-white text-sm font-medium rounded-lg hover:opacity-90 transition-colors"
|
|
114
|
+
style={{ backgroundColor: btnColor }}
|
|
115
|
+
>
|
|
116
|
+
Sign In
|
|
117
|
+
</a>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</section>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<section
|
|
126
|
+
className="py-12"
|
|
127
|
+
style={{ backgroundColor: bgColor }}
|
|
128
|
+
data-section-id={section.id}
|
|
129
|
+
data-section-type={section.type}
|
|
130
|
+
data-section-template="default"
|
|
131
|
+
>
|
|
132
|
+
<div className="container mx-auto px-4 max-w-2xl">
|
|
133
|
+
<div
|
|
134
|
+
className={`shadow-lg overflow-hidden ${radiusClass}`}
|
|
135
|
+
style={{ backgroundColor: cardBg }}
|
|
136
|
+
>
|
|
137
|
+
{/* Header */}
|
|
138
|
+
<div className="flex items-center justify-between px-8 pt-8 pb-2">
|
|
139
|
+
<h1 className="text-2xl font-bold" style={{ color: headingColor }}>
|
|
140
|
+
{String(heading)}
|
|
141
|
+
</h1>
|
|
142
|
+
{!isEditing && (
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
onClick={profile.handleLogout}
|
|
146
|
+
className="text-sm text-gray-500 hover:text-red-600 transition-colors"
|
|
147
|
+
>
|
|
148
|
+
{String(logoutText)}
|
|
149
|
+
</button>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{/* Profile Form */}
|
|
154
|
+
<form onSubmit={profile.handleSubmit} className="px-8 py-6 space-y-5">
|
|
155
|
+
{/* Error */}
|
|
156
|
+
{profile.submitError && (
|
|
157
|
+
<div className="rounded-lg bg-red-50 border border-red-200 p-3 text-sm text-red-600">
|
|
158
|
+
{profile.submitError}
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{/* Name */}
|
|
163
|
+
<div className="flex flex-col gap-1.5">
|
|
164
|
+
<label
|
|
165
|
+
htmlFor="profile-name"
|
|
166
|
+
className="text-sm font-medium text-gray-700"
|
|
167
|
+
>
|
|
168
|
+
{String(nameLabel)}
|
|
169
|
+
</label>
|
|
170
|
+
<input
|
|
171
|
+
id="profile-name"
|
|
172
|
+
type="text"
|
|
173
|
+
value={profile.formData.name}
|
|
174
|
+
onChange={(e) =>
|
|
175
|
+
profile.handleFieldChange("name", e.target.value)
|
|
176
|
+
}
|
|
177
|
+
placeholder={String(namePlaceholder)}
|
|
178
|
+
disabled={profile.isSubmitting || isEditing}
|
|
179
|
+
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"
|
|
180
|
+
/>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
{/* Email (read-only) */}
|
|
184
|
+
<div className="flex flex-col gap-1.5">
|
|
185
|
+
<label
|
|
186
|
+
htmlFor="profile-email"
|
|
187
|
+
className="text-sm font-medium text-gray-700"
|
|
188
|
+
>
|
|
189
|
+
{String(emailLabel)}
|
|
190
|
+
</label>
|
|
191
|
+
<input
|
|
192
|
+
id="profile-email"
|
|
193
|
+
type="text"
|
|
194
|
+
value={profile.formData.email}
|
|
195
|
+
disabled
|
|
196
|
+
className="h-11 w-full rounded-lg border border-gray-200 bg-gray-50 px-4 text-sm text-gray-500 outline-none"
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
{/* Phone */}
|
|
201
|
+
<div className="flex flex-col gap-1.5">
|
|
202
|
+
<label
|
|
203
|
+
htmlFor="profile-phone"
|
|
204
|
+
className="text-sm font-medium text-gray-700"
|
|
205
|
+
>
|
|
206
|
+
{String(phoneLabel)}
|
|
207
|
+
</label>
|
|
208
|
+
<input
|
|
209
|
+
id="profile-phone"
|
|
210
|
+
type="tel"
|
|
211
|
+
value={profile.formData.phone}
|
|
212
|
+
onChange={(e) =>
|
|
213
|
+
profile.handleFieldChange("phone", e.target.value)
|
|
214
|
+
}
|
|
215
|
+
placeholder={String(phonePlaceholder)}
|
|
216
|
+
disabled={profile.isSubmitting || isEditing}
|
|
217
|
+
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"
|
|
218
|
+
/>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
{/* Address */}
|
|
222
|
+
<div className="flex flex-col gap-1.5">
|
|
223
|
+
<label
|
|
224
|
+
htmlFor="profile-address"
|
|
225
|
+
className="text-sm font-medium text-gray-700"
|
|
226
|
+
>
|
|
227
|
+
{String(addressLabel)}
|
|
228
|
+
</label>
|
|
229
|
+
<input
|
|
230
|
+
id="profile-address"
|
|
231
|
+
type="text"
|
|
232
|
+
value={profile.formData.address}
|
|
233
|
+
onChange={(e) =>
|
|
234
|
+
profile.handleFieldChange("address", e.target.value)
|
|
235
|
+
}
|
|
236
|
+
placeholder={String(addressPlaceholder)}
|
|
237
|
+
disabled={profile.isSubmitting || isEditing}
|
|
238
|
+
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"
|
|
239
|
+
/>
|
|
240
|
+
</div>
|
|
241
|
+
</form>
|
|
242
|
+
|
|
243
|
+
{/* Update Button Footer */}
|
|
244
|
+
<div className="flex justify-end border-t border-gray-100 bg-gray-50/50 px-8 py-4">
|
|
245
|
+
<button
|
|
246
|
+
type="button"
|
|
247
|
+
onClick={profile.handleSubmit}
|
|
248
|
+
disabled={profile.isSubmitting || !profile.isDirty || isEditing}
|
|
249
|
+
className="px-6 py-2.5 rounded-lg text-sm font-medium text-white transition-colors hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
|
250
|
+
style={{ backgroundColor: btnColor }}
|
|
251
|
+
>
|
|
252
|
+
{profile.isSubmitting && (
|
|
253
|
+
<svg
|
|
254
|
+
className="h-4 w-4 animate-spin"
|
|
255
|
+
fill="none"
|
|
256
|
+
viewBox="0 0 24 24"
|
|
257
|
+
>
|
|
258
|
+
<circle
|
|
259
|
+
className="opacity-25"
|
|
260
|
+
cx="12"
|
|
261
|
+
cy="12"
|
|
262
|
+
r="10"
|
|
263
|
+
stroke="currentColor"
|
|
264
|
+
strokeWidth="4"
|
|
265
|
+
/>
|
|
266
|
+
<path
|
|
267
|
+
className="opacity-75"
|
|
268
|
+
fill="currentColor"
|
|
269
|
+
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"
|
|
270
|
+
/>
|
|
271
|
+
</svg>
|
|
272
|
+
)}
|
|
273
|
+
{profile.isSubmitting
|
|
274
|
+
? String(updatingText)
|
|
275
|
+
: String(updateButtonText)}
|
|
276
|
+
</button>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
{/* Change Password Section */}
|
|
280
|
+
<div className="border-t border-gray-100 px-8 py-6">
|
|
281
|
+
{!profile.showPasswordForm ? (
|
|
282
|
+
<button
|
|
283
|
+
type="button"
|
|
284
|
+
onClick={() => profile.setShowPasswordForm(true)}
|
|
285
|
+
disabled={isEditing}
|
|
286
|
+
className="text-sm font-medium hover:opacity-80 transition-colors disabled:opacity-50"
|
|
287
|
+
style={{ color: btnColor }}
|
|
288
|
+
>
|
|
289
|
+
{String(changePasswordText)}
|
|
290
|
+
</button>
|
|
291
|
+
) : (
|
|
292
|
+
<div className="space-y-4">
|
|
293
|
+
<h3
|
|
294
|
+
className="text-base font-semibold"
|
|
295
|
+
style={{ color: headingColor }}
|
|
296
|
+
>
|
|
297
|
+
{String(changePasswordText)}
|
|
298
|
+
</h3>
|
|
299
|
+
|
|
300
|
+
{passwordErrors.form && (
|
|
301
|
+
<div className="rounded-lg bg-red-50 border border-red-200 p-3 text-sm text-red-600">
|
|
302
|
+
{passwordErrors.form}
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
|
|
306
|
+
{/* Current Password */}
|
|
307
|
+
<div className="flex flex-col gap-1.5">
|
|
308
|
+
<label
|
|
309
|
+
htmlFor="current-password"
|
|
310
|
+
className="text-sm font-medium text-gray-700"
|
|
311
|
+
>
|
|
312
|
+
{String(currentPasswordLabel)}{" "}
|
|
313
|
+
<span className="text-red-500">*</span>
|
|
314
|
+
</label>
|
|
315
|
+
<div className="relative">
|
|
316
|
+
<input
|
|
317
|
+
id="current-password"
|
|
318
|
+
type={profile.showCurrentPassword ? "text" : "password"}
|
|
319
|
+
value={profile.passwordData.currentPassword}
|
|
320
|
+
onChange={(e) =>
|
|
321
|
+
profile.handlePasswordFieldChange(
|
|
322
|
+
"currentPassword",
|
|
323
|
+
e.target.value
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
placeholder={String(currentPasswordPlaceholder)}
|
|
327
|
+
disabled={profile.isChangingPassword}
|
|
328
|
+
className={`h-11 w-full rounded-lg border px-4 pr-10 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 ${
|
|
329
|
+
passwordErrors.currentPassword
|
|
330
|
+
? "border-red-400"
|
|
331
|
+
: "border-gray-300"
|
|
332
|
+
}`}
|
|
333
|
+
/>
|
|
334
|
+
<button
|
|
335
|
+
type="button"
|
|
336
|
+
onClick={profile.toggleCurrentPassword}
|
|
337
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
|
338
|
+
tabIndex={-1}
|
|
339
|
+
>
|
|
340
|
+
<EyeIcon open={profile.showCurrentPassword} />
|
|
341
|
+
</button>
|
|
342
|
+
</div>
|
|
343
|
+
{passwordErrors.currentPassword && (
|
|
344
|
+
<p className="text-xs text-red-500">
|
|
345
|
+
{passwordErrors.currentPassword}
|
|
346
|
+
</p>
|
|
347
|
+
)}
|
|
348
|
+
</div>
|
|
349
|
+
|
|
350
|
+
{/* New Password */}
|
|
351
|
+
<div className="flex flex-col gap-1.5">
|
|
352
|
+
<label
|
|
353
|
+
htmlFor="new-password"
|
|
354
|
+
className="text-sm font-medium text-gray-700"
|
|
355
|
+
>
|
|
356
|
+
{String(newPasswordLabel)}{" "}
|
|
357
|
+
<span className="text-red-500">*</span>
|
|
358
|
+
</label>
|
|
359
|
+
<div className="relative">
|
|
360
|
+
<input
|
|
361
|
+
id="new-password"
|
|
362
|
+
type={profile.showNewPassword ? "text" : "password"}
|
|
363
|
+
value={profile.passwordData.newPassword}
|
|
364
|
+
onChange={(e) =>
|
|
365
|
+
profile.handlePasswordFieldChange(
|
|
366
|
+
"newPassword",
|
|
367
|
+
e.target.value
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
placeholder={String(newPasswordPlaceholder)}
|
|
371
|
+
disabled={profile.isChangingPassword}
|
|
372
|
+
className={`h-11 w-full rounded-lg border px-4 pr-10 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 ${
|
|
373
|
+
passwordErrors.newPassword
|
|
374
|
+
? "border-red-400"
|
|
375
|
+
: "border-gray-300"
|
|
376
|
+
}`}
|
|
377
|
+
/>
|
|
378
|
+
<button
|
|
379
|
+
type="button"
|
|
380
|
+
onClick={profile.toggleNewPassword}
|
|
381
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
|
382
|
+
tabIndex={-1}
|
|
383
|
+
>
|
|
384
|
+
<EyeIcon open={profile.showNewPassword} />
|
|
385
|
+
</button>
|
|
386
|
+
</div>
|
|
387
|
+
{passwordErrors.newPassword ? (
|
|
388
|
+
<p className="text-xs text-red-500">
|
|
389
|
+
{passwordErrors.newPassword}
|
|
390
|
+
</p>
|
|
391
|
+
) : (
|
|
392
|
+
<p className="text-xs text-gray-400">
|
|
393
|
+
{String(passwordRequirementText)}
|
|
394
|
+
</p>
|
|
395
|
+
)}
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
{/* Confirm Password */}
|
|
399
|
+
<div className="flex flex-col gap-1.5">
|
|
400
|
+
<label
|
|
401
|
+
htmlFor="confirm-new-password"
|
|
402
|
+
className="text-sm font-medium text-gray-700"
|
|
403
|
+
>
|
|
404
|
+
{String(confirmPasswordLabel)}{" "}
|
|
405
|
+
<span className="text-red-500">*</span>
|
|
406
|
+
</label>
|
|
407
|
+
<div className="relative">
|
|
408
|
+
<input
|
|
409
|
+
id="confirm-new-password"
|
|
410
|
+
type={profile.showConfirmPassword ? "text" : "password"}
|
|
411
|
+
value={profile.passwordData.confirmPassword}
|
|
412
|
+
onChange={(e) =>
|
|
413
|
+
profile.handlePasswordFieldChange(
|
|
414
|
+
"confirmPassword",
|
|
415
|
+
e.target.value
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
placeholder={String(confirmPasswordPlaceholder)}
|
|
419
|
+
disabled={profile.isChangingPassword}
|
|
420
|
+
className={`h-11 w-full rounded-lg border px-4 pr-10 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 ${
|
|
421
|
+
passwordErrors.confirmPassword
|
|
422
|
+
? "border-red-400"
|
|
423
|
+
: "border-gray-300"
|
|
424
|
+
}`}
|
|
425
|
+
/>
|
|
426
|
+
<button
|
|
427
|
+
type="button"
|
|
428
|
+
onClick={profile.toggleConfirmPassword}
|
|
429
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
|
430
|
+
tabIndex={-1}
|
|
431
|
+
>
|
|
432
|
+
<EyeIcon open={profile.showConfirmPassword} />
|
|
433
|
+
</button>
|
|
434
|
+
</div>
|
|
435
|
+
{passwordErrors.confirmPassword && (
|
|
436
|
+
<p className="text-xs text-red-500">
|
|
437
|
+
{passwordErrors.confirmPassword}
|
|
438
|
+
</p>
|
|
439
|
+
)}
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
{/* Buttons */}
|
|
443
|
+
<div className="flex items-center gap-3 pt-1">
|
|
444
|
+
<button
|
|
445
|
+
type="button"
|
|
446
|
+
onClick={profile.handlePasswordSubmit}
|
|
447
|
+
disabled={profile.isChangingPassword}
|
|
448
|
+
className="px-5 py-2.5 rounded-lg text-sm font-medium text-white transition-colors hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
|
449
|
+
style={{ backgroundColor: btnColor }}
|
|
450
|
+
>
|
|
451
|
+
{profile.isChangingPassword && (
|
|
452
|
+
<svg
|
|
453
|
+
className="h-4 w-4 animate-spin"
|
|
454
|
+
fill="none"
|
|
455
|
+
viewBox="0 0 24 24"
|
|
456
|
+
>
|
|
457
|
+
<circle
|
|
458
|
+
className="opacity-25"
|
|
459
|
+
cx="12"
|
|
460
|
+
cy="12"
|
|
461
|
+
r="10"
|
|
462
|
+
stroke="currentColor"
|
|
463
|
+
strokeWidth="4"
|
|
464
|
+
/>
|
|
465
|
+
<path
|
|
466
|
+
className="opacity-75"
|
|
467
|
+
fill="currentColor"
|
|
468
|
+
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"
|
|
469
|
+
/>
|
|
470
|
+
</svg>
|
|
471
|
+
)}
|
|
472
|
+
{profile.isChangingPassword
|
|
473
|
+
? String(changingPasswordText)
|
|
474
|
+
: String(changePasswordButtonText)}
|
|
475
|
+
</button>
|
|
476
|
+
<button
|
|
477
|
+
type="button"
|
|
478
|
+
onClick={() => profile.setShowPasswordForm(false)}
|
|
479
|
+
className="px-5 py-2.5 rounded-lg border border-gray-300 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50"
|
|
480
|
+
>
|
|
481
|
+
{String(cancelText)}
|
|
482
|
+
</button>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
)}
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
</section>
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** Inline eye icon to avoid lucide-react dependency */
|
|
494
|
+
function EyeIcon({ open }: { open: boolean }) {
|
|
495
|
+
if (open) {
|
|
496
|
+
return (
|
|
497
|
+
<svg
|
|
498
|
+
className="h-4 w-4"
|
|
499
|
+
fill="none"
|
|
500
|
+
viewBox="0 0 24 24"
|
|
501
|
+
stroke="currentColor"
|
|
502
|
+
strokeWidth={2}
|
|
503
|
+
>
|
|
504
|
+
<path 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.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
|
|
505
|
+
</svg>
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
return (
|
|
509
|
+
<svg
|
|
510
|
+
className="h-4 w-4"
|
|
511
|
+
fill="none"
|
|
512
|
+
viewBox="0 0 24 24"
|
|
513
|
+
stroke="currentColor"
|
|
514
|
+
strokeWidth={2}
|
|
515
|
+
>
|
|
516
|
+
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
517
|
+
<path 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" />
|
|
518
|
+
</svg>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export default ProfileDefault;
|