keystone-design-bootstrap 1.0.73 → 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,17 +66,11 @@ 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 = (required = false) => (
|
|
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,11 +78,11 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
77
78
|
/>
|
|
78
79
|
);
|
|
79
80
|
|
|
80
|
-
const phoneInput = (required = false) => (
|
|
81
|
+
const phoneInput = (value: string, onChange: (v: string) => void, required = false) => (
|
|
81
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">
|
|
82
83
|
<select
|
|
83
84
|
value={selectedCountry}
|
|
84
|
-
onChange={(e) => { setSelectedCountry(e.target.value);
|
|
85
|
+
onChange={(e) => { setSelectedCountry(e.target.value); onChange(''); }}
|
|
85
86
|
className="border-r border-secondary bg-secondary px-2 py-2.5 text-base text-secondary focus:outline-none"
|
|
86
87
|
aria-label="Country code"
|
|
87
88
|
>
|
|
@@ -91,8 +92,11 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
91
92
|
</select>
|
|
92
93
|
<input
|
|
93
94
|
type="tel"
|
|
94
|
-
value={
|
|
95
|
-
onChange={
|
|
95
|
+
value={value}
|
|
96
|
+
onChange={(e) => {
|
|
97
|
+
const digits = e.target.value.replace(/\D/g, '');
|
|
98
|
+
onChange(nationalMask ? formatDigitsToMask(digits, nationalMask) : digits);
|
|
99
|
+
}}
|
|
96
100
|
placeholder={nationalPlaceholder}
|
|
97
101
|
className="flex-1 px-3 py-2.5 text-base text-primary placeholder-quaternary bg-transparent focus:outline-none"
|
|
98
102
|
autoComplete="tel"
|
|
@@ -120,11 +124,17 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
120
124
|
firePixelEvent('Lead');
|
|
121
125
|
captureEvent('portal_login_identified', { method, user_exists: true });
|
|
122
126
|
setWelcomeName(result.firstName ?? null);
|
|
123
|
-
|
|
127
|
+
if (result.hasPassword === false) {
|
|
128
|
+
setIdentifiedWith(emailVal ? 'email' : 'phone');
|
|
129
|
+
setStep('new');
|
|
130
|
+
} else {
|
|
131
|
+
setStep('returning');
|
|
132
|
+
}
|
|
124
133
|
} else if (result.exists === false) {
|
|
125
134
|
await setPixelUserData({ email: emailVal, phone: fullPhone });
|
|
126
135
|
firePixelEvent('Lead');
|
|
127
136
|
captureEvent('portal_login_identified', { method, user_exists: false });
|
|
137
|
+
setIdentifiedWith(emailVal ? 'email' : 'phone');
|
|
128
138
|
setStep('new');
|
|
129
139
|
} else {
|
|
130
140
|
captureEvent('portal_login_failed', { step: 'identifier', reason: 'unknown_error' });
|
|
@@ -177,12 +187,16 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
177
187
|
setError(null);
|
|
178
188
|
setLoading(true);
|
|
179
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
|
+
|
|
180
194
|
const res = await fetch('/api/consumer/signup', {
|
|
181
195
|
method: 'POST',
|
|
182
196
|
headers: { 'Content-Type': 'application/json' },
|
|
183
197
|
body: JSON.stringify({
|
|
184
|
-
email: emailVal,
|
|
185
|
-
phone: fullPhone,
|
|
198
|
+
email: emailVal ?? additionalEmailVal,
|
|
199
|
+
phone: fullPhone ?? additionalFullPhone,
|
|
186
200
|
password,
|
|
187
201
|
password_confirmation: passwordConfirm,
|
|
188
202
|
first_name: firstName.trim() || undefined,
|
|
@@ -209,6 +223,9 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
209
223
|
setStep('identifier');
|
|
210
224
|
setPassword('');
|
|
211
225
|
setPasswordConfirm('');
|
|
226
|
+
setAdditionalPhone('');
|
|
227
|
+
setAdditionalEmail('');
|
|
228
|
+
setIdentifiedWith(null);
|
|
212
229
|
setError(null);
|
|
213
230
|
};
|
|
214
231
|
|
|
@@ -257,7 +274,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
257
274
|
<form onSubmit={handleContinue} className="space-y-3">
|
|
258
275
|
<div>
|
|
259
276
|
<label className={labelClass}>Email address</label>
|
|
260
|
-
{emailInput()}
|
|
277
|
+
{emailInput(email, setEmail)}
|
|
261
278
|
</div>
|
|
262
279
|
<div className="flex items-center gap-3">
|
|
263
280
|
<hr className="flex-1 border-secondary" />
|
|
@@ -266,7 +283,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
266
283
|
</div>
|
|
267
284
|
<div>
|
|
268
285
|
<label className={labelClass}>Phone number</label>
|
|
269
|
-
{phoneInput()}
|
|
286
|
+
{phoneInput(phoneValue, setPhoneValue)}
|
|
270
287
|
</div>
|
|
271
288
|
<div className="pt-1">
|
|
272
289
|
<button
|
|
@@ -347,16 +364,16 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
347
364
|
</div>
|
|
348
365
|
)}
|
|
349
366
|
{/* Collect the missing contact method so the business can reach out */}
|
|
350
|
-
{
|
|
367
|
+
{identifiedWith === 'email' && (
|
|
351
368
|
<div>
|
|
352
369
|
<label className={labelClass}>Phone number</label>
|
|
353
|
-
{phoneInput(true)}
|
|
370
|
+
{phoneInput(additionalPhone, setAdditionalPhone, true)}
|
|
354
371
|
</div>
|
|
355
372
|
)}
|
|
356
|
-
{
|
|
373
|
+
{identifiedWith === 'phone' && (
|
|
357
374
|
<div>
|
|
358
375
|
<label className={labelClass}>Email address</label>
|
|
359
|
-
{emailInput(true)}
|
|
376
|
+
{emailInput(additionalEmail, setAdditionalEmail, true)}
|
|
360
377
|
</div>
|
|
361
378
|
)}
|
|
362
379
|
<div>
|