@ciromaciel/auth-react 1.0.1 → 1.1.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/dist/index.js CHANGED
@@ -9,6 +9,143 @@ var core = require('@mantine/core');
9
9
  var form = require('@mantine/form');
10
10
  var iconsReact = require('@tabler/icons-react');
11
11
 
12
+ /**
13
+ * The accounts that already signed in on this browser.
14
+ *
15
+ * `<SignIn />` offers them before the empty email field: for a returning
16
+ * person, choosing the account IS the click that sends the code. Nothing here
17
+ * is a credential — a saved email still has to receive and type a fresh code —
18
+ * so the list is a shortcut, never a session.
19
+ *
20
+ * WHAT IS KEPT, AND WHEN
21
+ *
22
+ * Email, the way in (`'code'` or a provider id such as `'google'`) and the time
23
+ * of the last sign-in. No token, name or picture: that is everything needed to
24
+ * draw a row, and the rest arrives with the session.
25
+ *
26
+ * An email enters only after a session exists — the code confirmed, or the
27
+ * provider's token back in the fragment. Never on "send code": a typo there
28
+ * would otherwise become a permanent suggestion.
29
+ *
30
+ * WHY IT SURVIVES SIGN-OUT AND IDENTITY SWITCHES
31
+ *
32
+ * The list exists precisely for the person who left and is coming back, so
33
+ * signing out does not clear it; removing an account is an explicit action on
34
+ * the screen. The key is in `KEEP` in `identitySwitch.js` for the same reason:
35
+ * the list belongs to the browser, not to whichever account is signed in, and
36
+ * holds no data of any account beyond its own email.
37
+ *
38
+ * `localStorage` is per origin, so each panel keeps its own list.
39
+ */
40
+
41
+ const RECENT_ACCOUNTS_KEY = 'auth:recent-accounts';
42
+
43
+ // Five rows fit the 350px card with no scroll. The oldest drops out on its
44
+ // own; signing in with it again puts it back on top.
45
+ const MAX_RECENT_ACCOUNTS = 5;
46
+
47
+ // Which provider this tab left for. `sessionStorage` because the answer only
48
+ // matters to the tab that comes back, and it expires so an abandoned consent
49
+ // screen does not label a later, unrelated token.
50
+ const SOCIAL_DEPARTURE_KEY = 'auth:social-departure';
51
+ const SOCIAL_DEPARTURE_TTL_MS = 15 * 60 * 1000;
52
+ const normalizeEmail = email => String(email ?? '').trim().toLowerCase();
53
+
54
+ /**
55
+ * The saved accounts, most recent first. Never throws: a private window,
56
+ * blocked storage or a hand-edited value all read as an empty list.
57
+ *
58
+ * @returns {{ email: string, method: string, lastUsedAt: number }[]}
59
+ */
60
+ function listRecentAccounts() {
61
+ try {
62
+ const raw = window.localStorage.getItem(RECENT_ACCOUNTS_KEY);
63
+ if (!raw) return [];
64
+ const parsed = JSON.parse(raw);
65
+ if (!Array.isArray(parsed)) return [];
66
+ return parsed.filter(account => account && typeof account.email === 'string' && account.email.includes('@')).map(account => ({
67
+ email: normalizeEmail(account.email),
68
+ method: typeof account.method === 'string' && account.method ? account.method : 'code',
69
+ lastUsedAt: Number(account.lastUsedAt) || 0
70
+ })).sort((a, b) => b.lastUsedAt - a.lastUsedAt).slice(0, MAX_RECENT_ACCOUNTS);
71
+ } catch {
72
+ return [];
73
+ }
74
+ }
75
+ function writeRecentAccounts(accounts) {
76
+ try {
77
+ window.localStorage.setItem(RECENT_ACCOUNTS_KEY, JSON.stringify(accounts));
78
+ } catch {
79
+ // No storage: the screen simply keeps asking for the email.
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Puts the account on top of the list, creating it or refreshing it.
85
+ *
86
+ * @param {string} email
87
+ * @param {string} [method='code'] - `'code'` or the provider id
88
+ * @returns the list as it stands after the write
89
+ */
90
+ function rememberAccount(email, method = 'code') {
91
+ const normalized = normalizeEmail(email);
92
+ if (!normalized.includes('@')) return listRecentAccounts();
93
+ const next = [{
94
+ email: normalized,
95
+ method: method || 'code',
96
+ lastUsedAt: Date.now()
97
+ }, ...listRecentAccounts().filter(account => account.email !== normalized)].slice(0, MAX_RECENT_ACCOUNTS);
98
+ writeRecentAccounts(next);
99
+ return next;
100
+ }
101
+
102
+ /**
103
+ * Removes one account from this browser's list. The account itself, its
104
+ * sessions and the lists of other panels are untouched.
105
+ *
106
+ * @returns the list as it stands after the removal
107
+ */
108
+ function forgetAccount(email) {
109
+ const normalized = normalizeEmail(email);
110
+ const next = listRecentAccounts().filter(account => account.email !== normalized);
111
+ writeRecentAccounts(next);
112
+ return next;
113
+ }
114
+
115
+ /** Records the provider this tab is leaving for. */
116
+ function markSocialDeparture(provider) {
117
+ try {
118
+ window.sessionStorage.setItem(SOCIAL_DEPARTURE_KEY, JSON.stringify({
119
+ provider,
120
+ at: Date.now()
121
+ }));
122
+ } catch {
123
+ // No storage: the account is not remembered, and the sign-in still works.
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Reads and clears the provider this tab left for, if it left recently.
129
+ *
130
+ * Read BEFORE an identity switch runs: the switch clears `sessionStorage`.
131
+ */
132
+ function takeSocialDeparture() {
133
+ try {
134
+ const raw = window.sessionStorage.getItem(SOCIAL_DEPARTURE_KEY);
135
+ if (!raw) return null;
136
+ window.sessionStorage.removeItem(SOCIAL_DEPARTURE_KEY);
137
+ const {
138
+ provider,
139
+ at
140
+ } = JSON.parse(raw);
141
+ if (typeof provider !== 'string' || !provider) return null;
142
+ if (!(Date.now() - Number(at) < SOCIAL_DEPARTURE_TTL_MS)) return null;
143
+ return provider;
144
+ } catch {
145
+ return null;
146
+ }
147
+ }
148
+
12
149
  /**
13
150
  * The identity switch — when the panel stops being one person and becomes
14
151
  * another in the middle of a live session.
@@ -106,7 +243,12 @@ const KEEP = new Set([
106
243
  // The switch beacon: a timestamp the sibling tabs listen for. Wiping it
107
244
  // would make the NEXT write look unchanged to the browser in some cases,
108
245
  // and the event that carries the switch would not fire.
109
- 'auth:identity-switched']);
246
+ 'auth:identity-switched',
247
+ // The accounts that signed in on this browser (`recent-accounts.js`). It
248
+ // belongs to the browser, not to whoever is signed in, and holds nothing
249
+ // of any account beyond its email — wiping it on every switch would empty
250
+ // the sign-in shortcuts each time an operator impersonates someone.
251
+ RECENT_ACCOUNTS_KEY]);
110
252
  function dropStoredAccountState() {
111
253
  try {
112
254
  const doomed = [];
@@ -585,14 +727,22 @@ const getSocialProviders = async () => {
585
727
  * `redirect` is where to come back to; the worker validates it against the
586
728
  * application's allowlist BEFORE leaving, and stores the validated value. It
587
729
  * defaults to the current page.
730
+ *
731
+ * `rememberAccount: false` keeps the account off this browser's recent list
732
+ * (`recent-accounts.js`) when the token comes back.
588
733
  */
589
734
  const startSocialSignIn = (provider, {
590
- redirect
735
+ redirect,
736
+ rememberAccount: shouldRemember = true
591
737
  } = {}) => {
592
738
  const destination = redirect || window.location.href.split('#')[0];
593
739
  const url = new URL(`${API_BASE}/auth/sign-in/${provider}`);
594
740
  url.searchParams.set('redirect', destination);
595
741
  if (API_KEY && !INTERNAL_MODE) url.searchParams.set('api_key', API_KEY);
742
+
743
+ // The fragment that comes back carries only the token, not which provider
744
+ // issued it — so the tab notes where it went before leaving.
745
+ if (shouldRemember) markSocialDeparture(provider);
596
746
  window.location.assign(url.toString());
597
747
  };
598
748
 
@@ -611,6 +761,11 @@ const consumeSocialToken = () => {
611
761
  const token = params.get('token');
612
762
  if (!token) return null;
613
763
 
764
+ // Read before the identity switch below: it clears `sessionStorage`, where
765
+ // the departure is noted. A token without a departure (a handoff between
766
+ // panels) is not a sign-in made here, and does not enter the list.
767
+ const provider = takeSocialDeparture();
768
+
614
769
  /*
615
770
  * A social sign-in can land on a tab that already belongs to SOMEBODY ELSE.
616
771
  *
@@ -643,6 +798,10 @@ const consumeSocialToken = () => {
643
798
  if (before && after && before !== after) markIdentitySwitching();
644
799
  }
645
800
  setStoredToken(token);
801
+ if (provider) {
802
+ const email = decodeJWT(token)?.email;
803
+ if (email) rememberAccount(email, provider);
804
+ }
646
805
  params.delete('token');
647
806
  const rest = params.toString();
648
807
  window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}${rest ? `#${rest}` : ''}`);
@@ -661,6 +820,9 @@ const consumeSocialError = () => {
661
820
  const params = new URLSearchParams(window.location.search);
662
821
  const reason = params.get('social_error');
663
822
  if (!reason) return null;
823
+
824
+ // The trip failed: the departure noted for it must not label a later token.
825
+ takeSocialDeparture();
664
826
  params.delete('social_error');
665
827
  const rest = params.toString();
666
828
  window.history.replaceState(null, '', `${window.location.pathname}${rest ? `?${rest}` : ''}${window.location.hash}`);
@@ -2029,6 +2191,156 @@ function AuthCard({
2029
2191
  });
2030
2192
  }
2031
2193
 
2194
+ const PROVIDER_NAMES = {
2195
+ google: 'Google',
2196
+ github: 'GitHub'
2197
+ };
2198
+ const providerName = method => PROVIDER_NAMES[method] || method.charAt(0).toUpperCase() + method.slice(1);
2199
+ const RELATIVE = new Intl.RelativeTimeFormat('pt-BR', {
2200
+ numeric: 'auto'
2201
+ });
2202
+
2203
+ /** "há 2 horas", "ontem", "há 6 dias" — the unit that reads naturally. */
2204
+ function formatLastUsed(timestamp, now = Date.now()) {
2205
+ const minutes = Math.round((timestamp - now) / 60000);
2206
+ if (Math.abs(minutes) < 1) return RELATIVE.format(0, 'second');
2207
+ if (Math.abs(minutes) < 60) return RELATIVE.format(minutes, 'minute');
2208
+ const hours = Math.round(minutes / 60);
2209
+ if (Math.abs(hours) < 24) return RELATIVE.format(hours, 'hour');
2210
+ const days = Math.round(hours / 24);
2211
+ if (Math.abs(days) < 30) return RELATIVE.format(days, 'day');
2212
+ const months = Math.round(days / 30);
2213
+ if (Math.abs(months) < 12) return RELATIVE.format(months, 'month');
2214
+ return RELATIVE.format(Math.round(days / 365), 'year');
2215
+ }
2216
+
2217
+ /** "ciro" → "C", "qa+monitors" → "QM". */
2218
+ function initialsOf(email) {
2219
+ const local = email.split('@')[0];
2220
+ const parts = local.split(/[.+_-]/).filter(Boolean);
2221
+ return ((parts[0] || local).charAt(0) + (parts[1] ? parts[1].charAt(0) : '')).toUpperCase();
2222
+ }
2223
+
2224
+ /**
2225
+ * The first step for someone this browser already knows.
2226
+ *
2227
+ * Each row is the whole action: clicking it sends the code (or leaves for the
2228
+ * provider the account used last time), so a returning person goes from
2229
+ * opening the screen to typing the code in one click.
2230
+ *
2231
+ * Removing is behind "Gerenciar" on purpose. An × always in view sits right
2232
+ * next to the row the person came to click, and a slip would drop the account
2233
+ * they meant to use. Nothing is lost by removing — signing in again brings the
2234
+ * account back — so there is no confirmation step either.
2235
+ */
2236
+ function RecentAccounts({
2237
+ accounts,
2238
+ pickingEmail = null,
2239
+ managing = false,
2240
+ onToggleManage,
2241
+ onPick,
2242
+ onForget,
2243
+ onUseOther,
2244
+ labels = {}
2245
+ }) {
2246
+ const busy = !!pickingEmail;
2247
+ return /*#__PURE__*/jsxRuntime.jsxs(core.Stack, {
2248
+ gap: "md",
2249
+ children: [/*#__PURE__*/jsxRuntime.jsxs(core.Stack, {
2250
+ gap: 8,
2251
+ children: [/*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2252
+ justify: "space-between",
2253
+ align: "baseline",
2254
+ wrap: "nowrap",
2255
+ children: [/*#__PURE__*/jsxRuntime.jsx(core.Text, {
2256
+ fz: 11,
2257
+ fw: 800,
2258
+ lh: 1,
2259
+ tt: "uppercase",
2260
+ lts: "1.5px",
2261
+ c: "gray.4",
2262
+ children: labels.recentAccountsHeading || 'Contas neste navegador'
2263
+ }), /*#__PURE__*/jsxRuntime.jsx(core.Anchor, {
2264
+ component: "button",
2265
+ type: "button"
2266
+ /*
2267
+ * The size of the heading it sits beside, not of body
2268
+ * text: it is a secondary control of the list, and at
2269
+ * 14px it outweighed the 11px label it annotates.
2270
+ */,
2271
+ fz: 12,
2272
+ lh: 1,
2273
+ c: "dimmed",
2274
+ onClick: busy ? undefined : onToggleManage,
2275
+ children: managing ? labels.recentAccountsDone || 'Concluir' : labels.recentAccountsManage || 'Gerenciar'
2276
+ })]
2277
+ }), /*#__PURE__*/jsxRuntime.jsx(core.Paper, {
2278
+ withBorder: true,
2279
+ radius: 0,
2280
+ p: 0,
2281
+ children: accounts.map((account, index) => {
2282
+ const isPicking = pickingEmail === account.email;
2283
+ const isSocial = account.method !== 'code';
2284
+ const description = isPicking ? isSocial ? `${labels.openingProvider || 'Abrindo o'} ${providerName(account.method)}…` : labels.sendingCode || 'Enviando código…' : `${labels.lastUsed || 'Último acesso'} ${formatLastUsed(account.lastUsedAt)}${isSocial ? ` · ${providerName(account.method)}` : ''}`;
2285
+ return /*#__PURE__*/jsxRuntime.jsx(core.NavLink, {
2286
+ /*
2287
+ * A `div` while managing: the row then holds the
2288
+ * remove button, and a button inside a button is
2289
+ * invalid HTML that browsers repair by splitting
2290
+ * the row in two.
2291
+ */
2292
+ component: managing ? 'div' : 'button',
2293
+ type: managing ? undefined : 'button',
2294
+ "aria-disabled": busy || undefined,
2295
+ onClick: managing || busy ? undefined : () => onPick(account),
2296
+ noWrap: true,
2297
+ label: /*#__PURE__*/jsxRuntime.jsx(core.Text, {
2298
+ fz: 14,
2299
+ fw: 600,
2300
+ c: "gray.9",
2301
+ truncate: true,
2302
+ children: account.email
2303
+ }),
2304
+ description: description,
2305
+ leftSection: /*#__PURE__*/jsxRuntime.jsx(core.Avatar, {
2306
+ radius: 0,
2307
+ size: 36,
2308
+ color: "gray",
2309
+ variant: "light",
2310
+ children: initialsOf(account.email)
2311
+ }),
2312
+ rightSection: managing ? /*#__PURE__*/jsxRuntime.jsx(core.ActionIcon, {
2313
+ variant: "subtle",
2314
+ color: "gray",
2315
+ "aria-label": `${labels.removeAccount || 'Remover'} ${account.email}`,
2316
+ onClick: () => onForget(account),
2317
+ children: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconX, {
2318
+ size: 16
2319
+ })
2320
+ }) : isPicking ? /*#__PURE__*/jsxRuntime.jsx(core.Loader, {
2321
+ size: 14
2322
+ }) : /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconArrowRight, {
2323
+ size: 16,
2324
+ color: "var(--mantine-color-gray-5)"
2325
+ }),
2326
+ py: 10,
2327
+ style: index > 0 ? {
2328
+ borderTop: '1px solid var(--mantine-color-gray-2)'
2329
+ } : undefined
2330
+ }, account.email);
2331
+ })
2332
+ })]
2333
+ }), /*#__PURE__*/jsxRuntime.jsx(core.Button, {
2334
+ type: "button",
2335
+ variant: "default",
2336
+ fullWidth: true,
2337
+ "aria-disabled": busy,
2338
+ onClick: busy ? undefined : onUseOther,
2339
+ children: labels.useOtherEmail || 'Usar outro e-mail'
2340
+ })]
2341
+ });
2342
+ }
2343
+
2032
2344
  const MARKS = {
2033
2345
  google: iconsReact.IconBrandGoogle
2034
2346
  };
@@ -2050,7 +2362,11 @@ const MARKS = {
2050
2362
  function SocialButtons({
2051
2363
  labels = {},
2052
2364
  redirect,
2053
- disabled = false
2365
+ disabled = false,
2366
+ // Whether the account returning from the provider joins this browser's
2367
+ // recent list (`recent-accounts.js`). `<SignIn recentAccounts={false}>`
2368
+ // turns it off here too.
2369
+ rememberAccount = true
2054
2370
  }) {
2055
2371
  const [providers, setProviders] = react.useState(null);
2056
2372
  const [leaving, setLeaving] = react.useState(null);
@@ -2094,7 +2410,8 @@ function SocialButtons({
2094
2410
  // `oauth_states` row.
2095
2411
  setLeaving(provider.provider);
2096
2412
  startSocialSignIn(provider.provider, {
2097
- redirect
2413
+ redirect,
2414
+ rememberAccount
2098
2415
  });
2099
2416
  },
2100
2417
  children: /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
@@ -2293,6 +2610,15 @@ function SignIn({
2293
2610
  * `false` opts out entirely, for a screen that wants the emailed code only.
2294
2611
  */
2295
2612
  socialLogin = 'auto',
2613
+ /*
2614
+ * The accounts that already signed in on this browser, offered before the
2615
+ * empty field (`recent-accounts.js`). With none saved the screen is exactly
2616
+ * the email form, so the default changes nothing for a first visit.
2617
+ *
2618
+ * `false` neither shows nor saves: a shared computer, or an internal panel,
2619
+ * should not remember who used it.
2620
+ */
2621
+ recentAccounts = true,
2296
2622
  ...cardProps
2297
2623
  }) {
2298
2624
  const user = useAuthStore(s => s.user);
@@ -2308,6 +2634,14 @@ function SignIn({
2308
2634
  const [code, setCode] = react.useState('');
2309
2635
  const [codeError, setCodeError] = react.useState(null);
2310
2636
 
2637
+ // Read once, on mount: the list only changes through this screen, and
2638
+ // every change below writes the new list back into state.
2639
+ const [accounts, setAccounts] = react.useState(() => recentAccounts ? listRecentAccounts() : []);
2640
+ const [isChoosingOther, setIsChoosingOther] = react.useState(false);
2641
+ const [isManaging, setIsManaging] = react.useState(false);
2642
+ const [pickingEmail, setPickingEmail] = react.useState(null);
2643
+ const isShowingAccounts = recentAccounts && accounts.length > 0 && !isChoosingOther;
2644
+
2311
2645
  // Hook that fetches the application's logo
2312
2646
  const applicationLogo = useApplicationLogo();
2313
2647
  const finalLogo = logo || applicationLogo || /*#__PURE__*/jsxRuntime.jsx(Wordmark, {});
@@ -2365,6 +2699,36 @@ function SignIn({
2365
2699
  }
2366
2700
  };
2367
2701
 
2702
+ // Step 1, from the list — the click on a saved account IS the request.
2703
+ //
2704
+ // An account that came in through a provider goes back to that provider,
2705
+ // as long as the application still offers it. If the owner turned it off
2706
+ // in the meantime the emailed code still works for the same email, so that
2707
+ // is the fallback rather than a dead row.
2708
+ const handlePick = async account => {
2709
+ if (pickingEmail || sending) return;
2710
+ setPickingEmail(account.email);
2711
+ if (account.method !== 'code' && socialLogin !== false) {
2712
+ const providers = await getSocialProviders();
2713
+ if (providers?.some(provider => provider.provider === account.method)) {
2714
+ // The page is leaving; the row keeps its spinner until it does.
2715
+ startSocialSignIn(account.method, {
2716
+ rememberAccount: recentAccounts
2717
+ });
2718
+ return;
2719
+ }
2720
+ }
2721
+ await handleRequest({
2722
+ email: account.email
2723
+ });
2724
+ setPickingEmail(null);
2725
+ };
2726
+ const handleForget = account => {
2727
+ const next = forgetAccount(account.email);
2728
+ setAccounts(next);
2729
+ if (next.length === 0) setIsManaging(false);
2730
+ };
2731
+
2368
2732
  // Step 2 — trade the code for the session.
2369
2733
  //
2370
2734
  // The redirect is decided AND EXECUTED before onSuccess.
@@ -2385,6 +2749,11 @@ function SignIn({
2385
2749
  setCodeError(null);
2386
2750
  try {
2387
2751
  const result = await verifyCode(sentTo, value);
2752
+
2753
+ // Only now, with a session: an email that never received a valid
2754
+ // code never becomes a suggestion. Written before the redirect,
2755
+ // which may unmount this screen.
2756
+ if (recentAccounts) setAccounts(rememberAccount(sentTo, 'code'));
2388
2757
  const target = handleRedirect ? getRedirectFromLocation(redirectOrigins) : null;
2389
2758
  if (target) applyRedirect(target, navigate);
2390
2759
  onSuccess?.(result?.user ?? null, {
@@ -2419,17 +2788,43 @@ function SignIn({
2419
2788
  logo: finalLogo,
2420
2789
  logoWidth: logoWidth,
2421
2790
  title: title,
2422
- subtitle: sentTo ? labels.codeSent || 'Digite o código que enviamos' : subtitle,
2791
+ subtitle: sentTo ? labels.codeSent || 'Digite o código que enviamos' : isShowingAccounts ? labels.recentAccountsSubtitle || 'Escolha a conta que vai receber o código' : subtitle,
2423
2792
  variant: variant,
2424
2793
  opened: opened,
2425
2794
  onClose: onClose,
2426
2795
  modalProps: modalProps,
2427
2796
  ...cardProps,
2428
- children: !sentTo ? /*#__PURE__*/jsxRuntime.jsx("form", {
2797
+ children: !sentTo && isShowingAccounts ? /*#__PURE__*/jsxRuntime.jsx(RecentAccounts, {
2798
+ accounts: accounts,
2799
+ pickingEmail: pickingEmail,
2800
+ managing: isManaging,
2801
+ onToggleManage: () => setIsManaging(value => !value),
2802
+ onPick: handlePick,
2803
+ onForget: handleForget,
2804
+ onUseOther: () => {
2805
+ setIsManaging(false);
2806
+ setIsChoosingOther(true);
2807
+ },
2808
+ labels: labels
2809
+ }) : !sentTo ? /*#__PURE__*/jsxRuntime.jsx("form", {
2429
2810
  onSubmit: form$1.onSubmit(handleRequest),
2430
2811
  children: /*#__PURE__*/jsxRuntime.jsxs(core.Stack, {
2431
2812
  gap: "md",
2432
- children: [/*#__PURE__*/jsxRuntime.jsx(core.TextInput, {
2813
+ children: [recentAccounts && accounts.length > 0 && /*#__PURE__*/jsxRuntime.jsx(core.Anchor, {
2814
+ component: "button",
2815
+ type: "button",
2816
+ size: "sm",
2817
+ c: "dimmed",
2818
+ w: "fit-content",
2819
+ onClick: () => setIsChoosingOther(false),
2820
+ children: /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
2821
+ gap: 6,
2822
+ wrap: "nowrap",
2823
+ children: [/*#__PURE__*/jsxRuntime.jsx(iconsReact.IconArrowLeft, {
2824
+ size: 14
2825
+ }), `${labels.savedAccounts || 'Contas salvas'} (${accounts.length})`]
2826
+ })
2827
+ }), /*#__PURE__*/jsxRuntime.jsx(core.TextInput, {
2433
2828
  label: labels.email || 'Email',
2434
2829
  placeholder: labels.emailPlaceholder || 'seu@email.com',
2435
2830
  type: "email",
@@ -2471,7 +2866,8 @@ function SignIn({
2471
2866
  children: sending ? labels.sendingCode || 'Enviando…' : labels.sendCodeButton || 'Enviar código'
2472
2867
  }), socialLogin !== false && /*#__PURE__*/jsxRuntime.jsx(SocialButtons, {
2473
2868
  labels: labels,
2474
- disabled: sending
2869
+ disabled: sending,
2870
+ rememberAccount: recentAccounts
2475
2871
  }), /*#__PURE__*/jsxRuntime.jsx(TermsNotice, {
2476
2872
  url: termsUrl,
2477
2873
  text: labels.termsNotice || 'Criando uma conta, você concorda com todos os nossos',
@@ -2538,6 +2934,9 @@ function SignIn({
2538
2934
  setSentTo(null);
2539
2935
  setCode('');
2540
2936
  setCodeError(null);
2937
+ // The label promises another email: the form,
2938
+ // not the list the person may have come from.
2939
+ setIsChoosingOther(true);
2541
2940
  },
2542
2941
  children: labels.changeEmail || 'Usar outro e-mail'
2543
2942
  }), /*#__PURE__*/jsxRuntime.jsx(core.Anchor, {
@@ -3535,7 +3934,9 @@ exports.AuthLoading = AuthLoading;
3535
3934
  exports.AuthProvider = AuthProvider;
3536
3935
  exports.GuestOnly = GuestOnly;
3537
3936
  exports.IDENTITY_CHANGED_EVENT = IDENTITY_CHANGED_EVENT;
3937
+ exports.MAX_RECENT_ACCOUNTS = MAX_RECENT_ACCOUNTS;
3538
3938
  exports.Protect = Protect;
3939
+ exports.RECENT_ACCOUNTS_KEY = RECENT_ACCOUNTS_KEY;
3539
3940
  exports.SignIn = SignIn;
3540
3941
  exports.SignInButton = SignInButton;
3541
3942
  exports.SignOutButton = SignOutButton;
@@ -3554,6 +3955,7 @@ exports.consumeSocialError = consumeSocialError;
3554
3955
  exports.consumeSocialToken = consumeSocialToken;
3555
3956
  exports.decodeJWT = decodeJWT;
3556
3957
  exports.endImpersonation = endImpersonation;
3958
+ exports.forgetAccount = forgetAccount;
3557
3959
  exports.getApiUrl = getApiUrl;
3558
3960
  exports.getApplicationInfo = getApplicationInfo;
3559
3961
  exports.getCurrentUser = getCurrentUser;
@@ -3564,10 +3966,12 @@ exports.getSocialProviders = getSocialProviders;
3564
3966
  exports.isAuthenticated = isAuthenticated;
3565
3967
  exports.isIdentitySwitching = isIdentitySwitching;
3566
3968
  exports.isInternal = isInternal;
3969
+ exports.listRecentAccounts = listRecentAccounts;
3567
3970
  exports.listSessions = listSessions;
3568
3971
  exports.markIdentitySwitching = markIdentitySwitching;
3569
3972
  exports.pollCode = pollCode;
3570
3973
  exports.refreshToken = refreshToken;
3974
+ exports.rememberAccount = rememberAccount;
3571
3975
  exports.requestCode = requestCode;
3572
3976
  exports.resolveRedirect = resolveRedirect;
3573
3977
  exports.revokeOtherSessions = revokeOtherSessions;