@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.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();
|
|
@@ -2784,6 +2867,32 @@ function SignIn({
|
|
|
2784
2867
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- redirectOrigins enters through the serialized key above
|
|
2785
2868
|
}, [authLoading, user, authenticatedRedirect, handleRedirect, redirectOriginsKey, navigate]);
|
|
2786
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
|
+
|
|
2787
2896
|
// Step 1 — ask for the code.
|
|
2788
2897
|
const handleRequest = async values => {
|
|
2789
2898
|
if (sending) return false;
|
|
@@ -2843,6 +2952,9 @@ function SignIn({
|
|
|
2843
2952
|
const handleForget = account => {
|
|
2844
2953
|
const next = forgetAccount(account.email);
|
|
2845
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);
|
|
2846
2958
|
if (next.length === 0) setIsManaging(false);
|
|
2847
2959
|
};
|
|
2848
2960
|
|
|
@@ -2871,7 +2983,10 @@ function SignIn({
|
|
|
2871
2983
|
// Only now, with a session: an email that never received a valid
|
|
2872
2984
|
// code never becomes a suggestion. Written before the redirect,
|
|
2873
2985
|
// which may unmount this screen.
|
|
2874
|
-
if (recentAccounts)
|
|
2986
|
+
if (recentAccounts) {
|
|
2987
|
+
setAccounts(rememberAccount(sentTo, 'code'));
|
|
2988
|
+
saveRecentAccount('code');
|
|
2989
|
+
}
|
|
2875
2990
|
const target = handleRedirect ? getRedirectFromLocation(redirectOrigins) : null;
|
|
2876
2991
|
if (target) applyRedirect(target, navigate);
|
|
2877
2992
|
onSuccess?.(result?.user ?? null, {
|
|
@@ -3869,150 +3984,357 @@ function UserProfile({
|
|
|
3869
3984
|
});
|
|
3870
3985
|
}
|
|
3871
3986
|
|
|
3987
|
+
const ROLE_LABELS = {
|
|
3988
|
+
owner: 'Dono',
|
|
3989
|
+
admin: 'Administrador',
|
|
3990
|
+
member: 'Membro'
|
|
3991
|
+
};
|
|
3992
|
+
|
|
3993
|
+
/**
|
|
3994
|
+
* @typedef {Object} UserInformationItem
|
|
3995
|
+
* @property {string} [id] - Stable key
|
|
3996
|
+
* @property {string} label - The row's text
|
|
3997
|
+
* @property {Function} [icon] - A Tabler icon component
|
|
3998
|
+
* @property {Function} onClick
|
|
3999
|
+
*
|
|
4000
|
+
* @typedef {Object} UserInformationPlan
|
|
4001
|
+
* @property {string} [name] - "Pro", "Starter"… Shown as the badge
|
|
4002
|
+
* @property {number} [used] - How many of the plan's resources are in use
|
|
4003
|
+
* @property {number|null} [limit] - The plan's ceiling; `null` hides the meter
|
|
4004
|
+
* @property {string} [unit] - What is counted, in the plural: "projetos"
|
|
4005
|
+
* @property {string} [actionLabel='Ver planos']
|
|
4006
|
+
* @property {Function} [onClick] - Opens the plans
|
|
4007
|
+
*
|
|
4008
|
+
* @typedef {Object} UserInformationOrganization
|
|
4009
|
+
* @property {string} name
|
|
4010
|
+
* @property {string} [role] - `owner`, `admin`, `member`, or already a label
|
|
4011
|
+
*/
|
|
4012
|
+
|
|
4013
|
+
/**
|
|
4014
|
+
* @param {Object} props
|
|
4015
|
+
* @param {Object} props.user - The signed-in user (`name`, `email`, `image`)
|
|
4016
|
+
* @param {Function} props.signOut
|
|
4017
|
+
* @param {Function} [props.onAccountClick]
|
|
4018
|
+
* @param {Function} [props.onBillingClick]
|
|
4019
|
+
* @param {UserInformationItem[]} [props.items] - The panel's own rows, shown in their own group
|
|
4020
|
+
* @param {UserInformationPlan} [props.plan] - The plan strip; omitted, the strip is not drawn
|
|
4021
|
+
* @param {UserInformationOrganization} [props.organization] - Where the person is acting
|
|
4022
|
+
* @param {boolean} [props.branded=true] - The "Protegido por Auth" line
|
|
4023
|
+
* @param {string} [props.accountLabel='Conta']
|
|
4024
|
+
* @param {string} [props.billingLabel='Assinatura']
|
|
4025
|
+
* @param {string} [props.signOutLabel='Sair']
|
|
4026
|
+
*/
|
|
3872
4027
|
function UserInformation({
|
|
3873
4028
|
user,
|
|
3874
4029
|
signOut,
|
|
3875
4030
|
onAccountClick,
|
|
3876
4031
|
onBillingClick,
|
|
4032
|
+
items = [],
|
|
4033
|
+
plan,
|
|
4034
|
+
organization,
|
|
4035
|
+
branded = true,
|
|
3877
4036
|
accountLabel = 'Conta',
|
|
3878
4037
|
billingLabel = 'Assinatura',
|
|
4038
|
+
signOutLabel = 'Sair',
|
|
4039
|
+
// Kept so existing calls keep working. The card has one density now: the
|
|
4040
|
+
// popover's. `padded={false}` still removes the outer padding.
|
|
3879
4041
|
padded = true,
|
|
3880
|
-
size
|
|
4042
|
+
size,
|
|
4043
|
+
// eslint-disable-line no-unused-vars -- accepted and ignored, see above
|
|
3881
4044
|
style,
|
|
3882
4045
|
...others
|
|
3883
4046
|
}) {
|
|
3884
4047
|
if (!user) return null;
|
|
4048
|
+
const name = (user.fullName || user.name || '').trim();
|
|
4049
|
+
const email = (user.primaryEmailAddress || user.email || '').trim();
|
|
3885
4050
|
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
const
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
md: 'sm',
|
|
3910
|
-
lg: 'md'
|
|
3911
|
-
};
|
|
3912
|
-
const widthMap = {
|
|
3913
|
-
sm: 280,
|
|
3914
|
-
md: 320,
|
|
3915
|
-
lg: 400
|
|
3916
|
-
};
|
|
3917
|
-
const name = user.fullName || user.name || 'User';
|
|
3918
|
-
const email = user.primaryEmailAddress || user.email || '';
|
|
3919
|
-
const initials = name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
|
3920
|
-
return /*#__PURE__*/jsxRuntime.jsx(core.Box, {
|
|
3921
|
-
p: padded ? gapMap[size] : 0,
|
|
3922
|
-
w: widthMap[size],
|
|
4051
|
+
/*
|
|
4052
|
+
* A name equal to the email's local part is not a name: it is what an
|
|
4053
|
+
* account born from a code gets by default, and showing it on top of the
|
|
4054
|
+
* email repeats the same fact. It counts as "no name".
|
|
4055
|
+
*/
|
|
4056
|
+
const hasRealName = Boolean(name) && name.toLowerCase() !== email.split('@')[0].toLowerCase() && name.toLowerCase() !== email.toLowerCase();
|
|
4057
|
+
const title = hasRealName ? name : email || name;
|
|
4058
|
+
const initials = hasRealName ? name.split(/\s+/).map(part => part[0]).join('').slice(0, 2).toUpperCase() : (email || name).charAt(0).toUpperCase();
|
|
4059
|
+
const baseRows = [onAccountClick && {
|
|
4060
|
+
id: 'account',
|
|
4061
|
+
label: accountLabel,
|
|
4062
|
+
icon: iconsReact.IconUser,
|
|
4063
|
+
onClick: onAccountClick
|
|
4064
|
+
}, onBillingClick && {
|
|
4065
|
+
id: 'billing',
|
|
4066
|
+
label: billingLabel,
|
|
4067
|
+
icon: iconsReact.IconCreditCard,
|
|
4068
|
+
onClick: onBillingClick
|
|
4069
|
+
}].filter(Boolean);
|
|
4070
|
+
const panelRows = items.filter(item => item && item.label && typeof item.onClick === 'function');
|
|
4071
|
+
return /*#__PURE__*/jsxRuntime.jsxs(core.Box, {
|
|
4072
|
+
w: 288,
|
|
4073
|
+
maw: "100%",
|
|
3923
4074
|
style: style,
|
|
3924
4075
|
...others,
|
|
3925
|
-
children: /*#__PURE__*/jsxRuntime.jsxs(core.
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
}
|
|
4076
|
+
children: [/*#__PURE__*/jsxRuntime.jsxs(core.Group, {
|
|
4077
|
+
wrap: "nowrap",
|
|
4078
|
+
gap: 10,
|
|
4079
|
+
px: padded ? 16 : 0,
|
|
4080
|
+
py: 14,
|
|
4081
|
+
children: [/*#__PURE__*/jsxRuntime.jsx(core.Avatar, {
|
|
4082
|
+
src: user.imageUrl || user.image,
|
|
4083
|
+
alt: "",
|
|
4084
|
+
size: 32,
|
|
4085
|
+
radius: 0,
|
|
4086
|
+
color: "gray.9",
|
|
4087
|
+
variant: "filled",
|
|
4088
|
+
styles: {
|
|
4089
|
+
root: {
|
|
4090
|
+
borderRadius: 0
|
|
3941
4091
|
},
|
|
3942
|
-
|
|
3943
|
-
|
|
4092
|
+
placeholder: {
|
|
4093
|
+
fontSize: 12,
|
|
4094
|
+
fontWeight: 800
|
|
4095
|
+
}
|
|
4096
|
+
},
|
|
4097
|
+
children: initials
|
|
4098
|
+
}), /*#__PURE__*/jsxRuntime.jsxs(core.Box, {
|
|
4099
|
+
style: {
|
|
4100
|
+
flex: 1,
|
|
4101
|
+
minWidth: 0
|
|
4102
|
+
},
|
|
4103
|
+
children: [/*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
4104
|
+
fz: 13,
|
|
4105
|
+
fw: 800,
|
|
4106
|
+
c: "gray.9",
|
|
4107
|
+
lh: 1.3,
|
|
4108
|
+
truncate: "end",
|
|
4109
|
+
children: title
|
|
4110
|
+
}), hasRealName && email && /*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
4111
|
+
fz: 12,
|
|
4112
|
+
fw: 500,
|
|
4113
|
+
c: "gray.5",
|
|
4114
|
+
lh: 1.4,
|
|
4115
|
+
truncate: "end",
|
|
4116
|
+
children: email
|
|
4117
|
+
})]
|
|
4118
|
+
})]
|
|
4119
|
+
}), (organization?.name || plan) && /*#__PURE__*/jsxRuntime.jsx(ContextStrip, {
|
|
4120
|
+
organization: organization,
|
|
4121
|
+
plan: plan,
|
|
4122
|
+
padded: padded
|
|
4123
|
+
}), /*#__PURE__*/jsxRuntime.jsxs(core.Box, {
|
|
4124
|
+
role: "menu",
|
|
4125
|
+
"aria-label": "Conta",
|
|
4126
|
+
onKeyDown: moveFocus,
|
|
4127
|
+
children: [baseRows.length > 0 && /*#__PURE__*/jsxRuntime.jsx(RowGroup, {
|
|
4128
|
+
rows: baseRows,
|
|
4129
|
+
hasDivider: !organization?.name && !plan
|
|
4130
|
+
}), panelRows.length > 0 && /*#__PURE__*/jsxRuntime.jsx(RowGroup, {
|
|
4131
|
+
rows: panelRows,
|
|
4132
|
+
hasDivider: true
|
|
4133
|
+
}), signOut && /*#__PURE__*/jsxRuntime.jsx(RowGroup, {
|
|
4134
|
+
rows: [{
|
|
4135
|
+
id: 'sign-out',
|
|
4136
|
+
label: signOutLabel,
|
|
4137
|
+
icon: iconsReact.IconLogout,
|
|
4138
|
+
onClick: signOut
|
|
4139
|
+
}],
|
|
4140
|
+
hasDivider: true
|
|
4141
|
+
})]
|
|
4142
|
+
}), branded && /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
|
|
4143
|
+
justify: "center",
|
|
4144
|
+
gap: 4,
|
|
4145
|
+
py: 8,
|
|
4146
|
+
bg: "gray.1",
|
|
4147
|
+
style: {
|
|
4148
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
4149
|
+
},
|
|
4150
|
+
children: [/*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
4151
|
+
fz: 11,
|
|
4152
|
+
fw: 500,
|
|
4153
|
+
c: "gray.5",
|
|
4154
|
+
children: "Protegido por"
|
|
4155
|
+
}), /*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
4156
|
+
fz: 11,
|
|
4157
|
+
fw: 800,
|
|
4158
|
+
c: "gray.7",
|
|
4159
|
+
children: "Auth"
|
|
4160
|
+
})]
|
|
4161
|
+
})]
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
/** The organization, the role and the plan's usage, on the alternate surface. */
|
|
4166
|
+
function ContextStrip({
|
|
4167
|
+
organization,
|
|
4168
|
+
plan,
|
|
4169
|
+
padded
|
|
4170
|
+
}) {
|
|
4171
|
+
const role = organization?.role ? ROLE_LABELS[organization.role] || organization.role : null;
|
|
4172
|
+
const hasMeter = plan && Number.isFinite(plan.used) && Number.isFinite(plan.limit) && plan.limit > 0;
|
|
4173
|
+
const ratio = hasMeter ? Math.min(1, Math.max(0, plan.used / plan.limit)) : 0;
|
|
4174
|
+
return /*#__PURE__*/jsxRuntime.jsxs(core.Stack, {
|
|
4175
|
+
gap: 8,
|
|
4176
|
+
px: padded ? 16 : 0,
|
|
4177
|
+
py: 12,
|
|
4178
|
+
bg: "gray.1",
|
|
4179
|
+
style: {
|
|
4180
|
+
borderTop: '1px solid var(--mantine-color-gray-2)',
|
|
4181
|
+
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
|
4182
|
+
},
|
|
4183
|
+
children: [(organization?.name || plan?.name) && /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
|
|
4184
|
+
gap: 8,
|
|
4185
|
+
wrap: "nowrap",
|
|
4186
|
+
children: [organization?.name && /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
4187
|
+
children: [/*#__PURE__*/jsxRuntime.jsx(iconsReact.IconBuilding, {
|
|
4188
|
+
size: 14,
|
|
4189
|
+
stroke: 1.5,
|
|
4190
|
+
style: {
|
|
4191
|
+
flex: 'none',
|
|
4192
|
+
color: 'var(--mantine-color-gray-5)'
|
|
4193
|
+
}
|
|
4194
|
+
}), /*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
4195
|
+
fz: 12,
|
|
4196
|
+
fw: 800,
|
|
4197
|
+
c: "gray.9",
|
|
4198
|
+
truncate: "end",
|
|
3944
4199
|
style: {
|
|
3945
|
-
|
|
3946
|
-
overflow: 'hidden'
|
|
4200
|
+
minWidth: 0
|
|
3947
4201
|
},
|
|
3948
|
-
children:
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
}
|
|
3956
|
-
|
|
3957
|
-
c: "gray.5",
|
|
3958
|
-
truncate: "end",
|
|
3959
|
-
lh: 1.1,
|
|
3960
|
-
children: email
|
|
3961
|
-
})]
|
|
3962
|
-
}), /*#__PURE__*/jsxRuntime.jsx(core.ActionIcon, {
|
|
3963
|
-
size: btnSizeMap[size],
|
|
3964
|
-
onClick: signOut,
|
|
3965
|
-
children: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconLogout, {
|
|
3966
|
-
size: 16,
|
|
3967
|
-
stroke: 1.5
|
|
3968
|
-
})
|
|
3969
|
-
})]
|
|
3970
|
-
}), /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
|
|
3971
|
-
grow: true,
|
|
3972
|
-
children: [/*#__PURE__*/jsxRuntime.jsx(core.Button, {
|
|
3973
|
-
variant: "default",
|
|
3974
|
-
size: btnSizeMap[size],
|
|
3975
|
-
leftSection: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconSettings, {
|
|
3976
|
-
size: 16,
|
|
3977
|
-
stroke: 1.5
|
|
3978
|
-
}),
|
|
3979
|
-
onClick: onAccountClick,
|
|
3980
|
-
children: accountLabel
|
|
3981
|
-
}), /*#__PURE__*/jsxRuntime.jsx(core.Button, {
|
|
3982
|
-
variant: "default",
|
|
3983
|
-
size: btnSizeMap[size],
|
|
3984
|
-
leftSection: /*#__PURE__*/jsxRuntime.jsx(iconsReact.IconCreditCard, {
|
|
3985
|
-
size: 16,
|
|
3986
|
-
stroke: 1.5
|
|
3987
|
-
}),
|
|
3988
|
-
onClick: onBillingClick,
|
|
3989
|
-
children: billingLabel
|
|
4202
|
+
children: organization.name
|
|
4203
|
+
}), role && /*#__PURE__*/jsxRuntime.jsxs(core.Text, {
|
|
4204
|
+
fz: 12,
|
|
4205
|
+
fw: 500,
|
|
4206
|
+
c: "gray.5",
|
|
4207
|
+
style: {
|
|
4208
|
+
flex: 'none'
|
|
4209
|
+
},
|
|
4210
|
+
children: ["\xB7 ", role]
|
|
3990
4211
|
})]
|
|
4212
|
+
}), plan?.name && /*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
4213
|
+
component: "span",
|
|
4214
|
+
fz: 10,
|
|
4215
|
+
fw: 800,
|
|
4216
|
+
tt: "uppercase",
|
|
4217
|
+
lts: "1.5px",
|
|
4218
|
+
c: "white",
|
|
4219
|
+
bg: "gray.9",
|
|
4220
|
+
px: 6,
|
|
4221
|
+
lh: 1.6,
|
|
4222
|
+
ml: "auto",
|
|
4223
|
+
style: {
|
|
4224
|
+
flex: 'none'
|
|
4225
|
+
},
|
|
4226
|
+
children: plan.name
|
|
4227
|
+
})]
|
|
4228
|
+
}), hasMeter && /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
4229
|
+
children: [/*#__PURE__*/jsxRuntime.jsx(core.Box, {
|
|
4230
|
+
h: 4,
|
|
4231
|
+
bg: "gray.2",
|
|
4232
|
+
role: "meter",
|
|
4233
|
+
"aria-valuemin": 0,
|
|
4234
|
+
"aria-valuemax": plan.limit,
|
|
4235
|
+
"aria-valuenow": plan.used,
|
|
4236
|
+
"aria-label": plan.unit ? `${plan.unit} em uso` : 'Uso do plano',
|
|
4237
|
+
children: /*#__PURE__*/jsxRuntime.jsx(core.Box, {
|
|
4238
|
+
h: "100%",
|
|
4239
|
+
w: `${ratio * 100}%`,
|
|
4240
|
+
bg: "gray.9"
|
|
4241
|
+
})
|
|
3991
4242
|
}), /*#__PURE__*/jsxRuntime.jsxs(core.Group, {
|
|
3992
|
-
justify: "
|
|
3993
|
-
gap:
|
|
3994
|
-
|
|
4243
|
+
justify: "space-between",
|
|
4244
|
+
gap: 8,
|
|
4245
|
+
wrap: "nowrap",
|
|
3995
4246
|
children: [/*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
3996
|
-
|
|
4247
|
+
fz: 12,
|
|
4248
|
+
fw: 500,
|
|
3997
4249
|
c: "gray.6",
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
c: "dark.9",
|
|
4009
|
-
children: "Auth"
|
|
4010
|
-
})]
|
|
4250
|
+
children: `${plan.used} de ${plan.limit}${plan.unit ? ` ${plan.unit}` : ''}`
|
|
4251
|
+
}), plan.onClick && /*#__PURE__*/jsxRuntime.jsx(core.Anchor, {
|
|
4252
|
+
component: "button",
|
|
4253
|
+
type: "button",
|
|
4254
|
+
fz: 12,
|
|
4255
|
+
fw: 700,
|
|
4256
|
+
c: "gray.9",
|
|
4257
|
+
underline: "always",
|
|
4258
|
+
onClick: plan.onClick,
|
|
4259
|
+
children: plan.actionLabel || 'Ver planos'
|
|
4011
4260
|
})]
|
|
4012
4261
|
})]
|
|
4013
|
-
})
|
|
4262
|
+
})]
|
|
4263
|
+
});
|
|
4264
|
+
}
|
|
4265
|
+
function RowGroup({
|
|
4266
|
+
rows,
|
|
4267
|
+
hasDivider
|
|
4268
|
+
}) {
|
|
4269
|
+
return /*#__PURE__*/jsxRuntime.jsx(core.Stack, {
|
|
4270
|
+
gap: 0,
|
|
4271
|
+
p: 6,
|
|
4272
|
+
style: hasDivider ? {
|
|
4273
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
4274
|
+
} : undefined,
|
|
4275
|
+
children: rows.map(row => /*#__PURE__*/jsxRuntime.jsx(Row, {
|
|
4276
|
+
...row
|
|
4277
|
+
}, row.id || row.label))
|
|
4014
4278
|
});
|
|
4015
4279
|
}
|
|
4280
|
+
function Row({
|
|
4281
|
+
label,
|
|
4282
|
+
icon: Icon,
|
|
4283
|
+
onClick
|
|
4284
|
+
}) {
|
|
4285
|
+
return /*#__PURE__*/jsxRuntime.jsxs(core.UnstyledButton, {
|
|
4286
|
+
role: "menuitem",
|
|
4287
|
+
onClick: onClick,
|
|
4288
|
+
px: 10,
|
|
4289
|
+
py: 7,
|
|
4290
|
+
w: "100%",
|
|
4291
|
+
style: {
|
|
4292
|
+
display: 'flex',
|
|
4293
|
+
alignItems: 'center',
|
|
4294
|
+
gap: 10,
|
|
4295
|
+
borderRadius: 0
|
|
4296
|
+
}
|
|
4297
|
+
/*
|
|
4298
|
+
* Hover and keyboard focus share the same surface: a row reached
|
|
4299
|
+
* with the arrows must look exactly like the one under the mouse.
|
|
4300
|
+
*/,
|
|
4301
|
+
onMouseEnter: event => event.currentTarget.style.background = 'var(--mantine-color-gray-1)',
|
|
4302
|
+
onMouseLeave: event => event.currentTarget.style.background = 'transparent',
|
|
4303
|
+
onFocus: event => event.currentTarget.style.background = 'var(--mantine-color-gray-1)',
|
|
4304
|
+
onBlur: event => event.currentTarget.style.background = 'transparent',
|
|
4305
|
+
children: [Icon && /*#__PURE__*/jsxRuntime.jsx(Icon, {
|
|
4306
|
+
size: 16,
|
|
4307
|
+
stroke: 1.5,
|
|
4308
|
+
style: {
|
|
4309
|
+
flex: 'none',
|
|
4310
|
+
color: 'var(--mantine-color-gray-5)'
|
|
4311
|
+
}
|
|
4312
|
+
}), /*#__PURE__*/jsxRuntime.jsx(core.Text, {
|
|
4313
|
+
fz: 12,
|
|
4314
|
+
fw: 500,
|
|
4315
|
+
c: "gray.9",
|
|
4316
|
+
truncate: "end",
|
|
4317
|
+
children: label
|
|
4318
|
+
})]
|
|
4319
|
+
});
|
|
4320
|
+
}
|
|
4321
|
+
|
|
4322
|
+
/*
|
|
4323
|
+
* Arrow keys walk the rows, Home and End jump to the ends — the WAI-ARIA menu
|
|
4324
|
+
* pattern. Tab still leaves the card, so it never traps focus inside a popover
|
|
4325
|
+
* the panel owns.
|
|
4326
|
+
*/
|
|
4327
|
+
function moveFocus(event) {
|
|
4328
|
+
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
4329
|
+
if (!keys.includes(event.key)) return;
|
|
4330
|
+
const rows = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
|
|
4331
|
+
if (rows.length === 0) return;
|
|
4332
|
+
event.preventDefault();
|
|
4333
|
+
const current = rows.indexOf(document.activeElement);
|
|
4334
|
+
const last = rows.length - 1;
|
|
4335
|
+
const next = event.key === 'Home' ? 0 : event.key === 'End' ? last : event.key === 'ArrowDown' ? current < last ? current + 1 : 0 : current > 0 ? current - 1 : last;
|
|
4336
|
+
rows[next].focus();
|
|
4337
|
+
}
|
|
4016
4338
|
|
|
4017
4339
|
/**
|
|
4018
4340
|
* Renderiza children apenas quando o usuário está autenticado
|
|
@@ -4121,6 +4443,7 @@ exports.TOKEN_STORAGE_KEY = TOKEN_STORAGE_KEY;
|
|
|
4121
4443
|
exports.UserInformation = UserInformation;
|
|
4122
4444
|
exports.UserProfile = UserProfile;
|
|
4123
4445
|
exports.Wordmark = Wordmark;
|
|
4446
|
+
exports.adoptRecentAccounts = adoptRecentAccounts;
|
|
4124
4447
|
exports.announceIdentityChange = announceIdentityChange;
|
|
4125
4448
|
exports.applyRedirect = applyRedirect;
|
|
4126
4449
|
exports.clearIdentitySwitching = clearIdentitySwitching;
|
|
@@ -4128,7 +4451,9 @@ exports.configure = configure;
|
|
|
4128
4451
|
exports.consumeSocialError = consumeSocialError;
|
|
4129
4452
|
exports.consumeSocialToken = consumeSocialToken;
|
|
4130
4453
|
exports.decodeJWT = decodeJWT;
|
|
4454
|
+
exports.deleteRecentAccount = deleteRecentAccount;
|
|
4131
4455
|
exports.endImpersonation = endImpersonation;
|
|
4456
|
+
exports.fetchRecentAccounts = fetchRecentAccounts;
|
|
4132
4457
|
exports.forgetAccount = forgetAccount;
|
|
4133
4458
|
exports.getApiUrl = getApiUrl;
|
|
4134
4459
|
exports.getApplicationInfo = getApplicationInfo;
|
|
@@ -4150,6 +4475,7 @@ exports.requestCode = requestCode;
|
|
|
4150
4475
|
exports.resolveRedirect = resolveRedirect;
|
|
4151
4476
|
exports.revokeOtherSessions = revokeOtherSessions;
|
|
4152
4477
|
exports.revokeSession = revokeSession;
|
|
4478
|
+
exports.saveRecentAccount = saveRecentAccount;
|
|
4153
4479
|
exports.setStoredToken = setStoredToken;
|
|
4154
4480
|
exports.shouldSignOutOn401 = shouldSignOutOn401;
|
|
4155
4481
|
exports.signOut = signOut;
|