keystone-design-bootstrap 1.0.72 → 1.0.74
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/package.json
CHANGED
|
@@ -44,6 +44,13 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
44
44
|
const [error, setError] = useState<string | null>(null);
|
|
45
45
|
const [loading, setLoading] = useState(false);
|
|
46
46
|
|
|
47
|
+
// Additional contact method collected on the new-user step.
|
|
48
|
+
// Kept separate so typing here doesn't affect the identifier-step conditions.
|
|
49
|
+
const [additionalPhone, setAdditionalPhone] = useState('');
|
|
50
|
+
const [additionalEmail, setAdditionalEmail] = useState('');
|
|
51
|
+
// Snapshot of which method the user identified with, set when moving to step 'new'.
|
|
52
|
+
const [identifiedWith, setIdentifiedWith] = useState<'email' | 'phone' | null>(null);
|
|
53
|
+
|
|
47
54
|
// Phone helpers
|
|
48
55
|
const country = useMemo(() => countries.find((c) => c.code === selectedCountry), [selectedCountry]);
|
|
49
56
|
const phoneCode = country ? (country.phoneCode.startsWith('+') ? country.phoneCode : `+${country.phoneCode}`) : '+1';
|
|
@@ -59,28 +66,23 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
59
66
|
const fullPhone = phoneDigits.length > 0 ? `${phoneCode}${phoneDigits}` : null;
|
|
60
67
|
const emailVal = email.trim() || null;
|
|
61
68
|
|
|
62
|
-
const
|
|
63
|
-
const digits = e.target.value.replace(/\D/g, '');
|
|
64
|
-
const formatted = nationalMask ? formatDigitsToMask(digits, nationalMask) : digits;
|
|
65
|
-
setPhoneValue(formatted);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const emailInput = (
|
|
69
|
+
const emailInput = (value: string, onChange: (v: string) => void, required = false) => (
|
|
69
70
|
<input
|
|
70
71
|
type="email"
|
|
71
|
-
value={
|
|
72
|
-
onChange={(e) =>
|
|
72
|
+
value={value}
|
|
73
|
+
onChange={(e) => onChange(e.target.value)}
|
|
73
74
|
placeholder="you@example.com"
|
|
74
75
|
className={inputClass}
|
|
75
76
|
autoComplete="email"
|
|
77
|
+
required={required}
|
|
76
78
|
/>
|
|
77
79
|
);
|
|
78
80
|
|
|
79
|
-
const phoneInput = (
|
|
81
|
+
const phoneInput = (value: string, onChange: (v: string) => void, required = false) => (
|
|
80
82
|
<div className="flex rounded-input border border-primary overflow-hidden focus-within:border-brand focus-within:ring-1 focus-within:ring-brand transition-colors">
|
|
81
83
|
<select
|
|
82
84
|
value={selectedCountry}
|
|
83
|
-
onChange={(e) => { setSelectedCountry(e.target.value);
|
|
85
|
+
onChange={(e) => { setSelectedCountry(e.target.value); onChange(''); }}
|
|
84
86
|
className="border-r border-secondary bg-secondary px-2 py-2.5 text-base text-secondary focus:outline-none"
|
|
85
87
|
aria-label="Country code"
|
|
86
88
|
>
|
|
@@ -90,11 +92,15 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
90
92
|
</select>
|
|
91
93
|
<input
|
|
92
94
|
type="tel"
|
|
93
|
-
value={
|
|
94
|
-
onChange={
|
|
95
|
+
value={value}
|
|
96
|
+
onChange={(e) => {
|
|
97
|
+
const digits = e.target.value.replace(/\D/g, '');
|
|
98
|
+
onChange(nationalMask ? formatDigitsToMask(digits, nationalMask) : digits);
|
|
99
|
+
}}
|
|
95
100
|
placeholder={nationalPlaceholder}
|
|
96
101
|
className="flex-1 px-3 py-2.5 text-base text-primary placeholder-quaternary bg-transparent focus:outline-none"
|
|
97
102
|
autoComplete="tel"
|
|
103
|
+
required={required}
|
|
98
104
|
/>
|
|
99
105
|
</div>
|
|
100
106
|
);
|
|
@@ -118,11 +124,17 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
118
124
|
firePixelEvent('Lead');
|
|
119
125
|
captureEvent('portal_login_identified', { method, user_exists: true });
|
|
120
126
|
setWelcomeName(result.firstName ?? null);
|
|
121
|
-
|
|
127
|
+
if (result.hasPassword === false) {
|
|
128
|
+
setIdentifiedWith(emailVal ? 'email' : 'phone');
|
|
129
|
+
setStep('new');
|
|
130
|
+
} else {
|
|
131
|
+
setStep('returning');
|
|
132
|
+
}
|
|
122
133
|
} else if (result.exists === false) {
|
|
123
134
|
await setPixelUserData({ email: emailVal, phone: fullPhone });
|
|
124
135
|
firePixelEvent('Lead');
|
|
125
136
|
captureEvent('portal_login_identified', { method, user_exists: false });
|
|
137
|
+
setIdentifiedWith(emailVal ? 'email' : 'phone');
|
|
126
138
|
setStep('new');
|
|
127
139
|
} else {
|
|
128
140
|
captureEvent('portal_login_failed', { step: 'identifier', reason: 'unknown_error' });
|
|
@@ -175,12 +187,16 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
175
187
|
setError(null);
|
|
176
188
|
setLoading(true);
|
|
177
189
|
try {
|
|
190
|
+
const additionalPhoneDigits = additionalPhone.replace(/\D/g, '');
|
|
191
|
+
const additionalFullPhone = additionalPhoneDigits.length > 0 ? `${phoneCode}${additionalPhoneDigits}` : null;
|
|
192
|
+
const additionalEmailVal = additionalEmail.trim() || null;
|
|
193
|
+
|
|
178
194
|
const res = await fetch('/api/consumer/signup', {
|
|
179
195
|
method: 'POST',
|
|
180
196
|
headers: { 'Content-Type': 'application/json' },
|
|
181
197
|
body: JSON.stringify({
|
|
182
|
-
email: emailVal,
|
|
183
|
-
phone: fullPhone,
|
|
198
|
+
email: emailVal ?? additionalEmailVal,
|
|
199
|
+
phone: fullPhone ?? additionalFullPhone,
|
|
184
200
|
password,
|
|
185
201
|
password_confirmation: passwordConfirm,
|
|
186
202
|
first_name: firstName.trim() || undefined,
|
|
@@ -207,6 +223,9 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
207
223
|
setStep('identifier');
|
|
208
224
|
setPassword('');
|
|
209
225
|
setPasswordConfirm('');
|
|
226
|
+
setAdditionalPhone('');
|
|
227
|
+
setAdditionalEmail('');
|
|
228
|
+
setIdentifiedWith(null);
|
|
210
229
|
setError(null);
|
|
211
230
|
};
|
|
212
231
|
|
|
@@ -255,7 +274,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
255
274
|
<form onSubmit={handleContinue} className="space-y-3">
|
|
256
275
|
<div>
|
|
257
276
|
<label className={labelClass}>Email address</label>
|
|
258
|
-
{emailInput}
|
|
277
|
+
{emailInput(email, setEmail)}
|
|
259
278
|
</div>
|
|
260
279
|
<div className="flex items-center gap-3">
|
|
261
280
|
<hr className="flex-1 border-secondary" />
|
|
@@ -264,7 +283,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
264
283
|
</div>
|
|
265
284
|
<div>
|
|
266
285
|
<label className={labelClass}>Phone number</label>
|
|
267
|
-
{phoneInput}
|
|
286
|
+
{phoneInput(phoneValue, setPhoneValue)}
|
|
268
287
|
</div>
|
|
269
288
|
<div className="pt-1">
|
|
270
289
|
<button
|
|
@@ -345,16 +364,16 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
345
364
|
</div>
|
|
346
365
|
)}
|
|
347
366
|
{/* Collect the missing contact method so the business can reach out */}
|
|
348
|
-
{
|
|
367
|
+
{identifiedWith === 'email' && (
|
|
349
368
|
<div>
|
|
350
|
-
<label className={labelClass}>Phone number
|
|
351
|
-
{phoneInput}
|
|
369
|
+
<label className={labelClass}>Phone number</label>
|
|
370
|
+
{phoneInput(additionalPhone, setAdditionalPhone, true)}
|
|
352
371
|
</div>
|
|
353
372
|
)}
|
|
354
|
-
{
|
|
373
|
+
{identifiedWith === 'phone' && (
|
|
355
374
|
<div>
|
|
356
|
-
<label className={labelClass}>Email address
|
|
357
|
-
{emailInput}
|
|
375
|
+
<label className={labelClass}>Email address</label>
|
|
376
|
+
{emailInput(additionalEmail, setAdditionalEmail, true)}
|
|
358
377
|
</div>
|
|
359
378
|
)}
|
|
360
379
|
<div>
|