chordia-ui 3.2.0 → 3.2.1

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": "chordia-ui",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "Chordia Design System - UI components, tokens, and Tailwind preset",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -157,6 +157,7 @@ export default function LoginPage({
157
157
  onGoToLogin,
158
158
  loading: externalLoading,
159
159
  error: externalError,
160
+ codeError,
160
161
  defaultView = 'signin',
161
162
  }) {
162
163
  const [email, setEmail] = useState('');
@@ -166,8 +167,9 @@ export default function LoginPage({
166
167
  const [internalError, setInternalError] = useState(null);
167
168
  const [view, setView] = useState(defaultView);
168
169
  const [codeEmail, setCodeEmail] = useState('');
170
+ const [codeFullName, setCodeFullName] = useState('');
169
171
  const [verifyEmail, setVerifyEmail] = useState('');
170
- const [otpDigits, setOtpDigits] = useState(['', '', '', '']);
172
+ const [otpDigits, setOtpDigits] = useState(['', '', '', '', '', '']);
171
173
  const otpRefs = useRef([]);
172
174
  const [firstName, setFirstName] = useState('');
173
175
  const [lastName, setLastName] = useState('');
@@ -185,6 +187,29 @@ export default function LoginPage({
185
187
  return () => clearInterval(t);
186
188
  }, []);
187
189
 
190
+ // Sync React state with browser autofill (autofill doesn't fire onChange)
191
+ const emailRef = useRef(null);
192
+ const passwordRef = useRef(null);
193
+ useEffect(() => {
194
+ const sync = () => {
195
+ const e = emailRef.current;
196
+ const p = passwordRef.current;
197
+ if (e && e.value && !email) setEmail(e.value);
198
+ if (p && p.value && !password) setPassword(p.value);
199
+ };
200
+ // Check multiple times as autofill timing varies across browsers
201
+ const timers = [100, 500, 1000, 2000].map(ms => setTimeout(sync, ms));
202
+ return () => timers.forEach(clearTimeout);
203
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
204
+
205
+ // Switch to full-name screen when host app signals unregistered email
206
+ useEffect(() => {
207
+ if (codeError && view === 'verifycode') {
208
+ setOtpDigits(['', '', '', '', '', '']);
209
+ setView('getcodewithname');
210
+ }
211
+ }, [codeError]); // eslint-disable-line react-hooks/exhaustive-deps
212
+
188
213
  const loading = externalLoading ?? internalLoading;
189
214
  const error = externalError ?? internalError;
190
215
  const canSubmit = email && password && !loading;
@@ -204,7 +229,7 @@ export default function LoginPage({
204
229
  const next = [...otpDigits];
205
230
  next[index] = digit;
206
231
  setOtpDigits(next);
207
- if (digit && index < 3) otpRefs.current[index + 1]?.focus();
232
+ if (digit && index < 5) otpRefs.current[index + 1]?.focus();
208
233
  };
209
234
 
210
235
  const handleOtpKeyDown = (index, e) => {
@@ -470,7 +495,7 @@ export default function LoginPage({
470
495
  onKeyDown={(e) => handleOtpKeyDown(i, e)}
471
496
  autoFocus={i === 0}
472
497
  style={{
473
- width: 72, height: 72, textAlign: 'center', fontSize: 24, fontWeight: 600,
498
+ width: 56, height: 56, textAlign: 'center', fontSize: 20, fontWeight: 600,
474
499
  fontFamily: FF, color: 'var(--color-text)', border: '1px solid var(--color-input-border)',
475
500
  borderRadius: 8, outline: 'none', background: 'white', boxSizing: 'border-box',
476
501
  transition: 'border-color 0.15s, box-shadow 0.15s', caretColor: GREEN,
@@ -491,7 +516,7 @@ export default function LoginPage({
491
516
  </>
492
517
 
493
518
  ) : view === 'getcode' ? (
494
- /* ── Get Code ── */
519
+ /* ── Get Code (original — email only) ── */
495
520
  <>
496
521
  <ViewHeading title="Get code" subtitle="We'll send a one time pass-code to your email" />
497
522
  <div style={{ display: 'flex', flexDirection: 'column', width: '100%', gap: 16 }}>
@@ -501,7 +526,7 @@ export default function LoginPage({
501
526
  style={inputBase} onFocus={focusGreen} onBlur={blurGray} />
502
527
  </Field>
503
528
 
504
- <GreenButton onClick={() => { setVerifyEmail(codeEmail); setOtpDigits(['', '', '', '']); setView('verifycode'); onOneTimeCode?.(codeEmail); }} disabled={!codeEmail}>
529
+ <GreenButton onClick={() => { setVerifyEmail(codeEmail); setOtpDigits(['', '', '', '', '', '']); setView('verifycode'); onOneTimeCode?.(codeEmail); }} disabled={!codeEmail}>
505
530
  Send One-time Code
506
531
  </GreenButton>
507
532
  <Divider />
@@ -510,6 +535,53 @@ export default function LoginPage({
510
535
  </div>
511
536
  </>
512
537
 
538
+ ) : view === 'getcodewithname' ? (
539
+ /* ── Get Code with Full Name (unregistered email) ── */
540
+ <>
541
+ <ViewHeading title="Complete your sign in" subtitle="We’ve sent a one-time code to your email. Please enter the code and your full name to sign in" />
542
+ <div style={{ display: 'flex', flexDirection: 'column', width: '100%', gap: 16 }}>
543
+
544
+ <Field label="Email" gap={12}>
545
+ <input type="email" value={codeEmail} onChange={(e) => setCodeEmail(e.target.value)}
546
+ placeholder="workemail@example.com" autoComplete="email"
547
+ style={inputBase} onFocus={focusGreen} onBlur={blurGray} />
548
+ </Field>
549
+
550
+ <Field label="Full Name" gap={12}>
551
+ <input type="text" value={codeFullName} onChange={(e) => setCodeFullName(e.target.value)}
552
+ placeholder="Enter your full name" autoComplete="name"
553
+ style={inputBase} onFocus={focusGreen} onBlur={blurGray} />
554
+ </Field>
555
+
556
+ <Field label="Enter one-time code" gap={12}>
557
+ <div style={{ display: 'flex', gap: 12 }}>
558
+ {otpDigits.map((digit, i) => (
559
+ <input key={i} ref={(el) => { otpRefs.current[i] = el; }}
560
+ type="text" inputMode="numeric" maxLength={1} value={digit}
561
+ onChange={(e) => handleOtpChange(i, e.target.value)}
562
+ onKeyDown={(e) => handleOtpKeyDown(i, e)}
563
+ autoFocus={i === 0}
564
+ style={{
565
+ width: 56, height: 56, textAlign: 'center', fontSize: 20, fontWeight: 600,
566
+ fontFamily: FF, color: 'var(--color-text)', border: '1px solid var(--color-input-border)',
567
+ borderRadius: 8, outline: 'none', background: 'white', boxSizing: 'border-box',
568
+ transition: 'border-color 0.15s, box-shadow 0.15s', caretColor: GREEN,
569
+ }}
570
+ onFocus={focusGreen} onBlur={blurGray}
571
+ />
572
+ ))}
573
+ </div>
574
+ </Field>
575
+
576
+ <GreenButton onClick={() => { setVerifyEmail(codeEmail); onOneTimeCode?.(codeEmail, codeFullName, otpDigits.join('')); }} disabled={!codeEmail || !codeFullName || otpDigits.some(d => !d)}>
577
+ Verify & Sign In
578
+ </GreenButton>
579
+ <Divider />
580
+ <NavRow text="I have my password" linkText="Back to Sign in" onClick={() => { setView('signin'); onGoToLogin?.(); }} />
581
+ <TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={40} />
582
+ </div>
583
+ </>
584
+
513
585
  ) : view === 'resetpassword' ? (
514
586
  /* ── Reset Password ── */
515
587
  <>
@@ -539,7 +611,7 @@ export default function LoginPage({
539
611
 
540
612
  <form onSubmit={handleSubmit} style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 16 }}>
541
613
  <Field label="Email" gap={12}>
542
- <input type="email" value={email} onChange={(e) => setEmail(e.target.value)}
614
+ <input ref={emailRef} type="email" value={email} onChange={(e) => setEmail(e.target.value)}
543
615
  placeholder="workemail@example.com" required autoComplete="email"
544
616
  style={{ ...inputBase, border: `1px solid ${error ? 'var(--color-text)' : 'var(--color-input-border)'}` }}
545
617
  onFocus={focusErr} onBlur={blurErr} />
@@ -547,7 +619,7 @@ export default function LoginPage({
547
619
 
548
620
  <Field label="Password" gap={12}>
549
621
  <div style={{ position: 'relative' }}>
550
- <input type={showPassword ? 'text' : 'password'} value={password}
622
+ <input ref={passwordRef} type={showPassword ? 'text' : 'password'} value={password}
551
623
  onChange={(e) => setPassword(e.target.value)}
552
624
  placeholder="Enter password" required autoComplete="current-password"
553
625
  style={{ ...inputBase, padding: '10px 44px 10px 16px', border: `1px solid ${error ? 'var(--color-text)' : 'var(--color-input-border)'}` }}
@@ -588,7 +660,7 @@ export default function LoginPage({
588
660
  </div>
589
661
 
590
662
  <NavRow text="Not a member yet?" linkText="Sign Up" onClick={() => { setView('signup'); onSignUp?.(); }} />
591
- <TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={6} />
663
+ <TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={12} />
592
664
  </div>
593
665
  </div>
594
666
  )}