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/dist/index.cjs2.js +2 -2
- package/dist/index.cjs2.js.map +1 -1
- package/dist/index.es2.js +988 -900
- package/dist/index.es2.js.map +1 -1
- package/package.json +1 -1
- package/src/components/login/LoginPage.jsx +80 -8
package/package.json
CHANGED
|
@@ -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 <
|
|
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:
|
|
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={
|
|
663
|
+
<TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={12} />
|
|
592
664
|
</div>
|
|
593
665
|
</div>
|
|
594
666
|
)}
|