@ciromaciel/auth-react 1.4.0 → 1.5.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 CHANGED
@@ -151,7 +151,7 @@ organization and on which plan, then one list of actions with "Sair" last.
151
151
  onAccountClick={openAccount}
152
152
  onBillingClick={openBilling}
153
153
  items={[{ id: 'docs', label: 'Documentação', icon: IconFileText, onClick: openDocs }]}
154
- plan={{ name: 'Pro', used: 3, limit: 9, unit: 'projetos', onClick: openPlans }}
154
+ plan={{ name: 'Pro', used: 8, limit: 9, unit: 'projetos', canUpgrade: true, onClick: openPlans }}
155
155
  organization={{ name: 'Acme', role: 'owner' }}
156
156
  />
157
157
  ```
@@ -162,7 +162,13 @@ organization and on which plan, then one list of actions with "Sair" last.
162
162
  own group after billing. The component takes no JSX, so every app's card keeps the same shape.
163
163
  - `plan` and `organization` draw the context strip; leave them out and it is not drawn.
164
164
  `limit: null` hides the usage bar.
165
- - `branded={false}` removes the "Protegido por Auth" line.
165
+ - The strip informs; "Assinatura" is the way to billing. The strip adds a "Fazer upgrade"
166
+ invite only when `canUpgrade` is `true` (there is a plan above this one) and usage is at 80%
167
+ of the limit or more. At the limit, the bar and the count turn red and "Limite atingido" appears on the badge's line.
168
+ `plan.onClick` is what the invite opens; `actionLabel` renames it.
169
+ - The footer reads "Protegido por Auth" with a "Termos" link to the same terms the sign-in
170
+ cites. `termsUrl` points it elsewhere, `termsUrl={null}` drops the link, and `branded={false}`
171
+ removes the footer.
166
172
 
167
173
  ## API
168
174
 
package/dist/index.esm.js CHANGED
@@ -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,
@@ -3988,6 +3984,13 @@ const ROLE_LABELS = {
3988
3984
  member: 'Membro'
3989
3985
  };
3990
3986
 
3987
+ /*
3988
+ * From this share of the limit on, the strip invites to upgrade. Below it the
3989
+ * strip only informs: "Assinatura" is the door that is always there, and a
3990
+ * second link to the same modal right above it was two doors to one room.
3991
+ */
3992
+ const UPGRADE_THRESHOLD = 0.8;
3993
+
3991
3994
  /**
3992
3995
  * @typedef {Object} UserInformationItem
3993
3996
  * @property {string} [id] - Stable key
@@ -4000,8 +4003,12 @@ const ROLE_LABELS = {
4000
4003
  * @property {number} [used] - How many of the plan's resources are in use
4001
4004
  * @property {number|null} [limit] - The plan's ceiling; `null` hides the meter
4002
4005
  * @property {string} [unit] - What is counted, in the plural: "projetos"
4003
- * @property {string} [actionLabel='Ver planos']
4004
- * @property {Function} [onClick] - Opens the plans
4006
+ * @property {boolean} [canUpgrade=false] - There is a plan above this one. Without
4007
+ * it the invite never shows, however full the plan is: on the last rung there
4008
+ * is nowhere to go
4009
+ * @property {string} [actionLabel='Fazer upgrade']
4010
+ * @property {Function} [onClick] - Opens the plans. The invite shows only with
4011
+ * `canUpgrade` and usage at 80% of the limit or more
4005
4012
  *
4006
4013
  * @typedef {Object} UserInformationOrganization
4007
4014
  * @property {string} name
@@ -4017,7 +4024,9 @@ const ROLE_LABELS = {
4017
4024
  * @param {UserInformationItem[]} [props.items] - The panel's own rows, shown in their own group
4018
4025
  * @param {UserInformationPlan} [props.plan] - The plan strip; omitted, the strip is not drawn
4019
4026
  * @param {UserInformationOrganization} [props.organization] - Where the person is acting
4020
- * @param {boolean} [props.branded=true] - The "Protegido por Auth" line
4027
+ * @param {boolean} [props.branded=true] - The footer: "Protegido por Auth" and the terms link
4028
+ * @param {string|null} [props.termsUrl] - Where "Termos" points; `null` removes the link
4029
+ * @param {string} [props.termsLabel='Termos']
4021
4030
  * @param {string} [props.accountLabel='Conta']
4022
4031
  * @param {string} [props.billingLabel='Assinatura']
4023
4032
  * @param {string} [props.signOutLabel='Sair']
@@ -4031,6 +4040,8 @@ function UserInformation({
4031
4040
  plan,
4032
4041
  organization,
4033
4042
  branded = true,
4043
+ termsUrl = TERMS_URL,
4044
+ termsLabel = 'Termos',
4034
4045
  accountLabel = 'Conta',
4035
4046
  billingLabel = 'Assinatura',
4036
4047
  signOutLabel = 'Sair',
@@ -4138,23 +4149,39 @@ function UserInformation({
4138
4149
  hasDivider: true
4139
4150
  })]
4140
4151
  }), branded && /*#__PURE__*/jsxs(Group, {
4141
- justify: "center",
4142
- gap: 4,
4143
- py: 8,
4152
+ justify: termsUrl ? 'space-between' : 'center',
4153
+ gap: 8,
4154
+ px: 16,
4155
+ py: 10,
4144
4156
  bg: "gray.1",
4145
4157
  style: {
4146
4158
  borderTop: '1px solid var(--mantine-color-gray-2)'
4147
4159
  },
4148
- children: [/*#__PURE__*/jsx(Text, {
4160
+ children: [/*#__PURE__*/jsxs(Text, {
4149
4161
  fz: 11,
4150
4162
  fw: 500,
4151
4163
  c: "gray.5",
4152
- children: "Protegido por"
4153
- }), /*#__PURE__*/jsx(Text, {
4164
+ children: ["Protegido por", ' ', /*#__PURE__*/jsx(Text, {
4165
+ span: true,
4166
+ inherit: true,
4167
+ fw: 800,
4168
+ c: "gray.7",
4169
+ children: "Auth"
4170
+ })]
4171
+ }), termsUrl && /*#__PURE__*/jsx(Anchor, {
4172
+ href: termsUrl
4173
+ /*
4174
+ * A new tab, like the sign-in's notice: the card sits
4175
+ * over a working panel, and reading the terms must
4176
+ * not navigate away from it.
4177
+ */,
4178
+ target: "_blank",
4179
+ rel: "noopener noreferrer",
4154
4180
  fz: 11,
4155
- fw: 800,
4156
- c: "gray.7",
4157
- children: "Auth"
4181
+ fw: 500,
4182
+ c: "gray.5",
4183
+ underline: "always",
4184
+ children: termsLabel
4158
4185
  })]
4159
4186
  })]
4160
4187
  });
@@ -4169,6 +4196,15 @@ function ContextStrip({
4169
4196
  const role = organization?.role ? ROLE_LABELS[organization.role] || organization.role : null;
4170
4197
  const hasMeter = plan && Number.isFinite(plan.used) && Number.isFinite(plan.limit) && plan.limit > 0;
4171
4198
  const ratio = hasMeter ? Math.min(1, Math.max(0, plan.used / plan.limit)) : 0;
4199
+ const isFull = hasMeter && plan.used >= plan.limit;
4200
+ const showsInvite = hasMeter && plan.canUpgrade === true && typeof plan.onClick === 'function' && ratio >= UPGRADE_THRESHOLD;
4201
+ /*
4202
+ * "Limite atingido" sits on the badge's line, whose left side is empty
4203
+ * when there is no organization. Next to the count it would wrap to two
4204
+ * lines beside the invite. With an organization there, it falls back to
4205
+ * the count.
4206
+ */
4207
+ const limitNoticeOnTop = isFull && !organization?.name;
4172
4208
  return /*#__PURE__*/jsxs(Stack, {
4173
4209
  gap: 8,
4174
4210
  px: padded ? 16 : 0,
@@ -4181,7 +4217,12 @@ function ContextStrip({
4181
4217
  children: [(organization?.name || plan?.name) && /*#__PURE__*/jsxs(Group, {
4182
4218
  gap: 8,
4183
4219
  wrap: "nowrap",
4184
- children: [organization?.name && /*#__PURE__*/jsxs(Fragment, {
4220
+ children: [limitNoticeOnTop && /*#__PURE__*/jsx(Text, {
4221
+ fz: 12,
4222
+ fw: 700,
4223
+ c: "red.8",
4224
+ children: "Limite atingido"
4225
+ }), organization?.name && /*#__PURE__*/jsxs(Fragment, {
4185
4226
  children: [/*#__PURE__*/jsx(IconBuilding, {
4186
4227
  size: 14,
4187
4228
  stroke: 1.5,
@@ -4235,7 +4276,7 @@ function ContextStrip({
4235
4276
  children: /*#__PURE__*/jsx(Box, {
4236
4277
  h: "100%",
4237
4278
  w: `${ratio * 100}%`,
4238
- bg: "gray.9"
4279
+ bg: isFull ? 'red.8' : 'gray.9'
4239
4280
  })
4240
4281
  }), /*#__PURE__*/jsxs(Group, {
4241
4282
  justify: "space-between",
@@ -4243,18 +4284,24 @@ function ContextStrip({
4243
4284
  wrap: "nowrap",
4244
4285
  children: [/*#__PURE__*/jsx(Text, {
4245
4286
  fz: 12,
4246
- fw: 500,
4247
- c: "gray.6",
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",
4287
+ fw: isFull ? 700 : 500,
4288
+ c: isFull ? 'red.8' : 'gray.6',
4289
+ children: `${plan.used} de ${plan.limit}${plan.unit ? ` ${plan.unit}` : ''}${isFull && !limitNoticeOnTop ? ' · limite atingido' : ''}`
4290
+ }), showsInvite && /*#__PURE__*/jsx(UnstyledButton, {
4256
4291
  onClick: plan.onClick,
4257
- children: plan.actionLabel || 'Ver planos'
4292
+ fz: 11,
4293
+ fw: 800,
4294
+ lts: "0.5px",
4295
+ c: "white",
4296
+ bg: "gray.9",
4297
+ px: 8,
4298
+ py: 3,
4299
+ lh: 1.4,
4300
+ style: {
4301
+ flex: 'none',
4302
+ whiteSpace: 'nowrap'
4303
+ },
4304
+ children: plan.actionLabel || 'Fazer upgrade'
4258
4305
  })]
4259
4306
  })]
4260
4307
  })]