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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keystone-design-bootstrap",
3
- "version": "1.0.73",
3
+ "version": "1.0.74",
4
4
  "description": "Keystone Design Bootstrap - Sections, Elements, and Theme System for customer websites",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -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 handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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={email}
72
- onChange={(e) => setEmail(e.target.value)}
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); setPhoneValue(''); }}
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={phoneValue}
95
- onChange={handlePhoneChange}
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
- setStep(result.hasPassword === false ? 'new' : 'returning');
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
- {emailVal && !fullPhone && (
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
- {fullPhone && !emailVal && (
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>