chordia-ui 3.2.2 → 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 +869 -811
- 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 +107 -5
- 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);
|
|
@@ -190,13 +192,24 @@ export default function LoginPage({
|
|
|
190
192
|
// Sync React state with browser autofill (autofill doesn't fire onChange)
|
|
191
193
|
const emailRef = useRef(null);
|
|
192
194
|
const passwordRef = useRef(null);
|
|
193
|
-
const [autofilled, setAutofilled] = useState(false);
|
|
194
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
|
+
|
|
195
206
|
const sync = () => {
|
|
196
207
|
const e = emailRef.current;
|
|
197
208
|
const p = passwordRef.current;
|
|
198
209
|
if (e && e.value) setEmail(e.value);
|
|
199
210
|
if (p && p.value) setPassword(p.value);
|
|
211
|
+
setEmailAutofilled(isAutofilled(e));
|
|
212
|
+
setPasswordAutofilled(isAutofilled(p));
|
|
200
213
|
};
|
|
201
214
|
// Poll for autofill values at multiple intervals
|
|
202
215
|
const timers = [100, 300, 600, 1000, 2000].map(ms => setTimeout(sync, ms));
|
|
@@ -204,7 +217,6 @@ export default function LoginPage({
|
|
|
204
217
|
// Detect Chrome/Safari autofill via the :-webkit-autofill animation
|
|
205
218
|
const handleAnimationStart = (e) => {
|
|
206
219
|
if (e.animationName === 'onAutoFillStart' || e.target.matches(':-webkit-autofill')) {
|
|
207
|
-
setAutofilled(true);
|
|
208
220
|
setTimeout(sync, 50);
|
|
209
221
|
}
|
|
210
222
|
};
|
|
@@ -212,10 +224,13 @@ export default function LoginPage({
|
|
|
212
224
|
const pEl = passwordRef.current;
|
|
213
225
|
eEl?.addEventListener('animationstart', handleAnimationStart);
|
|
214
226
|
pEl?.addEventListener('animationstart', handleAnimationStart);
|
|
215
|
-
//
|
|
227
|
+
// Listen for events commonly used by password managers/autofill
|
|
216
228
|
const onInput = () => setTimeout(sync, 0);
|
|
229
|
+
const onChange = () => setTimeout(sync, 0);
|
|
217
230
|
eEl?.addEventListener('input', onInput);
|
|
218
231
|
pEl?.addEventListener('input', onInput);
|
|
232
|
+
eEl?.addEventListener('change', onChange);
|
|
233
|
+
pEl?.addEventListener('change', onChange);
|
|
219
234
|
|
|
220
235
|
return () => {
|
|
221
236
|
timers.forEach(clearTimeout);
|
|
@@ -223,9 +238,43 @@ export default function LoginPage({
|
|
|
223
238
|
pEl?.removeEventListener('animationstart', handleAnimationStart);
|
|
224
239
|
eEl?.removeEventListener('input', onInput);
|
|
225
240
|
pEl?.removeEventListener('input', onInput);
|
|
241
|
+
eEl?.removeEventListener('change', onChange);
|
|
242
|
+
pEl?.removeEventListener('change', onChange);
|
|
226
243
|
};
|
|
227
244
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
228
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
|
+
|
|
229
278
|
// Switch to full-name screen when host app signals unregistered email
|
|
230
279
|
useEffect(() => {
|
|
231
280
|
if (codeError && view === 'verifycode') {
|
|
@@ -236,7 +285,12 @@ export default function LoginPage({
|
|
|
236
285
|
|
|
237
286
|
const loading = externalLoading ?? internalLoading;
|
|
238
287
|
const error = externalError ?? internalError;
|
|
239
|
-
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;
|
|
240
294
|
|
|
241
295
|
// Error-aware focus handlers for sign-in fields
|
|
242
296
|
const focusErr = (e) => {
|
|
@@ -248,8 +302,32 @@ export default function LoginPage({
|
|
|
248
302
|
e.target.style.boxShadow = 'none';
|
|
249
303
|
};
|
|
250
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
|
+
|
|
251
323
|
const handleOtpChange = (index, value) => {
|
|
252
|
-
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);
|
|
253
331
|
const next = [...otpDigits];
|
|
254
332
|
next[index] = digit;
|
|
255
333
|
setOtpDigits(next);
|
|
@@ -262,6 +340,12 @@ export default function LoginPage({
|
|
|
262
340
|
}
|
|
263
341
|
};
|
|
264
342
|
|
|
343
|
+
const handleOtpPaste = (index, e) => {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
const pasted = e.clipboardData?.getData('text') || '';
|
|
346
|
+
applyOtpFromString(index, pasted);
|
|
347
|
+
};
|
|
348
|
+
|
|
265
349
|
const handleSubmit = async (e) => {
|
|
266
350
|
e.preventDefault();
|
|
267
351
|
if (!canSubmit) return;
|
|
@@ -521,6 +605,7 @@ export default function LoginPage({
|
|
|
521
605
|
type="text" inputMode="numeric" maxLength={1} value={digit}
|
|
522
606
|
onChange={(e) => handleOtpChange(i, e.target.value)}
|
|
523
607
|
onKeyDown={(e) => handleOtpKeyDown(i, e)}
|
|
608
|
+
onPaste={(e) => handleOtpPaste(i, e)}
|
|
524
609
|
autoFocus={i === 0}
|
|
525
610
|
style={{
|
|
526
611
|
flex: 1, minWidth: 0, height: 40, textAlign: 'center', fontSize: 16, fontWeight: 600,
|
|
@@ -537,6 +622,14 @@ export default function LoginPage({
|
|
|
537
622
|
<GreenButton onClick={() => onVerifyCode?.(verifyEmail, otpDigits.join(''))} disabled={otpDigits.some(d => !d)}>
|
|
538
623
|
Verify Code
|
|
539
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
|
+
)}
|
|
540
633
|
<Divider />
|
|
541
634
|
<NavRow text="Not received yet?" linkText="Resend" onClick={() => onResendCode?.(verifyEmail)} />
|
|
542
635
|
<TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={40} />
|
|
@@ -588,6 +681,7 @@ export default function LoginPage({
|
|
|
588
681
|
type="text" inputMode="numeric" maxLength={1} value={digit}
|
|
589
682
|
onChange={(e) => handleOtpChange(i, e.target.value)}
|
|
590
683
|
onKeyDown={(e) => handleOtpKeyDown(i, e)}
|
|
684
|
+
onPaste={(e) => handleOtpPaste(i, e)}
|
|
591
685
|
autoFocus={i === 0}
|
|
592
686
|
style={{
|
|
593
687
|
flex: 1, minWidth: 0, height: 40, textAlign: 'center', fontSize: 16, fontWeight: 600,
|
|
@@ -604,6 +698,14 @@ export default function LoginPage({
|
|
|
604
698
|
<GreenButton onClick={() => { setVerifyEmail(codeEmail); onOneTimeCode?.(codeEmail, codeFullName, otpDigits.join('')); }} disabled={!codeEmail || !codeFullName || otpDigits.some(d => !d)}>
|
|
605
699
|
Verify & Sign In
|
|
606
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
|
+
)}
|
|
607
709
|
<Divider />
|
|
608
710
|
<NavRow text="I have my password" linkText="Back to Sign in" onClick={() => { setView('signin'); onGoToLogin?.(); }} />
|
|
609
711
|
<TermsFooter onTerms={onTerms} onPrivacyPolicy={onPrivacyPolicy} paddingTop={40} />
|
|
@@ -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;
|