@ciromaciel/auth-react 1.2.0 → 1.4.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 +37 -7
- package/dist/index.esm.js +444 -122
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +445 -119
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -3,9 +3,9 @@ import { useState, useCallback, useEffect, useMemo, createContext, useRef } from
|
|
|
3
3
|
import { useShallow } from 'zustand/react/shallow';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { Navigate, Outlet, useNavigate } from 'react-router-dom';
|
|
6
|
-
import { Modal, Stack, Text, Group, Image, Title, Paper, Anchor, NavLink, ActionIcon, Loader, Avatar, Button, Divider, TextInput, Alert, Center, Box, Collapse, FileButton, Tooltip, ThemeIcon, Badge,
|
|
6
|
+
import { Modal, Stack, Text, Group, Image, Title, Paper, Anchor, NavLink, ActionIcon, Loader, Avatar, Button, Divider, TextInput, Alert, Center, Box, Collapse, FileButton, Tooltip, ThemeIcon, Badge, UnstyledButton } from '@mantine/core';
|
|
7
7
|
import { useForm } from '@mantine/form';
|
|
8
|
-
import { IconX, IconArrowRight, IconBrandGoogle, IconArrowLeft, IconRefresh, IconAlertCircle, IconUser, IconPhoto, IconTrash, IconCheck, IconPencil, IconMail, IconShield, IconDevices, IconDeviceMobile, IconLogout, IconUserCircle,
|
|
8
|
+
import { IconX, IconArrowRight, IconBrandGoogle, IconArrowLeft, IconRefresh, IconAlertCircle, IconUser, IconPhoto, IconTrash, IconCheck, IconPencil, IconMail, IconShield, IconDevices, IconDeviceMobile, IconLogout, IconUserCircle, IconBuilding, IconCreditCard } from '@tabler/icons-react';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The accounts that already signed in on this browser.
|
|
@@ -110,6 +110,28 @@ function forgetAccount(email) {
|
|
|
110
110
|
return next;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Replaces the local copy with the list the worker returned.
|
|
115
|
+
*
|
|
116
|
+
* The worker's list is the one every panel shares, so when it has accounts it
|
|
117
|
+
* wins: an account removed on another panel must not come back from this
|
|
118
|
+
* panel's stale copy. An EMPTY answer does not wipe the local one — it is what
|
|
119
|
+
* a browser that signed in before the shared list existed gets, and those
|
|
120
|
+
* shortcuts are still true.
|
|
121
|
+
*
|
|
122
|
+
* @returns the list to show
|
|
123
|
+
*/
|
|
124
|
+
function adoptRecentAccounts(remote) {
|
|
125
|
+
if (!Array.isArray(remote) || remote.length === 0) return listRecentAccounts();
|
|
126
|
+
const next = remote.filter(account => account && typeof account.email === 'string' && account.email.includes('@')).map(account => ({
|
|
127
|
+
email: normalizeEmail(account.email),
|
|
128
|
+
method: typeof account.method === 'string' && account.method ? account.method : 'code',
|
|
129
|
+
lastUsedAt: Number(account.lastUsedAt) || 0
|
|
130
|
+
})).sort((a, b) => b.lastUsedAt - a.lastUsedAt).slice(0, MAX_RECENT_ACCOUNTS);
|
|
131
|
+
writeRecentAccounts(next);
|
|
132
|
+
return next;
|
|
133
|
+
}
|
|
134
|
+
|
|
113
135
|
/** Records the provider this tab is leaving for. */
|
|
114
136
|
function markSocialDeparture(provider) {
|
|
115
137
|
try {
|
|
@@ -691,6 +713,64 @@ const updateProfile = async data => {
|
|
|
691
713
|
});
|
|
692
714
|
};
|
|
693
715
|
|
|
716
|
+
/*--- Recent accounts ------------------------------------------------------*/
|
|
717
|
+
|
|
718
|
+
/*
|
|
719
|
+
* The server's copy of this browser's sign-in shortcuts.
|
|
720
|
+
*
|
|
721
|
+
* `localStorage` is per origin, so a list kept only there stayed on the panel
|
|
722
|
+
* where the person signed in. The worker keeps it in an HttpOnly cookie on its
|
|
723
|
+
* own host, which every panel of the application reaches — and answers only to
|
|
724
|
+
* the origins the application allows (`routes/recent-accounts.js`).
|
|
725
|
+
*
|
|
726
|
+
* All three fail soft: the shortcuts are a convenience, and a network error or
|
|
727
|
+
* an origin the worker refuses must never cost the sign-in. `null` means "no
|
|
728
|
+
* answer", which the screen reads as "keep what you have".
|
|
729
|
+
*/
|
|
730
|
+
|
|
731
|
+
/** The list for this application, or `null` when the worker did not answer. */
|
|
732
|
+
const fetchRecentAccounts = async () => {
|
|
733
|
+
try {
|
|
734
|
+
const response = await api('/auth/recent-accounts');
|
|
735
|
+
return Array.isArray(response?.items) ? response.items : null;
|
|
736
|
+
} catch {
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Records the account of the CURRENT session. The worker reads the email from
|
|
743
|
+
* the session, never from here. `keepalive` because the screen is usually
|
|
744
|
+
* navigating away at this very moment.
|
|
745
|
+
*/
|
|
746
|
+
const saveRecentAccount = async (method = 'code') => {
|
|
747
|
+
try {
|
|
748
|
+
const response = await api('/auth/recent-accounts', {
|
|
749
|
+
method: 'POST',
|
|
750
|
+
body: JSON.stringify({
|
|
751
|
+
method
|
|
752
|
+
}),
|
|
753
|
+
keepalive: true
|
|
754
|
+
});
|
|
755
|
+
return Array.isArray(response?.items) ? response.items : null;
|
|
756
|
+
} catch {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
/** Takes one account off this browser's list, on every panel. */
|
|
762
|
+
const deleteRecentAccount = async email => {
|
|
763
|
+
try {
|
|
764
|
+
await api(`/auth/recent-accounts/${encodeURIComponent(email)}`, {
|
|
765
|
+
method: 'DELETE',
|
|
766
|
+
keepalive: true
|
|
767
|
+
});
|
|
768
|
+
return true;
|
|
769
|
+
} catch {
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
|
|
694
774
|
/*--- Social sign-in -------------------------------------------------------*/
|
|
695
775
|
|
|
696
776
|
/**
|
|
@@ -799,6 +879,9 @@ const consumeSocialToken = () => {
|
|
|
799
879
|
if (provider) {
|
|
800
880
|
const email = decodeJWT(token)?.email;
|
|
801
881
|
if (email) rememberAccount(email, provider);
|
|
882
|
+
// The shared copy, so the other panels learn it too. Not awaited: the
|
|
883
|
+
// token is already stored, and the sign-in must not wait on a shortcut.
|
|
884
|
+
saveRecentAccount(provider);
|
|
802
885
|
}
|
|
803
886
|
params.delete('token');
|
|
804
887
|
const rest = params.toString();
|
|
@@ -2782,6 +2865,32 @@ function SignIn({
|
|
|
2782
2865
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- redirectOrigins enters through the serialized key above
|
|
2783
2866
|
}, [authLoading, user, authenticatedRedirect, handleRedirect, redirectOriginsKey, navigate]);
|
|
2784
2867
|
|
|
2868
|
+
/*
|
|
2869
|
+
* The shared list, from the worker.
|
|
2870
|
+
*
|
|
2871
|
+
* The local copy renders at once; this replaces it when the answer
|
|
2872
|
+
* arrives. That is what makes an account used on the Auth panel appear
|
|
2873
|
+
* on Hoster: `localStorage` never crosses between the two origins.
|
|
2874
|
+
*
|
|
2875
|
+
* If the person has already started typing an email, the list does not
|
|
2876
|
+
* yank the form away from under them — it only feeds the "Contas salvas"
|
|
2877
|
+
* link, one click away.
|
|
2878
|
+
*/
|
|
2879
|
+
useEffect(() => {
|
|
2880
|
+
if (!recentAccounts) return;
|
|
2881
|
+
let isActive = true;
|
|
2882
|
+
fetchRecentAccounts().then(remote => {
|
|
2883
|
+
if (!isActive || remote === null) return;
|
|
2884
|
+
const next = adoptRecentAccounts(remote);
|
|
2885
|
+
if (form.isDirty()) setIsChoosingOther(true);
|
|
2886
|
+
setAccounts(next);
|
|
2887
|
+
});
|
|
2888
|
+
return () => {
|
|
2889
|
+
isActive = false;
|
|
2890
|
+
};
|
|
2891
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- once per mount, like the local read
|
|
2892
|
+
}, [recentAccounts]);
|
|
2893
|
+
|
|
2785
2894
|
// Step 1 — ask for the code.
|
|
2786
2895
|
const handleRequest = async values => {
|
|
2787
2896
|
if (sending) return false;
|
|
@@ -2841,6 +2950,9 @@ function SignIn({
|
|
|
2841
2950
|
const handleForget = account => {
|
|
2842
2951
|
const next = forgetAccount(account.email);
|
|
2843
2952
|
setAccounts(next);
|
|
2953
|
+
// On every panel, not just this one. The local removal above already
|
|
2954
|
+
// took it off this screen, so a failure here costs nothing visible.
|
|
2955
|
+
deleteRecentAccount(account.email);
|
|
2844
2956
|
if (next.length === 0) setIsManaging(false);
|
|
2845
2957
|
};
|
|
2846
2958
|
|
|
@@ -2869,7 +2981,10 @@ function SignIn({
|
|
|
2869
2981
|
// Only now, with a session: an email that never received a valid
|
|
2870
2982
|
// code never becomes a suggestion. Written before the redirect,
|
|
2871
2983
|
// which may unmount this screen.
|
|
2872
|
-
if (recentAccounts)
|
|
2984
|
+
if (recentAccounts) {
|
|
2985
|
+
setAccounts(rememberAccount(sentTo, 'code'));
|
|
2986
|
+
saveRecentAccount('code');
|
|
2987
|
+
}
|
|
2873
2988
|
const target = handleRedirect ? getRedirectFromLocation(redirectOrigins) : null;
|
|
2874
2989
|
if (target) applyRedirect(target, navigate);
|
|
2875
2990
|
onSuccess?.(result?.user ?? null, {
|
|
@@ -3867,150 +3982,357 @@ function UserProfile({
|
|
|
3867
3982
|
});
|
|
3868
3983
|
}
|
|
3869
3984
|
|
|
3985
|
+
const ROLE_LABELS = {
|
|
3986
|
+
owner: 'Dono',
|
|
3987
|
+
admin: 'Administrador',
|
|
3988
|
+
member: 'Membro'
|
|
3989
|
+
};
|
|
3990
|
+
|
|
3991
|
+
/**
|
|
3992
|
+
* @typedef {Object} UserInformationItem
|
|
3993
|
+
* @property {string} [id] - Stable key
|
|
3994
|
+
* @property {string} label - The row's text
|
|
3995
|
+
* @property {Function} [icon] - A Tabler icon component
|
|
3996
|
+
* @property {Function} onClick
|
|
3997
|
+
*
|
|
3998
|
+
* @typedef {Object} UserInformationPlan
|
|
3999
|
+
* @property {string} [name] - "Pro", "Starter"… Shown as the badge
|
|
4000
|
+
* @property {number} [used] - How many of the plan's resources are in use
|
|
4001
|
+
* @property {number|null} [limit] - The plan's ceiling; `null` hides the meter
|
|
4002
|
+
* @property {string} [unit] - What is counted, in the plural: "projetos"
|
|
4003
|
+
* @property {string} [actionLabel='Ver planos']
|
|
4004
|
+
* @property {Function} [onClick] - Opens the plans
|
|
4005
|
+
*
|
|
4006
|
+
* @typedef {Object} UserInformationOrganization
|
|
4007
|
+
* @property {string} name
|
|
4008
|
+
* @property {string} [role] - `owner`, `admin`, `member`, or already a label
|
|
4009
|
+
*/
|
|
4010
|
+
|
|
4011
|
+
/**
|
|
4012
|
+
* @param {Object} props
|
|
4013
|
+
* @param {Object} props.user - The signed-in user (`name`, `email`, `image`)
|
|
4014
|
+
* @param {Function} props.signOut
|
|
4015
|
+
* @param {Function} [props.onAccountClick]
|
|
4016
|
+
* @param {Function} [props.onBillingClick]
|
|
4017
|
+
* @param {UserInformationItem[]} [props.items] - The panel's own rows, shown in their own group
|
|
4018
|
+
* @param {UserInformationPlan} [props.plan] - The plan strip; omitted, the strip is not drawn
|
|
4019
|
+
* @param {UserInformationOrganization} [props.organization] - Where the person is acting
|
|
4020
|
+
* @param {boolean} [props.branded=true] - The "Protegido por Auth" line
|
|
4021
|
+
* @param {string} [props.accountLabel='Conta']
|
|
4022
|
+
* @param {string} [props.billingLabel='Assinatura']
|
|
4023
|
+
* @param {string} [props.signOutLabel='Sair']
|
|
4024
|
+
*/
|
|
3870
4025
|
function UserInformation({
|
|
3871
4026
|
user,
|
|
3872
4027
|
signOut,
|
|
3873
4028
|
onAccountClick,
|
|
3874
4029
|
onBillingClick,
|
|
4030
|
+
items = [],
|
|
4031
|
+
plan,
|
|
4032
|
+
organization,
|
|
4033
|
+
branded = true,
|
|
3875
4034
|
accountLabel = 'Conta',
|
|
3876
4035
|
billingLabel = 'Assinatura',
|
|
4036
|
+
signOutLabel = 'Sair',
|
|
4037
|
+
// Kept so existing calls keep working. The card has one density now: the
|
|
4038
|
+
// popover's. `padded={false}` still removes the outer padding.
|
|
3877
4039
|
padded = true,
|
|
3878
|
-
size
|
|
4040
|
+
size,
|
|
4041
|
+
// eslint-disable-line no-unused-vars -- accepted and ignored, see above
|
|
3879
4042
|
style,
|
|
3880
4043
|
...others
|
|
3881
4044
|
}) {
|
|
3882
4045
|
if (!user) return null;
|
|
4046
|
+
const name = (user.fullName || user.name || '').trim();
|
|
4047
|
+
const email = (user.primaryEmailAddress || user.email || '').trim();
|
|
3883
4048
|
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
const
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
md: 'sm',
|
|
3908
|
-
lg: 'md'
|
|
3909
|
-
};
|
|
3910
|
-
const widthMap = {
|
|
3911
|
-
sm: 280,
|
|
3912
|
-
md: 320,
|
|
3913
|
-
lg: 400
|
|
3914
|
-
};
|
|
3915
|
-
const name = user.fullName || user.name || 'User';
|
|
3916
|
-
const email = user.primaryEmailAddress || user.email || '';
|
|
3917
|
-
const initials = name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
|
3918
|
-
return /*#__PURE__*/jsx(Box, {
|
|
3919
|
-
p: padded ? gapMap[size] : 0,
|
|
3920
|
-
w: widthMap[size],
|
|
4049
|
+
/*
|
|
4050
|
+
* A name equal to the email's local part is not a name: it is what an
|
|
4051
|
+
* account born from a code gets by default, and showing it on top of the
|
|
4052
|
+
* email repeats the same fact. It counts as "no name".
|
|
4053
|
+
*/
|
|
4054
|
+
const hasRealName = Boolean(name) && name.toLowerCase() !== email.split('@')[0].toLowerCase() && name.toLowerCase() !== email.toLowerCase();
|
|
4055
|
+
const title = hasRealName ? name : email || name;
|
|
4056
|
+
const initials = hasRealName ? name.split(/\s+/).map(part => part[0]).join('').slice(0, 2).toUpperCase() : (email || name).charAt(0).toUpperCase();
|
|
4057
|
+
const baseRows = [onAccountClick && {
|
|
4058
|
+
id: 'account',
|
|
4059
|
+
label: accountLabel,
|
|
4060
|
+
icon: IconUser,
|
|
4061
|
+
onClick: onAccountClick
|
|
4062
|
+
}, onBillingClick && {
|
|
4063
|
+
id: 'billing',
|
|
4064
|
+
label: billingLabel,
|
|
4065
|
+
icon: IconCreditCard,
|
|
4066
|
+
onClick: onBillingClick
|
|
4067
|
+
}].filter(Boolean);
|
|
4068
|
+
const panelRows = items.filter(item => item && item.label && typeof item.onClick === 'function');
|
|
4069
|
+
return /*#__PURE__*/jsxs(Box, {
|
|
4070
|
+
w: 288,
|
|
4071
|
+
maw: "100%",
|
|
3921
4072
|
style: style,
|
|
3922
4073
|
...others,
|
|
3923
|
-
children: /*#__PURE__*/jsxs(
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
}
|
|
4074
|
+
children: [/*#__PURE__*/jsxs(Group, {
|
|
4075
|
+
wrap: "nowrap",
|
|
4076
|
+
gap: 10,
|
|
4077
|
+
px: padded ? 16 : 0,
|
|
4078
|
+
py: 14,
|
|
4079
|
+
children: [/*#__PURE__*/jsx(Avatar, {
|
|
4080
|
+
src: user.imageUrl || user.image,
|
|
4081
|
+
alt: "",
|
|
4082
|
+
size: 32,
|
|
4083
|
+
radius: 0,
|
|
4084
|
+
color: "gray.9",
|
|
4085
|
+
variant: "filled",
|
|
4086
|
+
styles: {
|
|
4087
|
+
root: {
|
|
4088
|
+
borderRadius: 0
|
|
3939
4089
|
},
|
|
3940
|
-
|
|
3941
|
-
|
|
4090
|
+
placeholder: {
|
|
4091
|
+
fontSize: 12,
|
|
4092
|
+
fontWeight: 800
|
|
4093
|
+
}
|
|
4094
|
+
},
|
|
4095
|
+
children: initials
|
|
4096
|
+
}), /*#__PURE__*/jsxs(Box, {
|
|
4097
|
+
style: {
|
|
4098
|
+
flex: 1,
|
|
4099
|
+
minWidth: 0
|
|
4100
|
+
},
|
|
4101
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
4102
|
+
fz: 13,
|
|
4103
|
+
fw: 800,
|
|
4104
|
+
c: "gray.9",
|
|
4105
|
+
lh: 1.3,
|
|
4106
|
+
truncate: "end",
|
|
4107
|
+
children: title
|
|
4108
|
+
}), hasRealName && email && /*#__PURE__*/jsx(Text, {
|
|
4109
|
+
fz: 12,
|
|
4110
|
+
fw: 500,
|
|
4111
|
+
c: "gray.5",
|
|
4112
|
+
lh: 1.4,
|
|
4113
|
+
truncate: "end",
|
|
4114
|
+
children: email
|
|
4115
|
+
})]
|
|
4116
|
+
})]
|
|
4117
|
+
}), (organization?.name || plan) && /*#__PURE__*/jsx(ContextStrip, {
|
|
4118
|
+
organization: organization,
|
|
4119
|
+
plan: plan,
|
|
4120
|
+
padded: padded
|
|
4121
|
+
}), /*#__PURE__*/jsxs(Box, {
|
|
4122
|
+
role: "menu",
|
|
4123
|
+
"aria-label": "Conta",
|
|
4124
|
+
onKeyDown: moveFocus,
|
|
4125
|
+
children: [baseRows.length > 0 && /*#__PURE__*/jsx(RowGroup, {
|
|
4126
|
+
rows: baseRows,
|
|
4127
|
+
hasDivider: !organization?.name && !plan
|
|
4128
|
+
}), panelRows.length > 0 && /*#__PURE__*/jsx(RowGroup, {
|
|
4129
|
+
rows: panelRows,
|
|
4130
|
+
hasDivider: true
|
|
4131
|
+
}), signOut && /*#__PURE__*/jsx(RowGroup, {
|
|
4132
|
+
rows: [{
|
|
4133
|
+
id: 'sign-out',
|
|
4134
|
+
label: signOutLabel,
|
|
4135
|
+
icon: IconLogout,
|
|
4136
|
+
onClick: signOut
|
|
4137
|
+
}],
|
|
4138
|
+
hasDivider: true
|
|
4139
|
+
})]
|
|
4140
|
+
}), branded && /*#__PURE__*/jsxs(Group, {
|
|
4141
|
+
justify: "center",
|
|
4142
|
+
gap: 4,
|
|
4143
|
+
py: 8,
|
|
4144
|
+
bg: "gray.1",
|
|
4145
|
+
style: {
|
|
4146
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
4147
|
+
},
|
|
4148
|
+
children: [/*#__PURE__*/jsx(Text, {
|
|
4149
|
+
fz: 11,
|
|
4150
|
+
fw: 500,
|
|
4151
|
+
c: "gray.5",
|
|
4152
|
+
children: "Protegido por"
|
|
4153
|
+
}), /*#__PURE__*/jsx(Text, {
|
|
4154
|
+
fz: 11,
|
|
4155
|
+
fw: 800,
|
|
4156
|
+
c: "gray.7",
|
|
4157
|
+
children: "Auth"
|
|
4158
|
+
})]
|
|
4159
|
+
})]
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
4162
|
+
|
|
4163
|
+
/** The organization, the role and the plan's usage, on the alternate surface. */
|
|
4164
|
+
function ContextStrip({
|
|
4165
|
+
organization,
|
|
4166
|
+
plan,
|
|
4167
|
+
padded
|
|
4168
|
+
}) {
|
|
4169
|
+
const role = organization?.role ? ROLE_LABELS[organization.role] || organization.role : null;
|
|
4170
|
+
const hasMeter = plan && Number.isFinite(plan.used) && Number.isFinite(plan.limit) && plan.limit > 0;
|
|
4171
|
+
const ratio = hasMeter ? Math.min(1, Math.max(0, plan.used / plan.limit)) : 0;
|
|
4172
|
+
return /*#__PURE__*/jsxs(Stack, {
|
|
4173
|
+
gap: 8,
|
|
4174
|
+
px: padded ? 16 : 0,
|
|
4175
|
+
py: 12,
|
|
4176
|
+
bg: "gray.1",
|
|
4177
|
+
style: {
|
|
4178
|
+
borderTop: '1px solid var(--mantine-color-gray-2)',
|
|
4179
|
+
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
|
4180
|
+
},
|
|
4181
|
+
children: [(organization?.name || plan?.name) && /*#__PURE__*/jsxs(Group, {
|
|
4182
|
+
gap: 8,
|
|
4183
|
+
wrap: "nowrap",
|
|
4184
|
+
children: [organization?.name && /*#__PURE__*/jsxs(Fragment, {
|
|
4185
|
+
children: [/*#__PURE__*/jsx(IconBuilding, {
|
|
4186
|
+
size: 14,
|
|
4187
|
+
stroke: 1.5,
|
|
4188
|
+
style: {
|
|
4189
|
+
flex: 'none',
|
|
4190
|
+
color: 'var(--mantine-color-gray-5)'
|
|
4191
|
+
}
|
|
4192
|
+
}), /*#__PURE__*/jsx(Text, {
|
|
4193
|
+
fz: 12,
|
|
4194
|
+
fw: 800,
|
|
4195
|
+
c: "gray.9",
|
|
4196
|
+
truncate: "end",
|
|
3942
4197
|
style: {
|
|
3943
|
-
|
|
3944
|
-
overflow: 'hidden'
|
|
4198
|
+
minWidth: 0
|
|
3945
4199
|
},
|
|
3946
|
-
children:
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
}
|
|
3954
|
-
|
|
3955
|
-
c: "gray.5",
|
|
3956
|
-
truncate: "end",
|
|
3957
|
-
lh: 1.1,
|
|
3958
|
-
children: email
|
|
3959
|
-
})]
|
|
3960
|
-
}), /*#__PURE__*/jsx(ActionIcon, {
|
|
3961
|
-
size: btnSizeMap[size],
|
|
3962
|
-
onClick: signOut,
|
|
3963
|
-
children: /*#__PURE__*/jsx(IconLogout, {
|
|
3964
|
-
size: 16,
|
|
3965
|
-
stroke: 1.5
|
|
3966
|
-
})
|
|
3967
|
-
})]
|
|
3968
|
-
}), /*#__PURE__*/jsxs(Group, {
|
|
3969
|
-
grow: true,
|
|
3970
|
-
children: [/*#__PURE__*/jsx(Button, {
|
|
3971
|
-
variant: "default",
|
|
3972
|
-
size: btnSizeMap[size],
|
|
3973
|
-
leftSection: /*#__PURE__*/jsx(IconSettings, {
|
|
3974
|
-
size: 16,
|
|
3975
|
-
stroke: 1.5
|
|
3976
|
-
}),
|
|
3977
|
-
onClick: onAccountClick,
|
|
3978
|
-
children: accountLabel
|
|
3979
|
-
}), /*#__PURE__*/jsx(Button, {
|
|
3980
|
-
variant: "default",
|
|
3981
|
-
size: btnSizeMap[size],
|
|
3982
|
-
leftSection: /*#__PURE__*/jsx(IconCreditCard, {
|
|
3983
|
-
size: 16,
|
|
3984
|
-
stroke: 1.5
|
|
3985
|
-
}),
|
|
3986
|
-
onClick: onBillingClick,
|
|
3987
|
-
children: billingLabel
|
|
4200
|
+
children: organization.name
|
|
4201
|
+
}), role && /*#__PURE__*/jsxs(Text, {
|
|
4202
|
+
fz: 12,
|
|
4203
|
+
fw: 500,
|
|
4204
|
+
c: "gray.5",
|
|
4205
|
+
style: {
|
|
4206
|
+
flex: 'none'
|
|
4207
|
+
},
|
|
4208
|
+
children: ["\xB7 ", role]
|
|
3988
4209
|
})]
|
|
4210
|
+
}), plan?.name && /*#__PURE__*/jsx(Text, {
|
|
4211
|
+
component: "span",
|
|
4212
|
+
fz: 10,
|
|
4213
|
+
fw: 800,
|
|
4214
|
+
tt: "uppercase",
|
|
4215
|
+
lts: "1.5px",
|
|
4216
|
+
c: "white",
|
|
4217
|
+
bg: "gray.9",
|
|
4218
|
+
px: 6,
|
|
4219
|
+
lh: 1.6,
|
|
4220
|
+
ml: "auto",
|
|
4221
|
+
style: {
|
|
4222
|
+
flex: 'none'
|
|
4223
|
+
},
|
|
4224
|
+
children: plan.name
|
|
4225
|
+
})]
|
|
4226
|
+
}), hasMeter && /*#__PURE__*/jsxs(Fragment, {
|
|
4227
|
+
children: [/*#__PURE__*/jsx(Box, {
|
|
4228
|
+
h: 4,
|
|
4229
|
+
bg: "gray.2",
|
|
4230
|
+
role: "meter",
|
|
4231
|
+
"aria-valuemin": 0,
|
|
4232
|
+
"aria-valuemax": plan.limit,
|
|
4233
|
+
"aria-valuenow": plan.used,
|
|
4234
|
+
"aria-label": plan.unit ? `${plan.unit} em uso` : 'Uso do plano',
|
|
4235
|
+
children: /*#__PURE__*/jsx(Box, {
|
|
4236
|
+
h: "100%",
|
|
4237
|
+
w: `${ratio * 100}%`,
|
|
4238
|
+
bg: "gray.9"
|
|
4239
|
+
})
|
|
3989
4240
|
}), /*#__PURE__*/jsxs(Group, {
|
|
3990
|
-
justify: "
|
|
3991
|
-
gap:
|
|
3992
|
-
|
|
4241
|
+
justify: "space-between",
|
|
4242
|
+
gap: 8,
|
|
4243
|
+
wrap: "nowrap",
|
|
3993
4244
|
children: [/*#__PURE__*/jsx(Text, {
|
|
3994
|
-
|
|
4245
|
+
fz: 12,
|
|
4246
|
+
fw: 500,
|
|
3995
4247
|
c: "gray.6",
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
c: "dark.9",
|
|
4007
|
-
children: "Auth"
|
|
4008
|
-
})]
|
|
4248
|
+
children: `${plan.used} de ${plan.limit}${plan.unit ? ` ${plan.unit}` : ''}`
|
|
4249
|
+
}), plan.onClick && /*#__PURE__*/jsx(Anchor, {
|
|
4250
|
+
component: "button",
|
|
4251
|
+
type: "button",
|
|
4252
|
+
fz: 12,
|
|
4253
|
+
fw: 700,
|
|
4254
|
+
c: "gray.9",
|
|
4255
|
+
underline: "always",
|
|
4256
|
+
onClick: plan.onClick,
|
|
4257
|
+
children: plan.actionLabel || 'Ver planos'
|
|
4009
4258
|
})]
|
|
4010
4259
|
})]
|
|
4011
|
-
})
|
|
4260
|
+
})]
|
|
4261
|
+
});
|
|
4262
|
+
}
|
|
4263
|
+
function RowGroup({
|
|
4264
|
+
rows,
|
|
4265
|
+
hasDivider
|
|
4266
|
+
}) {
|
|
4267
|
+
return /*#__PURE__*/jsx(Stack, {
|
|
4268
|
+
gap: 0,
|
|
4269
|
+
p: 6,
|
|
4270
|
+
style: hasDivider ? {
|
|
4271
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
4272
|
+
} : undefined,
|
|
4273
|
+
children: rows.map(row => /*#__PURE__*/jsx(Row, {
|
|
4274
|
+
...row
|
|
4275
|
+
}, row.id || row.label))
|
|
4012
4276
|
});
|
|
4013
4277
|
}
|
|
4278
|
+
function Row({
|
|
4279
|
+
label,
|
|
4280
|
+
icon: Icon,
|
|
4281
|
+
onClick
|
|
4282
|
+
}) {
|
|
4283
|
+
return /*#__PURE__*/jsxs(UnstyledButton, {
|
|
4284
|
+
role: "menuitem",
|
|
4285
|
+
onClick: onClick,
|
|
4286
|
+
px: 10,
|
|
4287
|
+
py: 7,
|
|
4288
|
+
w: "100%",
|
|
4289
|
+
style: {
|
|
4290
|
+
display: 'flex',
|
|
4291
|
+
alignItems: 'center',
|
|
4292
|
+
gap: 10,
|
|
4293
|
+
borderRadius: 0
|
|
4294
|
+
}
|
|
4295
|
+
/*
|
|
4296
|
+
* Hover and keyboard focus share the same surface: a row reached
|
|
4297
|
+
* with the arrows must look exactly like the one under the mouse.
|
|
4298
|
+
*/,
|
|
4299
|
+
onMouseEnter: event => event.currentTarget.style.background = 'var(--mantine-color-gray-1)',
|
|
4300
|
+
onMouseLeave: event => event.currentTarget.style.background = 'transparent',
|
|
4301
|
+
onFocus: event => event.currentTarget.style.background = 'var(--mantine-color-gray-1)',
|
|
4302
|
+
onBlur: event => event.currentTarget.style.background = 'transparent',
|
|
4303
|
+
children: [Icon && /*#__PURE__*/jsx(Icon, {
|
|
4304
|
+
size: 16,
|
|
4305
|
+
stroke: 1.5,
|
|
4306
|
+
style: {
|
|
4307
|
+
flex: 'none',
|
|
4308
|
+
color: 'var(--mantine-color-gray-5)'
|
|
4309
|
+
}
|
|
4310
|
+
}), /*#__PURE__*/jsx(Text, {
|
|
4311
|
+
fz: 12,
|
|
4312
|
+
fw: 500,
|
|
4313
|
+
c: "gray.9",
|
|
4314
|
+
truncate: "end",
|
|
4315
|
+
children: label
|
|
4316
|
+
})]
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
/*
|
|
4321
|
+
* Arrow keys walk the rows, Home and End jump to the ends — the WAI-ARIA menu
|
|
4322
|
+
* pattern. Tab still leaves the card, so it never traps focus inside a popover
|
|
4323
|
+
* the panel owns.
|
|
4324
|
+
*/
|
|
4325
|
+
function moveFocus(event) {
|
|
4326
|
+
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
4327
|
+
if (!keys.includes(event.key)) return;
|
|
4328
|
+
const rows = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
|
|
4329
|
+
if (rows.length === 0) return;
|
|
4330
|
+
event.preventDefault();
|
|
4331
|
+
const current = rows.indexOf(document.activeElement);
|
|
4332
|
+
const last = rows.length - 1;
|
|
4333
|
+
const next = event.key === 'Home' ? 0 : event.key === 'End' ? last : event.key === 'ArrowDown' ? current < last ? current + 1 : 0 : current > 0 ? current - 1 : last;
|
|
4334
|
+
rows[next].focus();
|
|
4335
|
+
}
|
|
4014
4336
|
|
|
4015
4337
|
/**
|
|
4016
4338
|
* Renderiza children apenas quando o usuário está autenticado
|
|
@@ -4100,5 +4422,5 @@ function SignOutButton({
|
|
|
4100
4422
|
});
|
|
4101
4423
|
}
|
|
4102
4424
|
|
|
4103
|
-
export { AuthCard, AuthLoaded, AuthLoading, AuthProvider, GuestOnly, IDENTITY_CHANGED_EVENT, MAX_RECENT_ACCOUNTS, Protect, RECENT_ACCOUNTS_KEY, SignIn, SignInButton, SignOutButton, SignedIn, SignedOut, SocialButtons, TOKEN_STORAGE_KEY, UserInformation, UserProfile, Wordmark, announceIdentityChange, applyRedirect, clearIdentitySwitching, configure, consumeSocialError, consumeSocialToken, decodeJWT, endImpersonation, forgetAccount, getApiUrl, getApplicationInfo, getCurrentUser, getLinkedProviders, getRedirectFromLocation, getSession, getSocialProviders, isAuthenticated, isIdentitySwitching, isInternal, listRecentAccounts, listSessions, markIdentitySwitching, pollCode, refreshToken, rememberAccount, requestCode, resolveRedirect, revokeOtherSessions, revokeSession, setStoredToken, shouldSignOutOn401, signOut, startSocialLink, startSocialSignIn, unlinkSocialProvider, updateProfile, useApplicationLogo, useAuth, useAuthLoading, useAuthStore, useCheckToken, useImpersonation, useSession, useSessions, useSignIn, useSignOut, useUser, verifyCode };
|
|
4425
|
+
export { AuthCard, AuthLoaded, AuthLoading, AuthProvider, GuestOnly, IDENTITY_CHANGED_EVENT, MAX_RECENT_ACCOUNTS, Protect, RECENT_ACCOUNTS_KEY, SignIn, SignInButton, SignOutButton, SignedIn, SignedOut, SocialButtons, TOKEN_STORAGE_KEY, UserInformation, UserProfile, Wordmark, adoptRecentAccounts, announceIdentityChange, applyRedirect, clearIdentitySwitching, configure, consumeSocialError, consumeSocialToken, decodeJWT, deleteRecentAccount, endImpersonation, fetchRecentAccounts, forgetAccount, getApiUrl, getApplicationInfo, getCurrentUser, getLinkedProviders, getRedirectFromLocation, getSession, getSocialProviders, isAuthenticated, isIdentitySwitching, isInternal, listRecentAccounts, listSessions, markIdentitySwitching, pollCode, refreshToken, rememberAccount, requestCode, resolveRedirect, revokeOtherSessions, revokeSession, saveRecentAccount, setStoredToken, shouldSignOutOn401, signOut, startSocialLink, startSocialSignIn, unlinkSocialProvider, updateProfile, useApplicationLogo, useAuth, useAuthLoading, useAuthStore, useCheckToken, useImpersonation, useSession, useSessions, useSignIn, useSignOut, useUser, verifyCode };
|
|
4104
4426
|
//# sourceMappingURL=index.esm.js.map
|