chordia-ui 3.2.1 → 3.2.3
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/README.md +61 -0
- package/dist/index.cjs2.js +2 -2
- package/dist/index.cjs2.js.map +1 -1
- package/dist/index.es2.js +903 -833
- package/dist/index.es2.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/login/LoginPage.jsx +145 -15
- package/src/components/onboarding/ConnectData.jsx +89 -0
- package/src/components/onboarding/GettingStarted.jsx +520 -0
- package/src/components/onboarding/UploadEvaluate.jsx +255 -0
- package/src/components/onboarding/UploadInteraction.jsx +186 -0
- package/src/tokens/colors.css +13 -0
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--font-sans: "Averta", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-display: "Tomato Grotesk", "Averta", ui-sans-serif, system-ui, sans-serif;--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--default-font-family: var(--font-sans);--default-mono-font-family: var(--font-mono);--bg-chordia: #F4F1E6;--paper: rgba(255, 255, 255, .78);--paper-secondary: rgba(255, 255, 255, .55);--paper-elevated: rgba(255, 255, 255, .82);--paper-high: rgba(255, 255, 255, .85);--text-ink: #1E2125;--text-base: rgba(30, 33, 37, .78);--text-strong: rgba(30, 33, 37, .92);--text-muted: rgba(30, 33, 37, .56);--text-faint: rgba(30, 33, 37, .36);--text-subtle: rgba(30, 33, 37, .52);--text-xfaint: rgba(30, 33, 37, .28);--border: rgba(52, 58, 64, .12);--border-hover: rgba(52, 58, 64, .18);--border-strong: rgba(52, 58, 64, .22);--border-subtle: rgba(52, 58, 64, .08);--hover-warm: rgba(231, 212, 162, .12);--hover-warm-strong: rgba(231, 212, 162, .18);--hover-warm-subtle: rgba(231, 212, 162, .08);--hover-cool: rgba(210, 220, 235, .18);--focus: rgba(231, 212, 162, .55);--state-present: #25A372;--state-absent: rgba(30, 33, 37, .28);--state-unknown: #E7BF33;--rail-compliance: #C98A5A;--rail-orange: #C98A5A;--rail-tone: #9B7AA8;--rail-purple: #9B7AA8;--rail-discovery: #5E88B0;--rail-blue: #5E88B0;--rail-outcome: #6B7C93;--rail-slate: #6B7C93;--rail-signal-churn: #D17B6B;--rail-coral: #D17B6B;--rail-signal-upsell: #7BA89D;--rail-teal: #B8976A;--rail-signal-satisfaction: #9B8E6F;--rail-olive: #9B8E6F;--rail-quality: #8A9BAF;--card-customer: rgba(94, 136, 176, .06);--card-agent: var(--paper-elevated);--card-assistant: rgba(155, 122, 168, .05);--rail-width-thin: 4px;--rail-width: 5px;--rail-width-thick: 6px;--deviation-dot-size: 6px;--deviation-gap: 3px;--timestamp-bg: rgba(255, 255, 255, .7);--timestamp-border: rgba(52, 58, 64, .16);--bar-h: 3px;--bar-w: 45px;--bar-bg: rgba(30, 33, 37, .12);--bar-fill: rgba(30, 33, 37, .45);--tooltip-bg: rgba(30, 33, 37, .95);--tooltip-text: rgba(255, 255, 255, .95);--background: var(--bg-chordia);--foreground: var(--text-ink);--card: var(--paper-elevated);--card-foreground: var(--text-ink);--popover: var(--paper-high);--popover-foreground: var(--text-ink);--primary: #030213;--primary-foreground: #ffffff;--secondary: rgba(249, 250, 251, 1);--secondary-foreground: #030213;--accent: rgba(233, 235, 239, 1);--accent-foreground: #030213;--destructive: #C98A5A;--destructive-foreground: #ffffff;--input: transparent;--input-background: rgba(243, 243, 245, 1);--switch-background: rgba(203, 206, 212, 1);--ring: rgba(113, 113, 122, .5);--chart-1: #25A372;--chart-2: #44C090;--chart-3: #B7D89E;--chart-4: #E7BF33;--chart-5: #C98A5A;--sidebar: var(--paper-high);--sidebar-foreground: var(--text-ink);--sidebar-primary: #030213;--sidebar-primary-foreground: #ffffff;--sidebar-accent: var(--hover-warm-subtle);--sidebar-accent-foreground: var(--text-ink);--sidebar-border: var(--border);--sidebar-ring: var(--ring);--bg: var(--bg-chordia);--paper2: var(--paper-secondary);--ink: var(--text-ink);--text: var(--text-base);--muted: var(--text-muted);--faint: var(--text-faint);--b1: var(--border);--b2: var(--border-hover);--warm: var(--hover-warm);--cool: var(--hover-cool);--present: var(--state-present);--absent: var(--state-absent);--unknown: var(--state-unknown);--s1: var(--shadow-lg);--s2: var(--shadow-md);--r1: var(--radius-lg);--r2: var(--radius-md);--font-size: .875rem;--color-green: #00A66E;--color-green-hover: #009160;--color-green-disabled: #80d3b7;--color-green-ring: rgba(0, 166, 110, .12);--surface-dark: #1D1E20;--surface-card-dark: linear-gradient(180deg, #48473B 0%, #26261F 100%);--surface-card-dark-border: #333A42;--color-card-icon: #000000;--color-card-text: #ffffff;--color-text: #2E3236;--color-text-secondary: #808183;--color-text-inverse: #E8E6E1;--color-text-overlay: rgba(255, 255, 255, .5);--color-input-border: #ACADAD;--color-divider: #E5E7EB;--color-error: #E15448;--color-error-border: #ED8077;--color-error-ring: rgba(237, 128, 119, .12);--color-error-bg: #F3F7F7;--gradient-card-active: linear-gradient(160deg, #4A5330 0%, #252C14 100%);--gradient-card-inactive: linear-gradient(160deg, rgba(74, 83, 48, .25) 0%, rgba(37, 44, 20, .35) 100%)}.custom-thin-scrollbar-library::-webkit-scrollbar{width:4px;background:transparent;border-radius:2px}.custom-thin-scrollbar-library::-webkit-scrollbar-track{background:transparent}.custom-thin-scrollbar-library::-webkit-scrollbar-thumb{background:rgb(180,178,178);border-radius:2px}.custom-thin-scrollbar-library::-webkit-scrollbar-thumb:hover{background:var(--border-hover)}.custom-thin-scrollbar-library-hidden::-webkit-scrollbar{width:0px;background:transparent}.custom-thin-scrollbar-library-hidden::-webkit-scrollbar-track{background:transparent}.custom-thin-scrollbar-library-hidden::-webkit-scrollbar-thumb{background:transparent}.custom-thin-scrollbar-hidden{scrollbar-width:none;-ms-overflow-style:none}.custom-thin-scrollbar-ultra::-webkit-scrollbar{width:2px;background:transparent}.custom-thin-scrollbar-ultra::-webkit-scrollbar-track{background:transparent}.custom-thin-scrollbar-ultra::-webkit-scrollbar-thumb{background:var(--border);border-radius:1px}.custom-thin-scrollbar-ultra::-webkit-scrollbar-thumb:hover{background:var(--border-hover)}.custom-thin-scrollbar-ultra{scrollbar-width:thin;scrollbar-color:var(--border) transparent}
|
|
1
|
+
:root{--font-sans: "Averta", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-display: "Tomato Grotesk", "Averta", ui-sans-serif, system-ui, sans-serif;--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--default-font-family: var(--font-sans);--default-mono-font-family: var(--font-mono);--bg-chordia: #F4F1E6;--paper: rgba(255, 255, 255, .78);--paper-secondary: rgba(255, 255, 255, .55);--paper-elevated: rgba(255, 255, 255, .82);--paper-high: rgba(255, 255, 255, .85);--text-ink: #1E2125;--text-base: rgba(30, 33, 37, .78);--text-strong: rgba(30, 33, 37, .92);--text-muted: rgba(30, 33, 37, .56);--text-faint: rgba(30, 33, 37, .36);--text-subtle: rgba(30, 33, 37, .52);--text-xfaint: rgba(30, 33, 37, .28);--border: rgba(52, 58, 64, .12);--border-hover: rgba(52, 58, 64, .18);--border-strong: rgba(52, 58, 64, .22);--border-subtle: rgba(52, 58, 64, .08);--hover-warm: rgba(231, 212, 162, .12);--hover-warm-strong: rgba(231, 212, 162, .18);--hover-warm-subtle: rgba(231, 212, 162, .08);--hover-cool: rgba(210, 220, 235, .18);--focus: rgba(231, 212, 162, .55);--state-present: #25A372;--state-absent: rgba(30, 33, 37, .28);--state-unknown: #E7BF33;--rail-compliance: #C98A5A;--rail-orange: #C98A5A;--rail-tone: #9B7AA8;--rail-purple: #9B7AA8;--rail-discovery: #5E88B0;--rail-blue: #5E88B0;--rail-outcome: #6B7C93;--rail-slate: #6B7C93;--rail-signal-churn: #D17B6B;--rail-coral: #D17B6B;--rail-signal-upsell: #7BA89D;--rail-teal: #B8976A;--rail-signal-satisfaction: #9B8E6F;--rail-olive: #9B8E6F;--rail-quality: #8A9BAF;--card-customer: rgba(94, 136, 176, .06);--card-agent: var(--paper-elevated);--card-assistant: rgba(155, 122, 168, .05);--rail-width-thin: 4px;--rail-width: 5px;--rail-width-thick: 6px;--deviation-dot-size: 6px;--deviation-gap: 3px;--timestamp-bg: rgba(255, 255, 255, .7);--timestamp-border: rgba(52, 58, 64, .16);--bar-h: 3px;--bar-w: 45px;--bar-bg: rgba(30, 33, 37, .12);--bar-fill: rgba(30, 33, 37, .45);--tooltip-bg: rgba(30, 33, 37, .95);--tooltip-text: rgba(255, 255, 255, .95);--background: var(--bg-chordia);--foreground: var(--text-ink);--card: var(--paper-elevated);--card-foreground: var(--text-ink);--popover: var(--paper-high);--popover-foreground: var(--text-ink);--primary: #030213;--primary-foreground: #ffffff;--secondary: rgba(249, 250, 251, 1);--secondary-foreground: #030213;--accent: rgba(233, 235, 239, 1);--accent-foreground: #030213;--destructive: #C98A5A;--destructive-foreground: #ffffff;--input: transparent;--input-background: rgba(243, 243, 245, 1);--switch-background: rgba(203, 206, 212, 1);--ring: rgba(113, 113, 122, .5);--chart-1: #25A372;--chart-2: #44C090;--chart-3: #B7D89E;--chart-4: #E7BF33;--chart-5: #C98A5A;--sidebar: var(--paper-high);--sidebar-foreground: var(--text-ink);--sidebar-primary: #030213;--sidebar-primary-foreground: #ffffff;--sidebar-accent: var(--hover-warm-subtle);--sidebar-accent-foreground: var(--text-ink);--sidebar-border: var(--border);--sidebar-ring: var(--ring);--bg: var(--bg-chordia);--paper2: var(--paper-secondary);--ink: var(--text-ink);--text: var(--text-base);--muted: var(--text-muted);--faint: var(--text-faint);--b1: var(--border);--b2: var(--border-hover);--warm: var(--hover-warm);--cool: var(--hover-cool);--present: var(--state-present);--absent: var(--state-absent);--unknown: var(--state-unknown);--s1: var(--shadow-lg);--s2: var(--shadow-md);--r1: var(--radius-lg);--r2: var(--radius-md);--font-size: .875rem;--color-green: #00A66E;--color-green-hover: #009160;--color-green-disabled: #80d3b7;--color-green-ring: rgba(0, 166, 110, .12);--surface-dark: #1D1E20;--surface-card-dark: linear-gradient(180deg, #48473B 0%, #26261F 100%);--surface-card-dark-border: #333A42;--color-card-icon: #000000;--color-card-text: #ffffff;--color-text: #2E3236;--color-text-secondary: #808183;--color-text-inverse: #E8E6E1;--color-text-overlay: rgba(255, 255, 255, .5);--color-input-border: #ACADAD;--color-divider: #E5E7EB;--color-error: #E15448;--color-error-border: #ED8077;--color-error-ring: rgba(237, 128, 119, .12);--color-error-bg: #F3F7F7;--grey-white: #FFFFFF;--grey-absent: #D9D9D9;--grey-muted: #BDBDBD;--grey-strong: var(--color-text);--focus-2: #FAF5E9;--gradient-card-active: linear-gradient(160deg, #4A5330 0%, #252C14 100%);--gradient-card-inactive: linear-gradient(160deg, rgba(74, 83, 48, .25) 0%, rgba(37, 44, 20, .35) 100%)}.custom-thin-scrollbar-library::-webkit-scrollbar{width:4px;background:transparent;border-radius:2px}.custom-thin-scrollbar-library::-webkit-scrollbar-track{background:transparent}.custom-thin-scrollbar-library::-webkit-scrollbar-thumb{background:rgb(180,178,178);border-radius:2px}.custom-thin-scrollbar-library::-webkit-scrollbar-thumb:hover{background:var(--border-hover)}.custom-thin-scrollbar-library-hidden::-webkit-scrollbar{width:0px;background:transparent}.custom-thin-scrollbar-library-hidden::-webkit-scrollbar-track{background:transparent}.custom-thin-scrollbar-library-hidden::-webkit-scrollbar-thumb{background:transparent}.custom-thin-scrollbar-hidden{scrollbar-width:none;-ms-overflow-style:none}.custom-thin-scrollbar-ultra::-webkit-scrollbar{width:2px;background:transparent}.custom-thin-scrollbar-ultra::-webkit-scrollbar-track{background:transparent}.custom-thin-scrollbar-ultra::-webkit-scrollbar-thumb{background:var(--border);border-radius:1px}.custom-thin-scrollbar-ultra::-webkit-scrollbar-thumb:hover{background:var(--border-hover)}.custom-thin-scrollbar-ultra{scrollbar-width:thin;scrollbar-color:var(--border) transparent}
|
package/package.json
CHANGED
|
@@ -162,6 +162,8 @@ export default function LoginPage({
|
|
|
162
162
|
}) {
|
|
163
163
|
const [email, setEmail] = useState('');
|
|
164
164
|
const [password, setPassword] = useState('');
|
|
165
|
+
const [emailAutofilled, setEmailAutofilled] = useState(false);
|
|
166
|
+
const [passwordAutofilled, setPasswordAutofilled] = useState(false);
|
|
165
167
|
const [showPassword, setShowPassword] = useState(false);
|
|
166
168
|
const [internalLoading, setInternalLoading] = useState(false);
|
|
167
169
|
const [internalError, setInternalError] = useState(null);
|
|
@@ -191,17 +193,88 @@ export default function LoginPage({
|
|
|
191
193
|
const emailRef = useRef(null);
|
|
192
194
|
const passwordRef = useRef(null);
|
|
193
195
|
useEffect(() => {
|
|
196
|
+
const isAutofilled = (el) => {
|
|
197
|
+
if (!el) return false;
|
|
198
|
+
try {
|
|
199
|
+
return el.matches(':-webkit-autofill, :autofill');
|
|
200
|
+
} catch {
|
|
201
|
+
// Some browsers may throw for unsupported pseudo-class selectors.
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
194
206
|
const sync = () => {
|
|
195
207
|
const e = emailRef.current;
|
|
196
208
|
const p = passwordRef.current;
|
|
197
|
-
if (e && e.value
|
|
198
|
-
if (p && p.value
|
|
209
|
+
if (e && e.value) setEmail(e.value);
|
|
210
|
+
if (p && p.value) setPassword(p.value);
|
|
211
|
+
setEmailAutofilled(isAutofilled(e));
|
|
212
|
+
setPasswordAutofilled(isAutofilled(p));
|
|
213
|
+
};
|
|
214
|
+
// Poll for autofill values at multiple intervals
|
|
215
|
+
const timers = [100, 300, 600, 1000, 2000].map(ms => setTimeout(sync, ms));
|
|
216
|
+
|
|
217
|
+
// Detect Chrome/Safari autofill via the :-webkit-autofill animation
|
|
218
|
+
const handleAnimationStart = (e) => {
|
|
219
|
+
if (e.animationName === 'onAutoFillStart' || e.target.matches(':-webkit-autofill')) {
|
|
220
|
+
setTimeout(sync, 50);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const eEl = emailRef.current;
|
|
224
|
+
const pEl = passwordRef.current;
|
|
225
|
+
eEl?.addEventListener('animationstart', handleAnimationStart);
|
|
226
|
+
pEl?.addEventListener('animationstart', handleAnimationStart);
|
|
227
|
+
// Listen for events commonly used by password managers/autofill
|
|
228
|
+
const onInput = () => setTimeout(sync, 0);
|
|
229
|
+
const onChange = () => setTimeout(sync, 0);
|
|
230
|
+
eEl?.addEventListener('input', onInput);
|
|
231
|
+
pEl?.addEventListener('input', onInput);
|
|
232
|
+
eEl?.addEventListener('change', onChange);
|
|
233
|
+
pEl?.addEventListener('change', onChange);
|
|
234
|
+
|
|
235
|
+
return () => {
|
|
236
|
+
timers.forEach(clearTimeout);
|
|
237
|
+
eEl?.removeEventListener('animationstart', handleAnimationStart);
|
|
238
|
+
pEl?.removeEventListener('animationstart', handleAnimationStart);
|
|
239
|
+
eEl?.removeEventListener('input', onInput);
|
|
240
|
+
pEl?.removeEventListener('input', onInput);
|
|
241
|
+
eEl?.removeEventListener('change', onChange);
|
|
242
|
+
pEl?.removeEventListener('change', onChange);
|
|
199
243
|
};
|
|
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
244
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
204
245
|
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
const isAutofilled = (el) => {
|
|
248
|
+
if (!el) return false;
|
|
249
|
+
try {
|
|
250
|
+
return el.matches(':-webkit-autofill, :autofill');
|
|
251
|
+
} catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const syncIfNeeded = () => {
|
|
257
|
+
const nextEmail = emailRef.current?.value || '';
|
|
258
|
+
const nextPassword = passwordRef.current?.value || '';
|
|
259
|
+
|
|
260
|
+
if (nextEmail && nextEmail !== email) setEmail(nextEmail);
|
|
261
|
+
if (nextPassword && nextPassword !== password) setPassword(nextPassword);
|
|
262
|
+
setEmailAutofilled(isAutofilled(emailRef.current));
|
|
263
|
+
setPasswordAutofilled(isAutofilled(passwordRef.current));
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Some browsers/password managers update input values without firing events.
|
|
267
|
+
const intervalId = setInterval(syncIfNeeded, 250);
|
|
268
|
+
window.addEventListener('focus', syncIfNeeded);
|
|
269
|
+
document.addEventListener('visibilitychange', syncIfNeeded);
|
|
270
|
+
|
|
271
|
+
return () => {
|
|
272
|
+
clearInterval(intervalId);
|
|
273
|
+
window.removeEventListener('focus', syncIfNeeded);
|
|
274
|
+
document.removeEventListener('visibilitychange', syncIfNeeded);
|
|
275
|
+
};
|
|
276
|
+
}, [email, password]);
|
|
277
|
+
|
|
205
278
|
// Switch to full-name screen when host app signals unregistered email
|
|
206
279
|
useEffect(() => {
|
|
207
280
|
if (codeError && view === 'verifycode') {
|
|
@@ -212,7 +285,12 @@ export default function LoginPage({
|
|
|
212
285
|
|
|
213
286
|
const loading = externalLoading ?? internalLoading;
|
|
214
287
|
const error = externalError ?? internalError;
|
|
215
|
-
const
|
|
288
|
+
const currentEmail = (email || emailRef.current?.value || '').trim();
|
|
289
|
+
const currentPassword = password || passwordRef.current?.value || '';
|
|
290
|
+
const hasEmail = Boolean(currentEmail || emailAutofilled);
|
|
291
|
+
const hasPassword = Boolean(currentPassword || passwordAutofilled);
|
|
292
|
+
const canSubmit = Boolean(hasEmail && hasPassword && !loading);
|
|
293
|
+
const otpError = (view === 'verifycode' || view === 'getcodewithname') ? error : null;
|
|
216
294
|
|
|
217
295
|
// Error-aware focus handlers for sign-in fields
|
|
218
296
|
const focusErr = (e) => {
|
|
@@ -224,8 +302,32 @@ export default function LoginPage({
|
|
|
224
302
|
e.target.style.boxShadow = 'none';
|
|
225
303
|
};
|
|
226
304
|
|
|
305
|
+
const applyOtpFromString = (startIndex, rawValue) => {
|
|
306
|
+
const clean = (rawValue || '').replace(/\D/g, '');
|
|
307
|
+
if (!clean) return;
|
|
308
|
+
|
|
309
|
+
const next = [...otpDigits];
|
|
310
|
+
let lastFilledIndex = startIndex;
|
|
311
|
+
|
|
312
|
+
for (let i = 0; i < clean.length && startIndex + i < next.length; i += 1) {
|
|
313
|
+
next[startIndex + i] = clean[i];
|
|
314
|
+
lastFilledIndex = startIndex + i;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
setOtpDigits(next);
|
|
318
|
+
|
|
319
|
+
const nextIndex = Math.min(lastFilledIndex + 1, next.length - 1);
|
|
320
|
+
otpRefs.current[nextIndex]?.focus();
|
|
321
|
+
};
|
|
322
|
+
|
|
227
323
|
const handleOtpChange = (index, value) => {
|
|
228
|
-
const
|
|
324
|
+
const clean = value.replace(/\D/g, '');
|
|
325
|
+
if (clean.length > 1) {
|
|
326
|
+
applyOtpFromString(index, clean);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const digit = clean.slice(-1);
|
|
229
331
|
const next = [...otpDigits];
|
|
230
332
|
next[index] = digit;
|
|
231
333
|
setOtpDigits(next);
|
|
@@ -238,13 +340,23 @@ export default function LoginPage({
|
|
|
238
340
|
}
|
|
239
341
|
};
|
|
240
342
|
|
|
343
|
+
const handleOtpPaste = (index, e) => {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
const pasted = e.clipboardData?.getData('text') || '';
|
|
346
|
+
applyOtpFromString(index, pasted);
|
|
347
|
+
};
|
|
348
|
+
|
|
241
349
|
const handleSubmit = async (e) => {
|
|
242
350
|
e.preventDefault();
|
|
243
351
|
if (!canSubmit) return;
|
|
352
|
+
// Read directly from DOM if React state missed autofill
|
|
353
|
+
const submitEmail = email || emailRef.current?.value || '';
|
|
354
|
+
const submitPassword = password || passwordRef.current?.value || '';
|
|
355
|
+
if (!submitEmail || !submitPassword) return;
|
|
244
356
|
setInternalError(null);
|
|
245
357
|
setInternalLoading(true);
|
|
246
358
|
try {
|
|
247
|
-
await onLogin?.(
|
|
359
|
+
await onLogin?.(submitEmail, submitPassword);
|
|
248
360
|
} catch (err) {
|
|
249
361
|
setInternalError(err?.message || 'Something went wrong. Please try again.');
|
|
250
362
|
} finally {
|
|
@@ -487,17 +599,18 @@ export default function LoginPage({
|
|
|
487
599
|
|
|
488
600
|
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', gap: 16 }}>
|
|
489
601
|
<Field label="Enter one-time code" gap={12}>
|
|
490
|
-
<div style={{ display: 'flex', gap:
|
|
602
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
491
603
|
{otpDigits.map((digit, i) => (
|
|
492
604
|
<input key={i} ref={(el) => { otpRefs.current[i] = el; }}
|
|
493
605
|
type="text" inputMode="numeric" maxLength={1} value={digit}
|
|
494
606
|
onChange={(e) => handleOtpChange(i, e.target.value)}
|
|
495
607
|
onKeyDown={(e) => handleOtpKeyDown(i, e)}
|
|
608
|
+
onPaste={(e) => handleOtpPaste(i, e)}
|
|
496
609
|
autoFocus={i === 0}
|
|
497
610
|
style={{
|
|
498
|
-
|
|
611
|
+
flex: 1, minWidth: 0, height: 40, textAlign: 'center', fontSize: 16, fontWeight: 600,
|
|
499
612
|
fontFamily: FF, color: 'var(--color-text)', border: '1px solid var(--color-input-border)',
|
|
500
|
-
borderRadius:
|
|
613
|
+
borderRadius: 4, outline: 'none', background: 'white', boxSizing: 'border-box',
|
|
501
614
|
transition: 'border-color 0.15s, box-shadow 0.15s', caretColor: GREEN,
|
|
502
615
|
}}
|
|
503
616
|
onFocus={focusGreen} onBlur={blurGray}
|
|
@@ -509,6 +622,14 @@ export default function LoginPage({
|
|
|
509
622
|
<GreenButton onClick={() => onVerifyCode?.(verifyEmail, otpDigits.join(''))} disabled={otpDigits.some(d => !d)}>
|
|
510
623
|
Verify Code
|
|
511
624
|
</GreenButton>
|
|
625
|
+
{otpError && (
|
|
626
|
+
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', padding: 8, gap: 6, width: '100%', boxSizing: 'border-box', background: 'var(--color-error-bg)', borderRadius: 5, fontSize: 15, fontWeight: 400, lineHeight: '22px', color: 'var(--color-text)', fontFamily: FF }}>
|
|
627
|
+
<svg width="17" height="17" viewBox="0 0 24 24" fill="var(--color-text)" style={{ flexShrink: 0 }}>
|
|
628
|
+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
|
|
629
|
+
</svg>
|
|
630
|
+
{otpError}
|
|
631
|
+
</div>
|
|
632
|
+
)}
|
|
512
633
|
<Divider />
|
|
513
634
|
<NavRow text="Not received yet?" linkText="Resend" onClick={() => onResendCode?.(verifyEmail)} />
|
|
514
635
|
<TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={40} />
|
|
@@ -554,17 +675,18 @@ export default function LoginPage({
|
|
|
554
675
|
</Field>
|
|
555
676
|
|
|
556
677
|
<Field label="Enter one-time code" gap={12}>
|
|
557
|
-
<div style={{ display: 'flex', gap:
|
|
678
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
558
679
|
{otpDigits.map((digit, i) => (
|
|
559
680
|
<input key={i} ref={(el) => { otpRefs.current[i] = el; }}
|
|
560
681
|
type="text" inputMode="numeric" maxLength={1} value={digit}
|
|
561
682
|
onChange={(e) => handleOtpChange(i, e.target.value)}
|
|
562
683
|
onKeyDown={(e) => handleOtpKeyDown(i, e)}
|
|
684
|
+
onPaste={(e) => handleOtpPaste(i, e)}
|
|
563
685
|
autoFocus={i === 0}
|
|
564
686
|
style={{
|
|
565
|
-
|
|
687
|
+
flex: 1, minWidth: 0, height: 40, textAlign: 'center', fontSize: 16, fontWeight: 600,
|
|
566
688
|
fontFamily: FF, color: 'var(--color-text)', border: '1px solid var(--color-input-border)',
|
|
567
|
-
borderRadius:
|
|
689
|
+
borderRadius: 4, outline: 'none', background: 'white', boxSizing: 'border-box',
|
|
568
690
|
transition: 'border-color 0.15s, box-shadow 0.15s', caretColor: GREEN,
|
|
569
691
|
}}
|
|
570
692
|
onFocus={focusGreen} onBlur={blurGray}
|
|
@@ -576,6 +698,14 @@ export default function LoginPage({
|
|
|
576
698
|
<GreenButton onClick={() => { setVerifyEmail(codeEmail); onOneTimeCode?.(codeEmail, codeFullName, otpDigits.join('')); }} disabled={!codeEmail || !codeFullName || otpDigits.some(d => !d)}>
|
|
577
699
|
Verify & Sign In
|
|
578
700
|
</GreenButton>
|
|
701
|
+
{otpError && (
|
|
702
|
+
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', padding: 8, gap: 6, width: '100%', boxSizing: 'border-box', background: 'var(--color-error-bg)', borderRadius: 5, fontSize: 15, fontWeight: 400, lineHeight: '22px', color: 'var(--color-text)', fontFamily: FF }}>
|
|
703
|
+
<svg width="17" height="17" viewBox="0 0 24 24" fill="var(--color-text)" style={{ flexShrink: 0 }}>
|
|
704
|
+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
|
|
705
|
+
</svg>
|
|
706
|
+
{otpError}
|
|
707
|
+
</div>
|
|
708
|
+
)}
|
|
579
709
|
<Divider />
|
|
580
710
|
<NavRow text="I have my password" linkText="Back to Sign in" onClick={() => { setView('signin'); onGoToLogin?.(); }} />
|
|
581
711
|
<TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={40} />
|
|
@@ -660,7 +790,7 @@ export default function LoginPage({
|
|
|
660
790
|
</div>
|
|
661
791
|
|
|
662
792
|
<NavRow text="Not a member yet?" linkText="Sign Up" onClick={() => { setView('signup'); onSignUp?.(); }} />
|
|
663
|
-
<TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={
|
|
793
|
+
<TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={16} />
|
|
664
794
|
</div>
|
|
665
795
|
</div>
|
|
666
796
|
)}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import IntegrationCard from '../layout/IntegrationCard';
|
|
2
|
+
|
|
3
|
+
const FF = 'var(--font-sans)';
|
|
4
|
+
|
|
5
|
+
// ─── Styles ───
|
|
6
|
+
|
|
7
|
+
const containerStyle = {
|
|
8
|
+
fontFamily: FF,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const sectionTitleStyle = {
|
|
12
|
+
fontSize: 20,
|
|
13
|
+
fontWeight: 600,
|
|
14
|
+
fontStyle: 'normal',
|
|
15
|
+
fontFamily: FF,
|
|
16
|
+
color: 'var(--grey-strong)',
|
|
17
|
+
margin: 0,
|
|
18
|
+
lineHeight: 'normal',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const sectionSubtitleStyle = {
|
|
22
|
+
fontSize: 13,
|
|
23
|
+
fontWeight: 400,
|
|
24
|
+
fontStyle: 'normal',
|
|
25
|
+
color: 'var(--color-text-secondary)',
|
|
26
|
+
fontFamily: FF,
|
|
27
|
+
margin: '4px 0 0',
|
|
28
|
+
lineHeight: '140%',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const gridStyle = {
|
|
32
|
+
display: 'grid',
|
|
33
|
+
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
34
|
+
gap: 16,
|
|
35
|
+
marginTop: 24,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// ─── Default Integrations ───
|
|
39
|
+
|
|
40
|
+
const DEFAULT_INTEGRATIONS = [
|
|
41
|
+
{
|
|
42
|
+
providerName: 'Five9',
|
|
43
|
+
description: 'Cloud contact center platform for voice and digital channels',
|
|
44
|
+
status: 'connected',
|
|
45
|
+
railColor: '#5E88B0',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
providerName: 'Twilio Flex',
|
|
49
|
+
description: 'Programmable contact center with custom workflows',
|
|
50
|
+
status: 'available',
|
|
51
|
+
railColor: '#9B7AA8',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
providerName: 'Zoom Phone',
|
|
55
|
+
description: 'Cloud phone system with recording capabilities',
|
|
56
|
+
status: 'coming-soon',
|
|
57
|
+
railColor: '#6B7C93',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// ─── Component ───
|
|
62
|
+
|
|
63
|
+
const ConnectData = ({ integrations = DEFAULT_INTEGRATIONS, onConfigure }) => {
|
|
64
|
+
return (
|
|
65
|
+
<div style={containerStyle}>
|
|
66
|
+
<h2 style={sectionTitleStyle}>Connect Data Source</h2>
|
|
67
|
+
<p style={sectionSubtitleStyle}>
|
|
68
|
+
Choose your preferred platforms to synchronise data.
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
<div style={gridStyle}>
|
|
72
|
+
{integrations.map((integration) => (
|
|
73
|
+
<IntegrationCard
|
|
74
|
+
key={integration.providerName}
|
|
75
|
+
providerName={integration.providerName}
|
|
76
|
+
description={integration.description}
|
|
77
|
+
status={integration.status}
|
|
78
|
+
railColor={integration.railColor}
|
|
79
|
+
logoUrl={integration.logoUrl}
|
|
80
|
+
icon={integration.icon}
|
|
81
|
+
onConfigure={() => onConfigure?.(integration)}
|
|
82
|
+
/>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default ConnectData;
|