@ciromaciel/auth-react 1.1.0 → 1.3.0
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 +19 -7
- package/dist/index.esm.js +320 -31
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +320 -27
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -112,6 +112,28 @@ function forgetAccount(email) {
|
|
|
112
112
|
return next;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Replaces the local copy with the list the worker returned.
|
|
117
|
+
*
|
|
118
|
+
* The worker's list is the one every panel shares, so when it has accounts it
|
|
119
|
+
* wins: an account removed on another panel must not come back from this
|
|
120
|
+
* panel's stale copy. An EMPTY answer does not wipe the local one — it is what
|
|
121
|
+
* a browser that signed in before the shared list existed gets, and those
|
|
122
|
+
* shortcuts are still true.
|
|
123
|
+
*
|
|
124
|
+
* @returns the list to show
|
|
125
|
+
*/
|
|
126
|
+
function adoptRecentAccounts(remote) {
|
|
127
|
+
if (!Array.isArray(remote) || remote.length === 0) return listRecentAccounts();
|
|
128
|
+
const next = remote.filter(account => account && typeof account.email === 'string' && account.email.includes('@')).map(account => ({
|
|
129
|
+
email: normalizeEmail(account.email),
|
|
130
|
+
method: typeof account.method === 'string' && account.method ? account.method : 'code',
|
|
131
|
+
lastUsedAt: Number(account.lastUsedAt) || 0
|
|
132
|
+
})).sort((a, b) => b.lastUsedAt - a.lastUsedAt).slice(0, MAX_RECENT_ACCOUNTS);
|
|
133
|
+
writeRecentAccounts(next);
|
|
134
|
+
return next;
|
|
135
|
+
}
|
|
136
|
+
|
|
115
137
|
/** Records the provider this tab is leaving for. */
|
|
116
138
|
function markSocialDeparture(provider) {
|
|
117
139
|
try {
|
|
@@ -693,6 +715,64 @@ const updateProfile = async data => {
|
|
|
693
715
|
});
|
|
694
716
|
};
|
|
695
717
|
|
|
718
|
+
/*--- Recent accounts ------------------------------------------------------*/
|
|
719
|
+
|
|
720
|
+
/*
|
|
721
|
+
* The server's copy of this browser's sign-in shortcuts.
|
|
722
|
+
*
|
|
723
|
+
* `localStorage` is per origin, so a list kept only there stayed on the panel
|
|
724
|
+
* where the person signed in. The worker keeps it in an HttpOnly cookie on its
|
|
725
|
+
* own host, which every panel of the application reaches — and answers only to
|
|
726
|
+
* the origins the application allows (`routes/recent-accounts.js`).
|
|
727
|
+
*
|
|
728
|
+
* All three fail soft: the shortcuts are a convenience, and a network error or
|
|
729
|
+
* an origin the worker refuses must never cost the sign-in. `null` means "no
|
|
730
|
+
* answer", which the screen reads as "keep what you have".
|
|
731
|
+
*/
|
|
732
|
+
|
|
733
|
+
/** The list for this application, or `null` when the worker did not answer. */
|
|
734
|
+
const fetchRecentAccounts = async () => {
|
|
735
|
+
try {
|
|
736
|
+
const response = await api('/auth/recent-accounts');
|
|
737
|
+
return Array.isArray(response?.items) ? response.items : null;
|
|
738
|
+
} catch {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Records the account of the CURRENT session. The worker reads the email from
|
|
745
|
+
* the session, never from here. `keepalive` because the screen is usually
|
|
746
|
+
* navigating away at this very moment.
|
|
747
|
+
*/
|
|
748
|
+
const saveRecentAccount = async (method = 'code') => {
|
|
749
|
+
try {
|
|
750
|
+
const response = await api('/auth/recent-accounts', {
|
|
751
|
+
method: 'POST',
|
|
752
|
+
body: JSON.stringify({
|
|
753
|
+
method
|
|
754
|
+
}),
|
|
755
|
+
keepalive: true
|
|
756
|
+
});
|
|
757
|
+
return Array.isArray(response?.items) ? response.items : null;
|
|
758
|
+
} catch {
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
/** Takes one account off this browser's list, on every panel. */
|
|
764
|
+
const deleteRecentAccount = async email => {
|
|
765
|
+
try {
|
|
766
|
+
await api(`/auth/recent-accounts/${encodeURIComponent(email)}`, {
|
|
767
|
+
method: 'DELETE',
|
|
768
|
+
keepalive: true
|
|
769
|
+
});
|
|
770
|
+
return true;
|
|
771
|
+
} catch {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
696
776
|
/*--- Social sign-in -------------------------------------------------------*/
|
|
697
777
|
|
|
698
778
|
/**
|
|
@@ -801,6 +881,9 @@ const consumeSocialToken = () => {
|
|
|
801
881
|
if (provider) {
|
|
802
882
|
const email = decodeJWT(token)?.email;
|
|
803
883
|
if (email) rememberAccount(email, provider);
|
|
884
|
+
// The shared copy, so the other panels learn it too. Not awaited: the
|
|
885
|
+
// token is already stored, and the sign-in must not wait on a shortcut.
|
|
886
|
+
saveRecentAccount(provider);
|
|
804
887
|
}
|
|
805
888
|
params.delete('token');
|
|
806
889
|
const rest = params.toString();
|
|
@@ -2520,6 +2603,102 @@ function TermsNotice({
|
|
|
2520
2603
|
});
|
|
2521
2604
|
}
|
|
2522
2605
|
|
|
2606
|
+
/**
|
|
2607
|
+
* What a failed `verify` means for the person, read from the worker's answer.
|
|
2608
|
+
*
|
|
2609
|
+
* The HTTP status carries the case — `code` is `VALIDATION_ERROR` for both a
|
|
2610
|
+
* wrong and an expired code, so it cannot tell them apart:
|
|
2611
|
+
* - 400 with `details.attemptsLeft`: wrong code, the request is still open;
|
|
2612
|
+
* - 400 without it: no open request for this email (already replaced);
|
|
2613
|
+
* - 429: the fifth wrong try destroyed the request;
|
|
2614
|
+
* - 410: the code outlived its minutes, and was destroyed too.
|
|
2615
|
+
*
|
|
2616
|
+
* Anything else — the network, a 500 — is not about the code, and must not
|
|
2617
|
+
* lock the field.
|
|
2618
|
+
*/
|
|
2619
|
+
function describeCodeFailure(error) {
|
|
2620
|
+
if (error?.status === 429) return {
|
|
2621
|
+
kind: 'exhausted',
|
|
2622
|
+
isLocked: true
|
|
2623
|
+
};
|
|
2624
|
+
if (error?.status === 410) return {
|
|
2625
|
+
kind: 'expired',
|
|
2626
|
+
isLocked: true
|
|
2627
|
+
};
|
|
2628
|
+
if (error?.status === 400) {
|
|
2629
|
+
const attemptsLeft = error?.details?.attemptsLeft;
|
|
2630
|
+
return {
|
|
2631
|
+
kind: 'wrong',
|
|
2632
|
+
isLocked: false,
|
|
2633
|
+
attemptsLeft: Number.isInteger(attemptsLeft) ? attemptsLeft : null
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
return {
|
|
2637
|
+
kind: 'other',
|
|
2638
|
+
isLocked: false,
|
|
2639
|
+
message: error?.message || null
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
/**
|
|
2644
|
+
* The notice above the code field.
|
|
2645
|
+
*
|
|
2646
|
+
* It sits ABOVE the field, not under it, and says what to do next — not only
|
|
2647
|
+
* that something failed. The field's own error line was 12px of red under a
|
|
2648
|
+
* cleared input, next to a greyed-out button: it read as a frozen screen.
|
|
2649
|
+
*
|
|
2650
|
+
* The most common cause gets named: a new code invalidates the previous one,
|
|
2651
|
+
* and the person is often reading an older email.
|
|
2652
|
+
*/
|
|
2653
|
+
function CodeFailureNotice({
|
|
2654
|
+
failure,
|
|
2655
|
+
labels
|
|
2656
|
+
}) {
|
|
2657
|
+
if (!failure) return null;
|
|
2658
|
+
const texts = {
|
|
2659
|
+
wrong: {
|
|
2660
|
+
title: labels.wrongCodeTitle || 'Código incorreto',
|
|
2661
|
+
body: [labels.wrongCodeHint || 'Confira o e-mail mais recente: um código novo invalida o anterior.', failure.attemptsLeft === 1 ? labels.lastAttempt || 'Esta é a última tentativa.' : failure.attemptsLeft > 1 ? labels.attemptsLeft ? labels.attemptsLeft(failure.attemptsLeft) : `Restam ${failure.attemptsLeft} tentativas.` : null].filter(Boolean).join(' ')
|
|
2662
|
+
},
|
|
2663
|
+
exhausted: {
|
|
2664
|
+
title: labels.attemptsExhaustedTitle || 'Tentativas esgotadas',
|
|
2665
|
+
body: labels.attemptsExhausted || 'Por segurança, este código foi cancelado. Peça um novo para continuar.'
|
|
2666
|
+
},
|
|
2667
|
+
expired: {
|
|
2668
|
+
title: labels.codeExpiredTitle || 'Código expirado',
|
|
2669
|
+
body: labels.codeExpired || 'O código vale por poucos minutos. Peça um novo para continuar.'
|
|
2670
|
+
},
|
|
2671
|
+
other: {
|
|
2672
|
+
title: labels.codeFailedTitle || 'Não foi possível entrar',
|
|
2673
|
+
body: failure.message || labels.invalidCode || 'Tente de novo em instantes.'
|
|
2674
|
+
}
|
|
2675
|
+
}[failure.kind];
|
|
2676
|
+
return /*#__PURE__*/jsxRuntime.jsx(core.Alert, {
|
|
2677
|
+
color: "red",
|
|
2678
|
+
variant: "light",
|
|
2679
|
+
radius: 0,
|
|
2680
|
+
icon: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconAlertCircle, {
|
|
2681
|
+
size: 18
|
|
2682
|
+
}),
|
|
2683
|
+
title: texts.title
|
|
2684
|
+
/*
|
|
2685
|
+
* `role="alert"` is Mantine's default and is what makes a screen
|
|
2686
|
+
* reader announce the failure without the person moving focus away
|
|
2687
|
+
* from the field they are about to retype in.
|
|
2688
|
+
*/,
|
|
2689
|
+
styles: {
|
|
2690
|
+
root: {
|
|
2691
|
+
border: '1px solid var(--mantine-color-red-2)'
|
|
2692
|
+
}
|
|
2693
|
+
},
|
|
2694
|
+
children: /*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
2695
|
+
size: "sm",
|
|
2696
|
+
lh: 1.45,
|
|
2697
|
+
children: texts.body
|
|
2698
|
+
})
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2523
2702
|
// The OAuth flow's pass-through screen.
|
|
2524
2703
|
//
|
|
2525
2704
|
// The panel is not the destination here: the user is authorizing an MCP client
|
|
@@ -2632,7 +2811,9 @@ function SignIn({
|
|
|
2632
2811
|
// present it again: it is the (email, code) pair the server validates.
|
|
2633
2812
|
const [sentTo, setSentTo] = react.useState(null);
|
|
2634
2813
|
const [code, setCode] = react.useState('');
|
|
2635
|
-
const [
|
|
2814
|
+
const [codeFailure, setCodeFailure] = react.useState(null);
|
|
2815
|
+
const [isCodeResent, setIsCodeResent] = react.useState(false);
|
|
2816
|
+
const codeInputRef = react.useRef(null);
|
|
2636
2817
|
|
|
2637
2818
|
// Read once, on mount: the list only changes through this screen, and
|
|
2638
2819
|
// every change below writes the new list back into state.
|
|
@@ -2641,6 +2822,7 @@ function SignIn({
|
|
|
2641
2822
|
const [isManaging, setIsManaging] = react.useState(false);
|
|
2642
2823
|
const [pickingEmail, setPickingEmail] = react.useState(null);
|
|
2643
2824
|
const isShowingAccounts = recentAccounts && accounts.length > 0 && !isChoosingOther;
|
|
2825
|
+
const isCodeLocked = !!codeFailure?.isLocked;
|
|
2644
2826
|
|
|
2645
2827
|
// Hook that fetches the application's logo
|
|
2646
2828
|
const applicationLogo = useApplicationLogo();
|
|
@@ -2685,20 +2867,64 @@ function SignIn({
|
|
|
2685
2867
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- redirectOrigins enters through the serialized key above
|
|
2686
2868
|
}, [authLoading, user, authenticatedRedirect, handleRedirect, redirectOriginsKey, navigate]);
|
|
2687
2869
|
|
|
2870
|
+
/*
|
|
2871
|
+
* The shared list, from the worker.
|
|
2872
|
+
*
|
|
2873
|
+
* The local copy renders at once; this replaces it when the answer
|
|
2874
|
+
* arrives. That is what makes an account used on the Auth panel appear
|
|
2875
|
+
* on Hoster: `localStorage` never crosses between the two origins.
|
|
2876
|
+
*
|
|
2877
|
+
* If the person has already started typing an email, the list does not
|
|
2878
|
+
* yank the form away from under them — it only feeds the "Contas salvas"
|
|
2879
|
+
* link, one click away.
|
|
2880
|
+
*/
|
|
2881
|
+
react.useEffect(() => {
|
|
2882
|
+
if (!recentAccounts) return;
|
|
2883
|
+
let isActive = true;
|
|
2884
|
+
fetchRecentAccounts().then(remote => {
|
|
2885
|
+
if (!isActive || remote === null) return;
|
|
2886
|
+
const next = adoptRecentAccounts(remote);
|
|
2887
|
+
if (form$1.isDirty()) setIsChoosingOther(true);
|
|
2888
|
+
setAccounts(next);
|
|
2889
|
+
});
|
|
2890
|
+
return () => {
|
|
2891
|
+
isActive = false;
|
|
2892
|
+
};
|
|
2893
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- once per mount, like the local read
|
|
2894
|
+
}, [recentAccounts]);
|
|
2895
|
+
|
|
2688
2896
|
// Step 1 — ask for the code.
|
|
2689
2897
|
const handleRequest = async values => {
|
|
2690
|
-
if (sending) return;
|
|
2898
|
+
if (sending) return false;
|
|
2691
2899
|
try {
|
|
2692
2900
|
await requestCode(values.email);
|
|
2693
2901
|
setSentTo(values.email);
|
|
2694
2902
|
setCode('');
|
|
2695
|
-
|
|
2903
|
+
setCodeFailure(null);
|
|
2696
2904
|
onCodeSent?.(values.email);
|
|
2905
|
+
return true;
|
|
2697
2906
|
} catch (error) {
|
|
2698
|
-
|
|
2907
|
+
// Nothing on the card shows this one: the app's notification is
|
|
2908
|
+
// the only place the person learns the code was not sent.
|
|
2909
|
+
onError?.(error, {
|
|
2910
|
+
step: 'request',
|
|
2911
|
+
isShownOnCard: false
|
|
2912
|
+
});
|
|
2913
|
+
return false;
|
|
2699
2914
|
}
|
|
2700
2915
|
};
|
|
2701
2916
|
|
|
2917
|
+
// A new code, from the code step. The worker replaces the request, so the
|
|
2918
|
+
// attempts start over and the previous code stops working — the notice
|
|
2919
|
+
// says so, or the person keeps typing the one from the older email.
|
|
2920
|
+
const handleResend = async () => {
|
|
2921
|
+
const isSent = await handleRequest({
|
|
2922
|
+
email: sentTo
|
|
2923
|
+
});
|
|
2924
|
+
setIsCodeResent(isSent);
|
|
2925
|
+
if (isSent) codeInputRef.current?.focus();
|
|
2926
|
+
};
|
|
2927
|
+
|
|
2702
2928
|
// Step 1, from the list — the click on a saved account IS the request.
|
|
2703
2929
|
//
|
|
2704
2930
|
// An account that came in through a provider goes back to that provider,
|
|
@@ -2726,6 +2952,9 @@ function SignIn({
|
|
|
2726
2952
|
const handleForget = account => {
|
|
2727
2953
|
const next = forgetAccount(account.email);
|
|
2728
2954
|
setAccounts(next);
|
|
2955
|
+
// On every panel, not just this one. The local removal above already
|
|
2956
|
+
// took it off this screen, so a failure here costs nothing visible.
|
|
2957
|
+
deleteRecentAccount(account.email);
|
|
2729
2958
|
if (next.length === 0) setIsManaging(false);
|
|
2730
2959
|
};
|
|
2731
2960
|
|
|
@@ -2746,14 +2975,18 @@ function SignIn({
|
|
|
2746
2975
|
// signals that navigation was taken over — now as information, not as a
|
|
2747
2976
|
// trap.
|
|
2748
2977
|
const handleVerify = async value => {
|
|
2749
|
-
|
|
2978
|
+
setCodeFailure(null);
|
|
2979
|
+
setIsCodeResent(false);
|
|
2750
2980
|
try {
|
|
2751
2981
|
const result = await verifyCode(sentTo, value);
|
|
2752
2982
|
|
|
2753
2983
|
// Only now, with a session: an email that never received a valid
|
|
2754
2984
|
// code never becomes a suggestion. Written before the redirect,
|
|
2755
2985
|
// which may unmount this screen.
|
|
2756
|
-
if (recentAccounts)
|
|
2986
|
+
if (recentAccounts) {
|
|
2987
|
+
setAccounts(rememberAccount(sentTo, 'code'));
|
|
2988
|
+
saveRecentAccount('code');
|
|
2989
|
+
}
|
|
2757
2990
|
const target = handleRedirect ? getRedirectFromLocation(redirectOrigins) : null;
|
|
2758
2991
|
if (target) applyRedirect(target, navigate);
|
|
2759
2992
|
onSuccess?.(result?.user ?? null, {
|
|
@@ -2761,12 +2994,19 @@ function SignIn({
|
|
|
2761
2994
|
redirectHandled: !!target
|
|
2762
2995
|
});
|
|
2763
2996
|
} catch (error) {
|
|
2764
|
-
// The
|
|
2765
|
-
//
|
|
2766
|
-
//
|
|
2767
|
-
|
|
2997
|
+
// The failure belongs to this card, not to the global notification:
|
|
2998
|
+
// the person is looking at the eight characters they just typed.
|
|
2999
|
+
// The field goes back empty and focused, ready for the next try.
|
|
3000
|
+
setCodeFailure(describeCodeFailure(error));
|
|
2768
3001
|
setCode('');
|
|
2769
|
-
|
|
3002
|
+
codeInputRef.current?.focus();
|
|
3003
|
+
// Still reported — an app may log it — but flagged: the card
|
|
3004
|
+
// already explains it, and a notification repeating "Código
|
|
3005
|
+
// inválido" in the corner would say it twice.
|
|
3006
|
+
onError?.(error, {
|
|
3007
|
+
step: 'verify',
|
|
3008
|
+
isShownOnCard: true
|
|
3009
|
+
});
|
|
2770
3010
|
}
|
|
2771
3011
|
};
|
|
2772
3012
|
|
|
@@ -2876,7 +3116,27 @@ function SignIn({
|
|
|
2876
3116
|
})
|
|
2877
3117
|
}) : /*#__PURE__*/jsxRuntime.jsxs(core.Stack, {
|
|
2878
3118
|
gap: "md",
|
|
2879
|
-
children: [/*#__PURE__*/jsxRuntime.jsx(core.
|
|
3119
|
+
children: [isCodeResent && /*#__PURE__*/jsxRuntime.jsx(core.Alert, {
|
|
3120
|
+
color: "gray",
|
|
3121
|
+
variant: "light",
|
|
3122
|
+
radius: 0,
|
|
3123
|
+
p: "xs",
|
|
3124
|
+
children: /*#__PURE__*/jsxRuntime.jsxs(core.Text, {
|
|
3125
|
+
size: "xs",
|
|
3126
|
+
lh: 1.4,
|
|
3127
|
+
children: [/*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
3128
|
+
span: true,
|
|
3129
|
+
inherit: true,
|
|
3130
|
+
fw: 700,
|
|
3131
|
+
c: "gray.9",
|
|
3132
|
+
children: labels.codeResentTitle || 'Novo código enviado.'
|
|
3133
|
+
}), ' ', labels.codeResent || 'O anterior deixou de valer.']
|
|
3134
|
+
})
|
|
3135
|
+
}), /*#__PURE__*/jsxRuntime.jsx(CodeFailureNotice, {
|
|
3136
|
+
failure: codeFailure,
|
|
3137
|
+
labels: labels
|
|
3138
|
+
}), /*#__PURE__*/jsxRuntime.jsx(core.TextInput, {
|
|
3139
|
+
ref: codeInputRef,
|
|
2880
3140
|
label: labels.codeLabel || 'Código de acesso'
|
|
2881
3141
|
/*
|
|
2882
3142
|
* The email is the field's description, not the subtitle:
|
|
@@ -2896,26 +3156,56 @@ function SignIn({
|
|
|
2896
3156
|
value: code,
|
|
2897
3157
|
onChange: event => {
|
|
2898
3158
|
setCode(event.currentTarget.value);
|
|
2899
|
-
|
|
3159
|
+
// Typing again is the correction: the notice has
|
|
3160
|
+
// done its job. A locked field cannot be typed in,
|
|
3161
|
+
// so an exhausted or expired notice stays.
|
|
3162
|
+
if (codeFailure) setCodeFailure(null);
|
|
2900
3163
|
},
|
|
2901
3164
|
onKeyDown: event => {
|
|
2902
3165
|
if (event.key === 'Enter' && code.trim()) handleVerify(code);
|
|
2903
3166
|
},
|
|
2904
3167
|
autoFocus: true,
|
|
2905
3168
|
autoComplete: "one-time-code",
|
|
2906
|
-
readOnly: verifying
|
|
2907
|
-
|
|
2908
|
-
|
|
3169
|
+
readOnly: verifying
|
|
3170
|
+
/*
|
|
3171
|
+
* No `error` on the field: the notice above carries the
|
|
3172
|
+
* failure, and a red border around an empty field turned
|
|
3173
|
+
* the PLACEHOLDER red — "ABCD-EFGH" read as the code the
|
|
3174
|
+
* person had typed. With the request gone there is
|
|
3175
|
+
* nothing left to type into.
|
|
3176
|
+
*/,
|
|
3177
|
+
disabled: isCodeLocked
|
|
3178
|
+
}), isCodeLocked ?
|
|
3179
|
+
/*#__PURE__*/
|
|
3180
|
+
/*
|
|
3181
|
+
* The request is gone: confirming can only fail again, so
|
|
3182
|
+
* the one action that works takes the button's place.
|
|
3183
|
+
*/
|
|
3184
|
+
jsxRuntime.jsx(core.Button, {
|
|
3185
|
+
type: "button",
|
|
3186
|
+
fullWidth: true,
|
|
3187
|
+
"aria-disabled": sending,
|
|
3188
|
+
onClick: sending ? undefined : handleResend,
|
|
3189
|
+
leftSection: sending ? /*#__PURE__*/jsxRuntime.jsx(core.Loader, {
|
|
3190
|
+
size: 14,
|
|
3191
|
+
color: "gray.0"
|
|
3192
|
+
}) : /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconRefresh, {
|
|
3193
|
+
size: 16
|
|
3194
|
+
}),
|
|
3195
|
+
children: sending ? labels.sendingCode || 'Enviando…' : labels.sendNewCode || 'Enviar novo código'
|
|
3196
|
+
}) : /*#__PURE__*/jsxRuntime.jsx(core.Button, {
|
|
2909
3197
|
type: "button",
|
|
2910
3198
|
fullWidth: true
|
|
2911
|
-
// Same reason as the previous step: `disabled` would
|
|
2912
|
-
// the button exactly while signing in happens.
|
|
2913
|
-
//
|
|
2914
|
-
//
|
|
3199
|
+
// Same reason as the previous step: `disabled` would
|
|
3200
|
+
// fade the button exactly while signing in happens.
|
|
3201
|
+
//
|
|
3202
|
+
// Never disabled for an empty field either. Right
|
|
3203
|
+
// after a failure the field is empty on purpose, and
|
|
3204
|
+
// a grey button there read as a frozen screen; the
|
|
3205
|
+
// click sends the cursor to the field instead.
|
|
2915
3206
|
,
|
|
2916
3207
|
"aria-disabled": verifying,
|
|
2917
|
-
|
|
2918
|
-
onClick: verifying ? undefined : () => handleVerify(code),
|
|
3208
|
+
onClick: verifying ? undefined : () => code.trim() ? handleVerify(code) : codeInputRef.current?.focus(),
|
|
2919
3209
|
leftSection: verifying ? /*#__PURE__*/jsxRuntime.jsx(core.Loader, {
|
|
2920
3210
|
size: 14,
|
|
2921
3211
|
color: "gray.0"
|
|
@@ -2933,18 +3223,17 @@ function SignIn({
|
|
|
2933
3223
|
onClick: () => {
|
|
2934
3224
|
setSentTo(null);
|
|
2935
3225
|
setCode('');
|
|
2936
|
-
|
|
3226
|
+
setCodeFailure(null);
|
|
3227
|
+
setIsCodeResent(false);
|
|
2937
3228
|
// The label promises another email: the form,
|
|
2938
3229
|
// not the list the person may have come from.
|
|
2939
3230
|
setIsChoosingOther(true);
|
|
2940
3231
|
},
|
|
2941
3232
|
children: labels.changeEmail || 'Usar outro e-mail'
|
|
2942
|
-
}), /*#__PURE__*/jsxRuntime.jsx(core.Anchor, {
|
|
3233
|
+
}), !isCodeLocked && /*#__PURE__*/jsxRuntime.jsx(core.Anchor, {
|
|
2943
3234
|
size: "sm",
|
|
2944
3235
|
c: "dimmed",
|
|
2945
|
-
onClick: sending ? undefined :
|
|
2946
|
-
email: sentTo
|
|
2947
|
-
}),
|
|
3236
|
+
onClick: sending ? undefined : handleResend,
|
|
2948
3237
|
children: sending ? labels.sendingCode || 'Enviando…' : labels.resendCode || 'Reenviar código'
|
|
2949
3238
|
})]
|
|
2950
3239
|
})]
|
|
@@ -3947,6 +4236,7 @@ exports.TOKEN_STORAGE_KEY = TOKEN_STORAGE_KEY;
|
|
|
3947
4236
|
exports.UserInformation = UserInformation;
|
|
3948
4237
|
exports.UserProfile = UserProfile;
|
|
3949
4238
|
exports.Wordmark = Wordmark;
|
|
4239
|
+
exports.adoptRecentAccounts = adoptRecentAccounts;
|
|
3950
4240
|
exports.announceIdentityChange = announceIdentityChange;
|
|
3951
4241
|
exports.applyRedirect = applyRedirect;
|
|
3952
4242
|
exports.clearIdentitySwitching = clearIdentitySwitching;
|
|
@@ -3954,7 +4244,9 @@ exports.configure = configure;
|
|
|
3954
4244
|
exports.consumeSocialError = consumeSocialError;
|
|
3955
4245
|
exports.consumeSocialToken = consumeSocialToken;
|
|
3956
4246
|
exports.decodeJWT = decodeJWT;
|
|
4247
|
+
exports.deleteRecentAccount = deleteRecentAccount;
|
|
3957
4248
|
exports.endImpersonation = endImpersonation;
|
|
4249
|
+
exports.fetchRecentAccounts = fetchRecentAccounts;
|
|
3958
4250
|
exports.forgetAccount = forgetAccount;
|
|
3959
4251
|
exports.getApiUrl = getApiUrl;
|
|
3960
4252
|
exports.getApplicationInfo = getApplicationInfo;
|
|
@@ -3976,6 +4268,7 @@ exports.requestCode = requestCode;
|
|
|
3976
4268
|
exports.resolveRedirect = resolveRedirect;
|
|
3977
4269
|
exports.revokeOtherSessions = revokeOtherSessions;
|
|
3978
4270
|
exports.revokeSession = revokeSession;
|
|
4271
|
+
exports.saveRecentAccount = saveRecentAccount;
|
|
3979
4272
|
exports.setStoredToken = setStoredToken;
|
|
3980
4273
|
exports.shouldSignOutOn401 = shouldSignOutOn401;
|
|
3981
4274
|
exports.signOut = signOut;
|