@greatapps/common 1.1.739 → 1.1.741

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.
Files changed (35) hide show
  1. package/dist/components/modals/billing/RequiredBillingDataModal.mjs +448 -0
  2. package/dist/components/modals/billing/RequiredBillingDataModal.mjs.map +1 -0
  3. package/dist/i18n/messages/en-us.mjs +39 -0
  4. package/dist/i18n/messages/en-us.mjs.map +1 -1
  5. package/dist/i18n/messages/es-es.mjs +39 -0
  6. package/dist/i18n/messages/es-es.mjs.map +1 -1
  7. package/dist/i18n/messages/pt-br.mjs +40 -0
  8. package/dist/i18n/messages/pt-br.mjs.map +1 -1
  9. package/dist/index.mjs +48 -32
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/modules/accounts/actions/account-management.action.mjs +15 -0
  12. package/dist/modules/accounts/actions/account-management.action.mjs.map +1 -1
  13. package/dist/modules/accounts/hooks/use-required-billing-data.hook.mjs +26 -0
  14. package/dist/modules/accounts/hooks/use-required-billing-data.hook.mjs.map +1 -0
  15. package/dist/modules/accounts/hooks/useAccountManagement.mjs +13 -1
  16. package/dist/modules/accounts/hooks/useAccountManagement.mjs.map +1 -1
  17. package/dist/modules/accounts/utils/billing-data.mjs +35 -0
  18. package/dist/modules/accounts/utils/billing-data.mjs.map +1 -0
  19. package/dist/modules/subscriptions/utils/has-paid-subscription.mjs +13 -0
  20. package/dist/modules/subscriptions/utils/has-paid-subscription.mjs.map +1 -0
  21. package/dist/server.mjs +2 -0
  22. package/dist/server.mjs.map +1 -1
  23. package/package.json +1 -1
  24. package/src/components/modals/billing/RequiredBillingDataModal.tsx +548 -0
  25. package/src/i18n/messages/en-us.ts +40 -0
  26. package/src/i18n/messages/es-es.ts +40 -0
  27. package/src/i18n/messages/pt-br.ts +42 -0
  28. package/src/index.ts +11 -0
  29. package/src/modules/accounts/actions/account-management.action.ts +24 -0
  30. package/src/modules/accounts/hooks/use-required-billing-data.hook.ts +38 -0
  31. package/src/modules/accounts/hooks/useAccountManagement.ts +13 -0
  32. package/src/modules/accounts/types.ts +11 -0
  33. package/src/modules/accounts/utils/billing-data.ts +81 -0
  34. package/src/modules/subscriptions/utils/has-paid-subscription.ts +27 -0
  35. package/src/server.ts +1 -0
@@ -348,6 +348,46 @@ const messages = {
348
348
  newPaymentMethod: 'New payment method',
349
349
  },
350
350
  },
351
+ billing: {
352
+ requiredData: {
353
+ title: 'Complete your billing information',
354
+ description:
355
+ 'We need this information to issue the invoice for your payments. Filling it in is required to keep using the platform.',
356
+ addressHeading: 'Billing address',
357
+ select: 'Select',
358
+ typeHere: 'Type here',
359
+ personType: 'Entity type',
360
+ personTypePf: 'Individual',
361
+ personTypePj: 'Company',
362
+ cpf: 'CPF',
363
+ cnpj: 'CNPJ',
364
+ fullName: 'Full name',
365
+ companyName: 'Legal name',
366
+ financialEmail: 'Billing email',
367
+ financialEmailPlaceholder: 'billing@company.com',
368
+ cep: 'ZIP code',
369
+ postalCode: 'Postal code',
370
+ street: 'Address',
371
+ streetNumber: 'Number',
372
+ neighborhood: 'District',
373
+ complement: 'Complement',
374
+ city: 'City',
375
+ state: 'State',
376
+ stateProvince: 'State/Province',
377
+ country: 'Country',
378
+ submit: 'Save and continue',
379
+ submitting: 'Saving...',
380
+ successToast: 'Billing information saved successfully.',
381
+ errorToast: 'We could not save your billing information, please try again.',
382
+ requiredField: 'Required field',
383
+ invalidEmail: 'Invalid email',
384
+ invalidCep: 'Invalid ZIP code',
385
+ invalidCpf: 'Invalid CPF',
386
+ invalidCnpj: 'Invalid CNPJ',
387
+ invalidCpfChecksum: 'Invalid CPF — check the verification digits',
388
+ invalidCnpjChecksum: 'Invalid CNPJ — check the verification digits',
389
+ },
390
+ },
351
391
  navigation: {
352
392
  banners: {
353
393
  cancelled: {
@@ -349,6 +349,46 @@ const messages = {
349
349
  newPaymentMethod: 'Nuevo método de pago',
350
350
  },
351
351
  },
352
+ billing: {
353
+ requiredData: {
354
+ title: 'Completa tus datos de facturación',
355
+ description:
356
+ 'Necesitamos esta información para emitir la factura de tus pagos. Completarla es obligatorio para seguir usando la plataforma.',
357
+ addressHeading: 'Dirección de facturación',
358
+ select: 'Selecciona',
359
+ typeHere: 'Escribe aquí',
360
+ personType: 'Tipo de persona',
361
+ personTypePf: 'Persona física',
362
+ personTypePj: 'Persona jurídica',
363
+ cpf: 'CPF',
364
+ cnpj: 'CNPJ',
365
+ fullName: 'Nombre completo',
366
+ companyName: 'Razón social',
367
+ financialEmail: 'Correo de facturación',
368
+ financialEmailPlaceholder: 'facturacion@empresa.com',
369
+ cep: 'Código postal',
370
+ postalCode: 'Código postal',
371
+ street: 'Dirección',
372
+ streetNumber: 'Número',
373
+ neighborhood: 'Barrio',
374
+ complement: 'Complemento',
375
+ city: 'Ciudad',
376
+ state: 'Estado',
377
+ stateProvince: 'Estado/Provincia',
378
+ country: 'País',
379
+ submit: 'Guardar y continuar',
380
+ submitting: 'Guardando...',
381
+ successToast: 'Datos de facturación guardados con éxito.',
382
+ errorToast: 'No se pudieron guardar los datos de facturación, inténtalo de nuevo.',
383
+ requiredField: 'Campo obligatorio',
384
+ invalidEmail: 'Correo electrónico inválido',
385
+ invalidCep: 'Código postal inválido',
386
+ invalidCpf: 'CPF inválido',
387
+ invalidCnpj: 'CNPJ inválido',
388
+ invalidCpfChecksum: 'CPF inválido — revisa los dígitos verificadores',
389
+ invalidCnpjChecksum: 'CNPJ inválido — revisa los dígitos verificadores',
390
+ },
391
+ },
352
392
  navigation: {
353
393
  banners: {
354
394
  cancelled: {
@@ -360,6 +360,48 @@ const messages = {
360
360
  },
361
361
  },
362
362
 
363
+ // Componente: RequiredBillingDataModal.
364
+ billing: {
365
+ requiredData: {
366
+ title: 'Complete seus dados de cobrança',
367
+ description:
368
+ 'Precisamos dessas informações para emitir a nota fiscal dos seus pagamentos. O preenchimento é obrigatório para continuar usando a plataforma.',
369
+ addressHeading: 'Endereço de cobrança',
370
+ select: 'Selecione',
371
+ typeHere: 'Digite aqui',
372
+ personType: 'Tipo de pessoa',
373
+ personTypePf: 'Pessoa física',
374
+ personTypePj: 'Pessoa jurídica',
375
+ cpf: 'CPF',
376
+ cnpj: 'CNPJ',
377
+ fullName: 'Nome completo',
378
+ companyName: 'Razão social',
379
+ financialEmail: 'E-mail financeiro',
380
+ financialEmailPlaceholder: 'financeiro@empresa.com',
381
+ cep: 'CEP',
382
+ postalCode: 'Código postal',
383
+ street: 'Endereço',
384
+ streetNumber: 'Número',
385
+ neighborhood: 'Bairro',
386
+ complement: 'Complemento',
387
+ city: 'Cidade',
388
+ state: 'UF',
389
+ stateProvince: 'Estado/Província',
390
+ country: 'País',
391
+ submit: 'Salvar e continuar',
392
+ submitting: 'Salvando...',
393
+ successToast: 'Dados de cobrança salvos com sucesso.',
394
+ errorToast: 'Não foi possível salvar os dados de cobrança, tente novamente.',
395
+ requiredField: 'Campo obrigatório',
396
+ invalidEmail: 'E-mail inválido',
397
+ invalidCep: 'CEP inválido',
398
+ invalidCpf: 'CPF inválido',
399
+ invalidCnpj: 'CNPJ inválido',
400
+ invalidCpfChecksum: 'CPF inválido — confira os dígitos verificadores',
401
+ invalidCnpjChecksum: 'CNPJ inválido — confira os dígitos verificadores',
402
+ },
403
+ },
404
+
363
405
  // Componentes de navegação: banners de assinatura, seletor de projeto, links de rodapé, créditos, plano.
364
406
  navigation: {
365
407
  banners: {
package/src/index.ts CHANGED
@@ -188,6 +188,7 @@ export {
188
188
  export { buildPlanExtras } from "./modules/subscriptions/utils/build-plan-extras";
189
189
  export { hasSubscriptionExpired } from "./modules/subscriptions/utils/has-subscription-expired";
190
190
  export { hasActivePaidSubscription } from "./modules/subscriptions/utils/has-active-paid-subscription";
191
+ export { hasPaidSubscription } from "./modules/subscriptions/utils/has-paid-subscription";
191
192
  export {
192
193
  confirmPaymentIfRequired,
193
194
  PaymentAuthenticationError,
@@ -213,6 +214,7 @@ export { default as BuyCreditsModal } from "./components/modals/BuyCreditsModal"
213
214
  export { default as CreditsDisabledModal } from "./components/modals/CreditsDisabledModal";
214
215
  export { default as PaidPlanRequiredModal } from "./components/modals/PaidPlanRequiredModal";
215
216
  export { default as AccountDeletionWarningModal } from "./components/modals/AccountDeletionWarningModal";
217
+ export { default as RequiredBillingDataModal } from "./components/modals/billing/RequiredBillingDataModal";
216
218
  export { default as AddCardModal } from "./components/modals/cards/AddCardModal";
217
219
  export { DeleteCardModal } from "./components/modals/cards/DeleteCardModal";
218
220
  export { CannotDeleteCardModal } from "./components/modals/cards/CannotDeleteCardModal";
@@ -516,16 +518,25 @@ export {
516
518
  useUpdateAccount,
517
519
  useUpdateAccountUser,
518
520
  useUpdateAccountUserById,
521
+ useUpdateBillingData,
519
522
  useDeleteAccountUser,
520
523
  useDeleteAccount,
521
524
  ACCOUNT_USERS_QUERY_KEY,
522
525
  } from "./modules/accounts/hooks/useAccountManagement";
523
526
  export { useViaCep } from "./modules/accounts/hooks/useViaCep";
524
527
  export { useAccountToken } from "./modules/accounts/hooks/use-account-token.hook";
528
+ export { useRequiredBillingData } from "./modules/accounts/hooks/use-required-billing-data.hook";
529
+ export {
530
+ hasCompleteBillingData,
531
+ isInternationalAccount,
532
+ INTERNATIONAL_GATEWAY,
533
+ } from "./modules/accounts/utils/billing-data";
534
+ export type { BillingDataSnapshot } from "./modules/accounts/utils/billing-data";
525
535
 
526
536
  // Account Types
527
537
  export type {
528
538
  UpdateAccountRequest,
539
+ UpdateBillingDataRequest,
529
540
  UpdateAccountUserRequest,
530
541
  ChangePasswordRequest,
531
542
  DeleteAccountActionResult,
@@ -10,6 +10,7 @@ import type {
10
10
  DeleteAccountRequest,
11
11
  UpdateAccountRequest,
12
12
  UpdateAccountUserRequest,
13
+ UpdateBillingDataRequest,
13
14
  } from '../types';
14
15
  import { twoFactorService } from '../services/two-factor.service';
15
16
  import { findUserById } from '../../users/action/find-user-by-id.action';
@@ -51,6 +52,29 @@ export async function updateAccountAction(data: UpdateAccountRequest) {
51
52
  });
52
53
  }
53
54
 
55
+ /**
56
+ * Cadastro de cobrança/fiscal. Restrito a owner/admin — mesmo recorte de
57
+ * `manage_subscriptions` no `assertManagementPermission`, replicado aqui pra não
58
+ * arrastar a checagem de assinatura vigente junto (a conta pode estar em atraso
59
+ * e ainda assim precisa poder regularizar o cadastro).
60
+ */
61
+ export async function updateBillingDataAction(data: UpdateBillingDataRequest) {
62
+ return safeMutationAction(async () => {
63
+ const userResult = await findUserById();
64
+ const profile = userResult.success ? userResult.data?.profile : undefined;
65
+
66
+ if (profile !== UserProfile.owner && profile !== UserProfile.admin) {
67
+ throw new ApiError(
68
+ 'Apenas o proprietário ou um administrador pode alterar os dados de cobrança',
69
+ 'FORBIDDEN',
70
+ 403
71
+ );
72
+ }
73
+
74
+ return accountService.updateAccount(data);
75
+ });
76
+ }
77
+
54
78
  export async function deleteAccountUserAction(userId: number) {
55
79
  return safeMutationAction(() => accountService.deleteAccountUser(userId));
56
80
  }
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+
3
+ import { useCurrentAccount } from './current-account.hook';
4
+ import { useActiveSubscription } from '../../subscriptions/hooks/find-active-subscription.hook';
5
+ import { hasPaidSubscription } from '../../subscriptions/utils/has-paid-subscription';
6
+ import { useManagementPermissions } from '../../auth/hooks/useManagementPermissions';
7
+ import { hasCompleteBillingData, isInternationalAccount } from '../utils/billing-data';
8
+
9
+ /**
10
+ * Contas que já pagaram mas nunca preencheram o cadastro de cobrança ficam sem
11
+ * nota fiscal (chamado "Nota fiscal não emitida em pagamentos confirmados").
12
+ * Este hook diz quando o preenchimento vira obrigatório — o modal que ele
13
+ * alimenta é bloqueante, então só liga com os dados já carregados e só para
14
+ * quem consegue resolver: owner/admin.
15
+ */
16
+ export function useRequiredBillingData() {
17
+ const { isOwnerOrAdmin } = useManagementPermissions();
18
+ const { data: account, isPending: isAccountPending } = useCurrentAccount();
19
+ const {
20
+ data: { data: [subscription] = [] } = {},
21
+ isPending: isSubscriptionPending,
22
+ } = useActiveSubscription();
23
+
24
+ const isLoading = isAccountPending || isSubscriptionPending;
25
+
26
+ const isRequired =
27
+ !isLoading &&
28
+ isOwnerOrAdmin &&
29
+ hasPaidSubscription(subscription) &&
30
+ !hasCompleteBillingData(account);
31
+
32
+ return {
33
+ isRequired,
34
+ isLoading,
35
+ account,
36
+ isInternational: isInternationalAccount(account),
37
+ };
38
+ }
@@ -14,6 +14,7 @@ import {
14
14
  updateUserAction,
15
15
  updateAccountAction,
16
16
  updateAccountUserByIdAction,
17
+ updateBillingDataAction,
17
18
  deleteAccountUserAction,
18
19
  deleteAccountAction,
19
20
  } from '../actions/account-management.action';
@@ -39,6 +40,18 @@ export function useUpdateAccount() {
39
40
  });
40
41
  }
41
42
 
43
+ export function useUpdateBillingData() {
44
+ const queryClient = useQueryClient();
45
+ const accountKey = useWlQueryKey([...ACCOUNT_QUERY_KEY]);
46
+
47
+ return useMutation({
48
+ mutationFn: withAction(updateBillingDataAction),
49
+ onSuccess: () => {
50
+ queryClient.invalidateQueries({ queryKey: accountKey });
51
+ },
52
+ });
53
+ }
54
+
42
55
  export function useUpdateAccountUser() {
43
56
  const queryClient = useQueryClient();
44
57
  const accountUsersKey = useAuthQueryKey([...ACCOUNT_USERS_QUERY_KEY]);
@@ -154,6 +154,7 @@ export interface UpdateAccountRequest {
154
154
  financial_document_type?: number;
155
155
  financial_document?: string;
156
156
  financial_name?: string;
157
+ financial_email?: string;
157
158
  zipcode?: string;
158
159
  address?: string;
159
160
  address_number?: string;
@@ -164,6 +165,16 @@ export interface UpdateAccountRequest {
164
165
  country?: string;
165
166
  }
166
167
 
168
+ /**
169
+ * Dados de cobrança/fiscais. É o `UpdateAccountRequest` sem `name` de propósito:
170
+ * o nome da empresa é exclusivo do owner (ver `updateAccountAction`) e não deve
171
+ * viajar junto do formulário de cobrança, que também é editável por admin.
172
+ */
173
+ export type UpdateBillingDataRequest = Omit<
174
+ UpdateAccountRequest,
175
+ 'name' | 'currency' | 'timezone' | 'bussiness_type'
176
+ >;
177
+
167
178
  export interface UpdateAccountResponse {
168
179
  status: 0 | 1;
169
180
  message: string;
@@ -0,0 +1,81 @@
1
+ import type { Account } from '../types';
2
+
3
+ /**
4
+ * Gateway das contas cobradas em USD. Elas não têm CPF/CNPJ, bairro nem número —
5
+ * mesmo critério usado no AddCardModal (`account.gateway === 'stripe_usd'`).
6
+ */
7
+ export const INTERNATIONAL_GATEWAY = 'stripe_usd';
8
+
9
+ export type BillingDataSnapshot = Pick<
10
+ Account,
11
+ | 'gateway'
12
+ | 'financial_document'
13
+ | 'financial_name'
14
+ | 'financial_email'
15
+ | 'zipcode'
16
+ | 'address'
17
+ | 'address_number'
18
+ | 'neighborhood'
19
+ | 'city'
20
+ | 'state'
21
+ | 'country'
22
+ >;
23
+
24
+ type BillingField = keyof Omit<BillingDataSnapshot, 'gateway'>;
25
+
26
+ /**
27
+ * Campos exigidos em qualquer país — sem eles a nota fiscal não sai.
28
+ *
29
+ * `financial_email` NÃO entra: o backend cai no e-mail do usuário quando ele está
30
+ * vazio (`financial_email || userDocument.email`, accounts/services.js), então a
31
+ * nota sai do mesmo jeito. Exigi-lo aqui bloquearia 4.7k contas pagantes da wl 1
32
+ * em vez das ~460 que de fato estão sem cadastro fiscal. O campo continua no
33
+ * formulário, pré-preenchido — só não é critério de bloqueio.
34
+ */
35
+ const INTERNATIONAL_BILLING_FIELDS: readonly BillingField[] = [
36
+ 'financial_name',
37
+ 'zipcode',
38
+ 'address',
39
+ 'city',
40
+ 'state',
41
+ 'country',
42
+ ];
43
+
44
+ /** No Brasil a NF-e também exige documento fiscal, número e bairro. */
45
+ const BR_BILLING_FIELDS: readonly BillingField[] = [
46
+ ...INTERNATIONAL_BILLING_FIELDS,
47
+ 'financial_document',
48
+ 'address_number',
49
+ 'neighborhood',
50
+ ];
51
+
52
+ export function isInternationalAccount(
53
+ account: Pick<BillingDataSnapshot, 'gateway'> | null | undefined,
54
+ ): boolean {
55
+ return account?.gateway === INTERNATIONAL_GATEWAY;
56
+ }
57
+
58
+ function isFilled(value: string | null | undefined): boolean {
59
+ return typeof value === 'string' && value.trim().length > 0;
60
+ }
61
+
62
+ /**
63
+ * A conta tem todos os dados de cobrança/fiscais necessários pra emissão de nota.
64
+ * Retorna `false` quando a conta ainda não carregou — quem decide bloquear é o
65
+ * chamador, que checa o loading antes (ver `useRequiredBillingData`).
66
+ */
67
+ export function hasCompleteBillingData(
68
+ account: BillingDataSnapshot | null | undefined,
69
+ ): boolean {
70
+ if (!account) return false;
71
+
72
+ const fields = isInternationalAccount(account)
73
+ ? INTERNATIONAL_BILLING_FIELDS
74
+ : BR_BILLING_FIELDS;
75
+
76
+ for (const field of fields) {
77
+ if (!isFilled(account[field])) return false;
78
+ }
79
+
80
+ return true;
81
+ }
@@ -0,0 +1,27 @@
1
+ import type { Subscription } from '../types/subscription.type';
2
+
3
+ const FREE_PLAN_ID = 5;
4
+
5
+ type PaidSubscriptionSnapshot = Pick<Subscription, 'type' | 'id_plan' | 'charged'>;
6
+
7
+ /**
8
+ * A conta já teve pagamento confirmado numa assinatura paga.
9
+ *
10
+ * Diferente de `hasActivePaidSubscription`, aqui NÃO importa se a assinatura
11
+ * segue vigente: quem pagou e depois cancelou/atrasou continua precisando de
12
+ * nota fiscal do que já foi cobrado. Por isso o sinal é `charged` (marcado pelo
13
+ * backend no `bill_paid`), e não `active`/`date_due`.
14
+ *
15
+ * Free e trial ficam de fora — nunca houve cobrança a documentar.
16
+ */
17
+ export function hasPaidSubscription(
18
+ subscription: PaidSubscriptionSnapshot | undefined | null,
19
+ ): boolean {
20
+ if (!subscription) return false;
21
+ if (subscription.type === 'free' || subscription.id_plan === FREE_PLAN_ID) {
22
+ return false;
23
+ }
24
+ if (subscription.type === 'trial') return false;
25
+
26
+ return subscription.charged === true;
27
+ }
package/src/server.ts CHANGED
@@ -59,6 +59,7 @@ export {
59
59
  updateUserAction,
60
60
  updateAccountAction,
61
61
  updateAccountUserByIdAction,
62
+ updateBillingDataAction,
62
63
  deleteAccountUserAction,
63
64
  deleteAccountAction,
64
65
  createAccountUserAction,