@ciromaciel/auth-react 1.3.0 → 1.4.1
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 +28 -1
- package/dist/index.esm.js +352 -129
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +350 -127
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -139,6 +139,33 @@ Building your own screen? `fetchRecentAccounts()`, `saveRecentAccount(method)` a
|
|
|
139
139
|
`rememberAccount(email, method)`, `forgetAccount(email)` and `adoptRecentAccounts(list)` manage
|
|
140
140
|
the local copy.
|
|
141
141
|
|
|
142
|
+
### The account card
|
|
143
|
+
|
|
144
|
+
`<UserInformation />` is what opens from the footer of a sidebar: who is signed in, in which
|
|
145
|
+
organization and on which plan, then one list of actions with "Sair" last.
|
|
146
|
+
|
|
147
|
+
```jsx
|
|
148
|
+
<UserInformation
|
|
149
|
+
user={user}
|
|
150
|
+
signOut={signOut}
|
|
151
|
+
onAccountClick={openAccount}
|
|
152
|
+
onBillingClick={openBilling}
|
|
153
|
+
items={[{ id: 'docs', label: 'Documentação', icon: IconFileText, onClick: openDocs }]}
|
|
154
|
+
plan={{ name: 'Pro', used: 3, limit: 9, unit: 'projetos', onClick: openPlans }}
|
|
155
|
+
organization={{ name: 'Acme', role: 'owner' }}
|
|
156
|
+
/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- The title is the user's name, or the email when there is none — never both saying the same
|
|
160
|
+
thing.
|
|
161
|
+
- `items` are your app's own rows, passed as data (`{ id, label, icon, onClick }`), shown in their
|
|
162
|
+
own group after billing. The component takes no JSX, so every app's card keeps the same shape.
|
|
163
|
+
- `plan` and `organization` draw the context strip; leave them out and it is not drawn.
|
|
164
|
+
`limit: null` hides the usage bar.
|
|
165
|
+
- The footer reads "Protegido por Auth" with a "Termos" link to the same terms the sign-in
|
|
166
|
+
cites. `termsUrl` points it elsewhere, `termsUrl={null}` drops the link, and `branded={false}`
|
|
167
|
+
removes the footer.
|
|
168
|
+
|
|
142
169
|
## API
|
|
143
170
|
|
|
144
171
|
### Components
|
|
@@ -147,7 +174,7 @@ the local copy.
|
|
|
147
174
|
| ------------------------- | ------------------------------------------------------------ |
|
|
148
175
|
| `<SignIn />` | The whole way in: asks for the email, then takes the code |
|
|
149
176
|
| `<UserProfile />` | User profile management modal |
|
|
150
|
-
| `<UserInformation />` |
|
|
177
|
+
| `<UserInformation />` | The account card: who, where, which plan, then the actions |
|
|
151
178
|
| `<SocialButtons />` | Sign-in buttons for the providers the application enabled |
|
|
152
179
|
| `<AuthCard />` | The card shell the screens are built on |
|
|
153
180
|
| `<Wordmark />` | Application wordmark |
|
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.
|
|
@@ -2533,17 +2533,13 @@ function Wordmark({
|
|
|
2533
2533
|
});
|
|
2534
2534
|
}
|
|
2535
2535
|
|
|
2536
|
+
// Where the terms live. Hardcoded on purpose, like `API_BASE` in authSdk.js:
|
|
2537
|
+
// this ships inside the published bundle, and every screen of the seven panels
|
|
2538
|
+
// that links to the terms — the sign-in notice and the account card's footer —
|
|
2539
|
+
// must point at the same document. `termsUrl` overrides it for whoever hosts
|
|
2540
|
+
// their own.
|
|
2536
2541
|
const TERMS_URL = 'https://myinfrastructure.click/legal/terms';
|
|
2537
2542
|
|
|
2538
|
-
/**
|
|
2539
|
-
* The terms notice under the first step.
|
|
2540
|
-
*
|
|
2541
|
-
* It sits on step ONE and nowhere else: the account is born on the first
|
|
2542
|
-
* `verify` and there is no separate sign-up screen to put it on — this form IS
|
|
2543
|
-
* the sign-up for whoever has never entered. On step two the person already
|
|
2544
|
-
* agreed by asking for the code; repeating it there would only push the code
|
|
2545
|
-
* field down.
|
|
2546
|
-
*/
|
|
2547
2543
|
function TermsNotice({
|
|
2548
2544
|
url,
|
|
2549
2545
|
text,
|
|
@@ -3982,150 +3978,377 @@ function UserProfile({
|
|
|
3982
3978
|
});
|
|
3983
3979
|
}
|
|
3984
3980
|
|
|
3981
|
+
const ROLE_LABELS = {
|
|
3982
|
+
owner: 'Dono',
|
|
3983
|
+
admin: 'Administrador',
|
|
3984
|
+
member: 'Membro'
|
|
3985
|
+
};
|
|
3986
|
+
|
|
3987
|
+
/**
|
|
3988
|
+
* @typedef {Object} UserInformationItem
|
|
3989
|
+
* @property {string} [id] - Stable key
|
|
3990
|
+
* @property {string} label - The row's text
|
|
3991
|
+
* @property {Function} [icon] - A Tabler icon component
|
|
3992
|
+
* @property {Function} onClick
|
|
3993
|
+
*
|
|
3994
|
+
* @typedef {Object} UserInformationPlan
|
|
3995
|
+
* @property {string} [name] - "Pro", "Starter"… Shown as the badge
|
|
3996
|
+
* @property {number} [used] - How many of the plan's resources are in use
|
|
3997
|
+
* @property {number|null} [limit] - The plan's ceiling; `null` hides the meter
|
|
3998
|
+
* @property {string} [unit] - What is counted, in the plural: "projetos"
|
|
3999
|
+
* @property {string} [actionLabel='Ver planos']
|
|
4000
|
+
* @property {Function} [onClick] - Opens the plans
|
|
4001
|
+
*
|
|
4002
|
+
* @typedef {Object} UserInformationOrganization
|
|
4003
|
+
* @property {string} name
|
|
4004
|
+
* @property {string} [role] - `owner`, `admin`, `member`, or already a label
|
|
4005
|
+
*/
|
|
4006
|
+
|
|
4007
|
+
/**
|
|
4008
|
+
* @param {Object} props
|
|
4009
|
+
* @param {Object} props.user - The signed-in user (`name`, `email`, `image`)
|
|
4010
|
+
* @param {Function} props.signOut
|
|
4011
|
+
* @param {Function} [props.onAccountClick]
|
|
4012
|
+
* @param {Function} [props.onBillingClick]
|
|
4013
|
+
* @param {UserInformationItem[]} [props.items] - The panel's own rows, shown in their own group
|
|
4014
|
+
* @param {UserInformationPlan} [props.plan] - The plan strip; omitted, the strip is not drawn
|
|
4015
|
+
* @param {UserInformationOrganization} [props.organization] - Where the person is acting
|
|
4016
|
+
* @param {boolean} [props.branded=true] - The footer: "Protegido por Auth" and the terms link
|
|
4017
|
+
* @param {string|null} [props.termsUrl] - Where "Termos" points; `null` removes the link
|
|
4018
|
+
* @param {string} [props.termsLabel='Termos']
|
|
4019
|
+
* @param {string} [props.accountLabel='Conta']
|
|
4020
|
+
* @param {string} [props.billingLabel='Assinatura']
|
|
4021
|
+
* @param {string} [props.signOutLabel='Sair']
|
|
4022
|
+
*/
|
|
3985
4023
|
function UserInformation({
|
|
3986
4024
|
user,
|
|
3987
4025
|
signOut,
|
|
3988
4026
|
onAccountClick,
|
|
3989
4027
|
onBillingClick,
|
|
4028
|
+
items = [],
|
|
4029
|
+
plan,
|
|
4030
|
+
organization,
|
|
4031
|
+
branded = true,
|
|
4032
|
+
termsUrl = TERMS_URL,
|
|
4033
|
+
termsLabel = 'Termos',
|
|
3990
4034
|
accountLabel = 'Conta',
|
|
3991
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.
|
|
3992
4039
|
padded = true,
|
|
3993
|
-
size
|
|
4040
|
+
size,
|
|
4041
|
+
// eslint-disable-line no-unused-vars -- accepted and ignored, see above
|
|
3994
4042
|
style,
|
|
3995
4043
|
...others
|
|
3996
4044
|
}) {
|
|
3997
4045
|
if (!user) return null;
|
|
4046
|
+
const name = (user.fullName || user.name || '').trim();
|
|
4047
|
+
const email = (user.primaryEmailAddress || user.email || '').trim();
|
|
3998
4048
|
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
const
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
md: 'sm',
|
|
4023
|
-
lg: 'md'
|
|
4024
|
-
};
|
|
4025
|
-
const widthMap = {
|
|
4026
|
-
sm: 280,
|
|
4027
|
-
md: 320,
|
|
4028
|
-
lg: 400
|
|
4029
|
-
};
|
|
4030
|
-
const name = user.fullName || user.name || 'User';
|
|
4031
|
-
const email = user.primaryEmailAddress || user.email || '';
|
|
4032
|
-
const initials = name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
|
4033
|
-
return /*#__PURE__*/jsx(Box, {
|
|
4034
|
-
p: padded ? gapMap[size] : 0,
|
|
4035
|
-
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%",
|
|
4036
4072
|
style: style,
|
|
4037
4073
|
...others,
|
|
4038
|
-
children: /*#__PURE__*/jsxs(
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
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
|
|
4089
|
+
},
|
|
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: termsUrl ? 'space-between' : 'center',
|
|
4142
|
+
gap: 8,
|
|
4143
|
+
px: 16,
|
|
4144
|
+
py: 10,
|
|
4145
|
+
bg: "gray.1",
|
|
4146
|
+
style: {
|
|
4147
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
4148
|
+
},
|
|
4149
|
+
children: [/*#__PURE__*/jsxs(Text, {
|
|
4150
|
+
fz: 11,
|
|
4151
|
+
fw: 500,
|
|
4152
|
+
c: "gray.5",
|
|
4153
|
+
children: ["Protegido por", ' ', /*#__PURE__*/jsx(Text, {
|
|
4154
|
+
span: true,
|
|
4155
|
+
inherit: true,
|
|
4156
|
+
fw: 800,
|
|
4157
|
+
c: "gray.7",
|
|
4158
|
+
children: "Auth"
|
|
4159
|
+
})]
|
|
4160
|
+
}), termsUrl && /*#__PURE__*/jsx(Anchor, {
|
|
4161
|
+
href: termsUrl
|
|
4162
|
+
/*
|
|
4163
|
+
* A new tab, like the sign-in's notice: the card sits
|
|
4164
|
+
* over a working panel, and reading the terms must
|
|
4165
|
+
* not navigate away from it.
|
|
4166
|
+
*/,
|
|
4167
|
+
target: "_blank",
|
|
4168
|
+
rel: "noopener noreferrer",
|
|
4169
|
+
fz: 11,
|
|
4170
|
+
fw: 500,
|
|
4171
|
+
c: "gray.5",
|
|
4172
|
+
underline: "always",
|
|
4173
|
+
children: termsLabel
|
|
4174
|
+
})]
|
|
4175
|
+
})]
|
|
4176
|
+
});
|
|
4177
|
+
}
|
|
4178
|
+
|
|
4179
|
+
/** The organization, the role and the plan's usage, on the alternate surface. */
|
|
4180
|
+
function ContextStrip({
|
|
4181
|
+
organization,
|
|
4182
|
+
plan,
|
|
4183
|
+
padded
|
|
4184
|
+
}) {
|
|
4185
|
+
const role = organization?.role ? ROLE_LABELS[organization.role] || organization.role : null;
|
|
4186
|
+
const hasMeter = plan && Number.isFinite(plan.used) && Number.isFinite(plan.limit) && plan.limit > 0;
|
|
4187
|
+
const ratio = hasMeter ? Math.min(1, Math.max(0, plan.used / plan.limit)) : 0;
|
|
4188
|
+
return /*#__PURE__*/jsxs(Stack, {
|
|
4189
|
+
gap: 8,
|
|
4190
|
+
px: padded ? 16 : 0,
|
|
4191
|
+
py: 12,
|
|
4192
|
+
bg: "gray.1",
|
|
4193
|
+
style: {
|
|
4194
|
+
borderTop: '1px solid var(--mantine-color-gray-2)',
|
|
4195
|
+
borderBottom: '1px solid var(--mantine-color-gray-2)'
|
|
4196
|
+
},
|
|
4197
|
+
children: [(organization?.name || plan?.name) && /*#__PURE__*/jsxs(Group, {
|
|
4198
|
+
gap: 8,
|
|
4199
|
+
wrap: "nowrap",
|
|
4200
|
+
children: [organization?.name && /*#__PURE__*/jsxs(Fragment, {
|
|
4201
|
+
children: [/*#__PURE__*/jsx(IconBuilding, {
|
|
4202
|
+
size: 14,
|
|
4203
|
+
stroke: 1.5,
|
|
4204
|
+
style: {
|
|
4205
|
+
flex: 'none',
|
|
4206
|
+
color: 'var(--mantine-color-gray-5)'
|
|
4207
|
+
}
|
|
4208
|
+
}), /*#__PURE__*/jsx(Text, {
|
|
4209
|
+
fz: 12,
|
|
4210
|
+
fw: 800,
|
|
4211
|
+
c: "gray.9",
|
|
4212
|
+
truncate: "end",
|
|
4213
|
+
style: {
|
|
4214
|
+
minWidth: 0
|
|
4054
4215
|
},
|
|
4055
|
-
children:
|
|
4056
|
-
}), /*#__PURE__*/jsxs(
|
|
4216
|
+
children: organization.name
|
|
4217
|
+
}), role && /*#__PURE__*/jsxs(Text, {
|
|
4218
|
+
fz: 12,
|
|
4219
|
+
fw: 500,
|
|
4220
|
+
c: "gray.5",
|
|
4057
4221
|
style: {
|
|
4058
|
-
flex:
|
|
4059
|
-
overflow: 'hidden'
|
|
4222
|
+
flex: 'none'
|
|
4060
4223
|
},
|
|
4061
|
-
children: [
|
|
4062
|
-
size: fontSizeTitleMap[size],
|
|
4063
|
-
fw: 700,
|
|
4064
|
-
truncate: "end",
|
|
4065
|
-
c: "dark.9",
|
|
4066
|
-
lh: 1.1,
|
|
4067
|
-
children: name
|
|
4068
|
-
}), /*#__PURE__*/jsx(Text, {
|
|
4069
|
-
size: fontSizeEmailMap[size],
|
|
4070
|
-
c: "gray.5",
|
|
4071
|
-
truncate: "end",
|
|
4072
|
-
lh: 1.1,
|
|
4073
|
-
children: email
|
|
4074
|
-
})]
|
|
4075
|
-
}), /*#__PURE__*/jsx(ActionIcon, {
|
|
4076
|
-
size: btnSizeMap[size],
|
|
4077
|
-
onClick: signOut,
|
|
4078
|
-
children: /*#__PURE__*/jsx(IconLogout, {
|
|
4079
|
-
size: 16,
|
|
4080
|
-
stroke: 1.5
|
|
4081
|
-
})
|
|
4082
|
-
})]
|
|
4083
|
-
}), /*#__PURE__*/jsxs(Group, {
|
|
4084
|
-
grow: true,
|
|
4085
|
-
children: [/*#__PURE__*/jsx(Button, {
|
|
4086
|
-
variant: "default",
|
|
4087
|
-
size: btnSizeMap[size],
|
|
4088
|
-
leftSection: /*#__PURE__*/jsx(IconSettings, {
|
|
4089
|
-
size: 16,
|
|
4090
|
-
stroke: 1.5
|
|
4091
|
-
}),
|
|
4092
|
-
onClick: onAccountClick,
|
|
4093
|
-
children: accountLabel
|
|
4094
|
-
}), /*#__PURE__*/jsx(Button, {
|
|
4095
|
-
variant: "default",
|
|
4096
|
-
size: btnSizeMap[size],
|
|
4097
|
-
leftSection: /*#__PURE__*/jsx(IconCreditCard, {
|
|
4098
|
-
size: 16,
|
|
4099
|
-
stroke: 1.5
|
|
4100
|
-
}),
|
|
4101
|
-
onClick: onBillingClick,
|
|
4102
|
-
children: billingLabel
|
|
4224
|
+
children: ["\xB7 ", role]
|
|
4103
4225
|
})]
|
|
4226
|
+
}), plan?.name && /*#__PURE__*/jsx(Text, {
|
|
4227
|
+
component: "span",
|
|
4228
|
+
fz: 10,
|
|
4229
|
+
fw: 800,
|
|
4230
|
+
tt: "uppercase",
|
|
4231
|
+
lts: "1.5px",
|
|
4232
|
+
c: "white",
|
|
4233
|
+
bg: "gray.9",
|
|
4234
|
+
px: 6,
|
|
4235
|
+
lh: 1.6,
|
|
4236
|
+
ml: "auto",
|
|
4237
|
+
style: {
|
|
4238
|
+
flex: 'none'
|
|
4239
|
+
},
|
|
4240
|
+
children: plan.name
|
|
4241
|
+
})]
|
|
4242
|
+
}), hasMeter && /*#__PURE__*/jsxs(Fragment, {
|
|
4243
|
+
children: [/*#__PURE__*/jsx(Box, {
|
|
4244
|
+
h: 4,
|
|
4245
|
+
bg: "gray.2",
|
|
4246
|
+
role: "meter",
|
|
4247
|
+
"aria-valuemin": 0,
|
|
4248
|
+
"aria-valuemax": plan.limit,
|
|
4249
|
+
"aria-valuenow": plan.used,
|
|
4250
|
+
"aria-label": plan.unit ? `${plan.unit} em uso` : 'Uso do plano',
|
|
4251
|
+
children: /*#__PURE__*/jsx(Box, {
|
|
4252
|
+
h: "100%",
|
|
4253
|
+
w: `${ratio * 100}%`,
|
|
4254
|
+
bg: "gray.9"
|
|
4255
|
+
})
|
|
4104
4256
|
}), /*#__PURE__*/jsxs(Group, {
|
|
4105
|
-
justify: "
|
|
4106
|
-
gap:
|
|
4107
|
-
|
|
4257
|
+
justify: "space-between",
|
|
4258
|
+
gap: 8,
|
|
4259
|
+
wrap: "nowrap",
|
|
4108
4260
|
children: [/*#__PURE__*/jsx(Text, {
|
|
4109
|
-
|
|
4261
|
+
fz: 12,
|
|
4262
|
+
fw: 500,
|
|
4110
4263
|
c: "gray.6",
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
c: "dark.9",
|
|
4122
|
-
children: "Auth"
|
|
4123
|
-
})]
|
|
4264
|
+
children: `${plan.used} de ${plan.limit}${plan.unit ? ` ${plan.unit}` : ''}`
|
|
4265
|
+
}), plan.onClick && /*#__PURE__*/jsx(Anchor, {
|
|
4266
|
+
component: "button",
|
|
4267
|
+
type: "button",
|
|
4268
|
+
fz: 12,
|
|
4269
|
+
fw: 700,
|
|
4270
|
+
c: "gray.9",
|
|
4271
|
+
underline: "always",
|
|
4272
|
+
onClick: plan.onClick,
|
|
4273
|
+
children: plan.actionLabel || 'Ver planos'
|
|
4124
4274
|
})]
|
|
4125
4275
|
})]
|
|
4126
|
-
})
|
|
4276
|
+
})]
|
|
4277
|
+
});
|
|
4278
|
+
}
|
|
4279
|
+
function RowGroup({
|
|
4280
|
+
rows,
|
|
4281
|
+
hasDivider
|
|
4282
|
+
}) {
|
|
4283
|
+
return /*#__PURE__*/jsx(Stack, {
|
|
4284
|
+
gap: 0,
|
|
4285
|
+
p: 6,
|
|
4286
|
+
style: hasDivider ? {
|
|
4287
|
+
borderTop: '1px solid var(--mantine-color-gray-2)'
|
|
4288
|
+
} : undefined,
|
|
4289
|
+
children: rows.map(row => /*#__PURE__*/jsx(Row, {
|
|
4290
|
+
...row
|
|
4291
|
+
}, row.id || row.label))
|
|
4127
4292
|
});
|
|
4128
4293
|
}
|
|
4294
|
+
function Row({
|
|
4295
|
+
label,
|
|
4296
|
+
icon: Icon,
|
|
4297
|
+
onClick
|
|
4298
|
+
}) {
|
|
4299
|
+
return /*#__PURE__*/jsxs(UnstyledButton, {
|
|
4300
|
+
role: "menuitem",
|
|
4301
|
+
onClick: onClick,
|
|
4302
|
+
px: 10,
|
|
4303
|
+
py: 7,
|
|
4304
|
+
w: "100%",
|
|
4305
|
+
style: {
|
|
4306
|
+
display: 'flex',
|
|
4307
|
+
alignItems: 'center',
|
|
4308
|
+
gap: 10,
|
|
4309
|
+
borderRadius: 0
|
|
4310
|
+
}
|
|
4311
|
+
/*
|
|
4312
|
+
* Hover and keyboard focus share the same surface: a row reached
|
|
4313
|
+
* with the arrows must look exactly like the one under the mouse.
|
|
4314
|
+
*/,
|
|
4315
|
+
onMouseEnter: event => event.currentTarget.style.background = 'var(--mantine-color-gray-1)',
|
|
4316
|
+
onMouseLeave: event => event.currentTarget.style.background = 'transparent',
|
|
4317
|
+
onFocus: event => event.currentTarget.style.background = 'var(--mantine-color-gray-1)',
|
|
4318
|
+
onBlur: event => event.currentTarget.style.background = 'transparent',
|
|
4319
|
+
children: [Icon && /*#__PURE__*/jsx(Icon, {
|
|
4320
|
+
size: 16,
|
|
4321
|
+
stroke: 1.5,
|
|
4322
|
+
style: {
|
|
4323
|
+
flex: 'none',
|
|
4324
|
+
color: 'var(--mantine-color-gray-5)'
|
|
4325
|
+
}
|
|
4326
|
+
}), /*#__PURE__*/jsx(Text, {
|
|
4327
|
+
fz: 12,
|
|
4328
|
+
fw: 500,
|
|
4329
|
+
c: "gray.9",
|
|
4330
|
+
truncate: "end",
|
|
4331
|
+
children: label
|
|
4332
|
+
})]
|
|
4333
|
+
});
|
|
4334
|
+
}
|
|
4335
|
+
|
|
4336
|
+
/*
|
|
4337
|
+
* Arrow keys walk the rows, Home and End jump to the ends — the WAI-ARIA menu
|
|
4338
|
+
* pattern. Tab still leaves the card, so it never traps focus inside a popover
|
|
4339
|
+
* the panel owns.
|
|
4340
|
+
*/
|
|
4341
|
+
function moveFocus(event) {
|
|
4342
|
+
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
4343
|
+
if (!keys.includes(event.key)) return;
|
|
4344
|
+
const rows = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
|
|
4345
|
+
if (rows.length === 0) return;
|
|
4346
|
+
event.preventDefault();
|
|
4347
|
+
const current = rows.indexOf(document.activeElement);
|
|
4348
|
+
const last = rows.length - 1;
|
|
4349
|
+
const next = event.key === 'Home' ? 0 : event.key === 'End' ? last : event.key === 'ArrowDown' ? current < last ? current + 1 : 0 : current > 0 ? current - 1 : last;
|
|
4350
|
+
rows[next].focus();
|
|
4351
|
+
}
|
|
4129
4352
|
|
|
4130
4353
|
/**
|
|
4131
4354
|
* Renderiza children apenas quando o usuário está autenticado
|