@greatapps/common 1.1.797 → 1.1.799

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.
@@ -20,7 +20,7 @@ import { useUpdateBillingData } from '../../../modules/accounts/hooks/useAccount
20
20
  import { COUNTRIES } from '../../../utils/countries';
21
21
  import { readGeoCountry } from '../../../utils/geo-country';
22
22
  import { BR_STATE_OPTIONS } from '../../../utils/constants/br-states';
23
- import { formatCNPJ, formatCPF, formatPostalCode } from '../../../utils/format/masks';
23
+ import { formatCPFCNPJ, formatPostalCode } from '../../../utils/format/masks';
24
24
  import { isValidCNPJ, isValidCPF } from '../../../utils/validators/common';
25
25
  import type { Account } from '../../../modules/accounts/types';
26
26
  import type { UpdateBillingDataRequest } from '../../../modules/accounts/types';
@@ -28,7 +28,6 @@ import type { UpdateBillingDataRequest } from '../../../modules/accounts/types';
28
28
  type TranslateFn = (key: any, values?: Record<string, string | number>) => string;
29
29
 
30
30
  type BillingDataFormValues = {
31
- personType: 'pf' | 'pj';
32
31
  document: string;
33
32
  financialName: string;
34
33
  financialEmail: string;
@@ -55,9 +54,12 @@ const BR_REQUIRED_ADDRESS_FIELDS: readonly (keyof BillingDataFormValues)[] = [
55
54
  'state',
56
55
  ];
57
56
 
57
+ function isCompanyDocument(document: string): boolean {
58
+ return document.replace(/\D/g, '').length > 11;
59
+ }
60
+
58
61
  function buildBillingDataSchema(translate: TranslateFn) {
59
62
  const base = z.object({
60
- personType: z.enum(['pf', 'pj']),
61
63
  document: z.string().trim(),
62
64
  financialName: z
63
65
  .string()
@@ -99,7 +101,7 @@ function buildBillingDataSchema(translate: TranslateFn) {
99
101
  });
100
102
  }
101
103
 
102
- const isCompany = data.personType === 'pj';
104
+ const isCompany = isCompanyDocument(data.document);
103
105
  const digits = data.document.replace(/\D/g, '');
104
106
 
105
107
  if (digits.length !== (isCompany ? 14 : 11)) {
@@ -126,11 +128,6 @@ function buildBillingDataSchema(translate: TranslateFn) {
126
128
  });
127
129
  }
128
130
 
129
- function formatDocument(document: string | null | undefined, isCompany: boolean) {
130
- if (!document) return '';
131
- return isCompany ? formatCNPJ(document) : formatCPF(document);
132
- }
133
-
134
131
  export interface BillingDataFormProps {
135
132
  account: Account | undefined;
136
133
  onSaved: () => void;
@@ -155,7 +152,6 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
155
152
  resolver: zodResolver(schema),
156
153
  mode: 'onChange',
157
154
  defaultValues: {
158
- personType: 'pf',
159
155
  document: '',
160
156
  financialName: '',
161
157
  financialEmail: '',
@@ -180,18 +176,15 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
180
176
  formState: { errors, isSubmitting, isValid },
181
177
  } = form;
182
178
 
183
- const personType = watch('personType');
184
- const isCompany = personType === 'pj';
179
+ const isCompany = isCompanyDocument(watch('document'));
185
180
  const isBrazil = watch('country') === 'BR';
186
181
 
187
182
  useEffect(() => {
188
183
  if (!account || isPrefilled.current) return;
189
184
  isPrefilled.current = true;
190
185
 
191
- const accountIsCompany = account.financial_document_type === 2;
192
186
  reset({
193
- personType: accountIsCompany ? 'pj' : 'pf',
194
- document: formatDocument(account.financial_document, accountIsCompany),
187
+ document: formatCPFCNPJ(account.financial_document ?? ''),
195
188
  financialName: account.financial_name ?? '',
196
189
  financialEmail: account.financial_email || user?.email || '',
197
190
  cep: account.zipcode ?? '',
@@ -206,15 +199,7 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
206
199
  }, [account, country, reset, user?.email]);
207
200
 
208
201
  const handleDocumentChange = (rawValue: string) => {
209
- setValue('document', isCompany ? formatCNPJ(rawValue) : formatCPF(rawValue), {
210
- shouldValidate: true,
211
- });
212
- };
213
-
214
- const handlePersonTypeChange = (value: 'pf' | 'pj') => {
215
- if (value === personType) return;
216
- setValue('document', '');
217
- setValue('personType', value, { shouldValidate: true });
202
+ setValue('document', formatCPFCNPJ(rawValue), { shouldValidate: true });
218
203
  };
219
204
 
220
205
  const handleCepChange = (rawValue: string) => {
@@ -237,7 +222,7 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
237
222
  const payload: UpdateBillingDataRequest =
238
223
  data.country === 'BR'
239
224
  ? {
240
- financial_document_type: data.personType === 'pf' ? 1 : 2,
225
+ financial_document_type: isCompanyDocument(data.document) ? 2 : 1,
241
226
  financial_document: data.document,
242
227
  financial_name: data.financialName,
243
228
  financial_email: data.financialEmail,
@@ -312,49 +297,6 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
312
297
  </>
313
298
  )}
314
299
 
315
- {isBrazil && (
316
- <div className="flex gap-4">
317
- <Controller
318
- name="personType"
319
- control={control}
320
- render={({ field }) => (
321
- <SelectField
322
- label={translate('common.billing.requiredData.personType')}
323
- placeholder={translate('common.billing.requiredData.select')}
324
- value={field.value}
325
- onChange={(value) => handlePersonTypeChange(value as 'pf' | 'pj')}
326
- options={[
327
- {
328
- value: 'pf',
329
- label: translate('common.billing.requiredData.personTypePf'),
330
- },
331
- {
332
- value: 'pj',
333
- label: translate('common.billing.requiredData.personTypePj'),
334
- },
335
- ]}
336
- containerClassName="flex-1"
337
- />
338
- )}
339
- />
340
- <div className="flex-1">
341
- <FormField
342
- label={
343
- isCompany
344
- ? translate('common.billing.requiredData.cnpj')
345
- : translate('common.billing.requiredData.cpf')
346
- }
347
- placeholder={isCompany ? '__.___.___/____-__' : '___.___.___-__'}
348
- error={!!errors.document}
349
- errorMessage={errors.document?.message}
350
- {...register('document', {
351
- onChange: (event) => handleDocumentChange(event.target.value),
352
- })}
353
- />
354
- </div>
355
- </div>
356
- )}
357
-
358
300
  <FormField
359
301
  label={
360
302
  isBrazil && isCompany
@@ -367,6 +309,18 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
367
309
  {...register('financialName')}
368
310
  />
369
311
 
312
+ {isBrazil && (
313
+ <FormField
314
+ label={translate('common.billing.requiredData.document')}
315
+ placeholder={translate('common.billing.requiredData.documentPlaceholder')}
316
+ error={!!errors.document}
317
+ errorMessage={errors.document?.message}
318
+ {...register('document', {
319
+ onChange: (event) => handleDocumentChange(event.target.value),
320
+ })}
321
+ />
322
+ )}
323
+
370
324
  <FormField
371
325
  label={translate('common.billing.requiredData.financialEmail')}
372
326
  placeholder={translate('common.billing.requiredData.financialEmailPlaceholder')}
@@ -1224,11 +1224,8 @@ const messages = {
1224
1224
  addressHeading: 'Billing address',
1225
1225
  select: 'Select',
1226
1226
  typeHere: 'Type here',
1227
- personType: 'Entity type',
1228
- personTypePf: 'Individual',
1229
- personTypePj: 'Company',
1230
- cpf: 'CPF',
1231
- cnpj: 'CNPJ',
1227
+ document: 'CPF or CNPJ',
1228
+ documentPlaceholder: 'Enter your CPF or CNPJ',
1232
1229
  fullName: 'Full name',
1233
1230
  companyName: 'Legal name',
1234
1231
  financialEmail: 'Billing email',
@@ -1252,8 +1249,8 @@ const messages = {
1252
1249
  invalidCep: 'Invalid ZIP code',
1253
1250
  invalidCpf: 'Invalid CPF',
1254
1251
  invalidCnpj: 'Invalid CNPJ',
1255
- invalidCpfChecksum: 'Invalid CPF — check the verification digits',
1256
- invalidCnpjChecksum: 'Invalid CNPJ — check the verification digits',
1252
+ invalidCpfChecksum: 'Invalid CPF, check the verification digits',
1253
+ invalidCnpjChecksum: 'Invalid CNPJ, check the verification digits',
1257
1254
  },
1258
1255
  },
1259
1256
  navigation: {
@@ -1230,11 +1230,8 @@ const messages = {
1230
1230
  addressHeading: 'Dirección de facturación',
1231
1231
  select: 'Selecciona',
1232
1232
  typeHere: 'Escribe aquí',
1233
- personType: 'Tipo de persona',
1234
- personTypePf: 'Persona física',
1235
- personTypePj: 'Persona jurídica',
1236
- cpf: 'CPF',
1237
- cnpj: 'CNPJ',
1233
+ document: 'CPF o CNPJ',
1234
+ documentPlaceholder: 'Escribe tu CPF o CNPJ',
1238
1235
  fullName: 'Nombre completo',
1239
1236
  companyName: 'Razón social',
1240
1237
  financialEmail: 'Correo de facturación',
@@ -1258,8 +1255,8 @@ const messages = {
1258
1255
  invalidCep: 'Código postal inválido',
1259
1256
  invalidCpf: 'CPF inválido',
1260
1257
  invalidCnpj: 'CNPJ inválido',
1261
- invalidCpfChecksum: 'CPF inválido — revisa los dígitos verificadores',
1262
- invalidCnpjChecksum: 'CNPJ inválido — revisa los dígitos verificadores',
1258
+ invalidCpfChecksum: 'CPF inválido, revisa los dígitos verificadores',
1259
+ invalidCnpjChecksum: 'CNPJ inválido, revisa los dígitos verificadores',
1263
1260
  },
1264
1261
  },
1265
1262
  navigation: {
@@ -1241,11 +1241,8 @@ const messages = {
1241
1241
  addressHeading: 'Endereço de cobrança',
1242
1242
  select: 'Selecione',
1243
1243
  typeHere: 'Digite aqui',
1244
- personType: 'Tipo de pessoa',
1245
- personTypePf: 'Pessoa física',
1246
- personTypePj: 'Pessoa jurídica',
1247
- cpf: 'CPF',
1248
- cnpj: 'CNPJ',
1244
+ document: 'CPF ou CNPJ',
1245
+ documentPlaceholder: 'Digite seu CPF ou CNPJ',
1249
1246
  fullName: 'Nome completo',
1250
1247
  companyName: 'Razão social',
1251
1248
  financialEmail: 'E-mail financeiro',
@@ -1269,8 +1266,8 @@ const messages = {
1269
1266
  invalidCep: 'CEP inválido',
1270
1267
  invalidCpf: 'CPF inválido',
1271
1268
  invalidCnpj: 'CNPJ inválido',
1272
- invalidCpfChecksum: 'CPF inválido — confira os dígitos verificadores',
1273
- invalidCnpjChecksum: 'CNPJ inválido — confira os dígitos verificadores',
1269
+ invalidCpfChecksum: 'CPF inválido, confira os dígitos verificadores',
1270
+ invalidCnpjChecksum: 'CNPJ inválido, confira os dígitos verificadores',
1274
1271
  },
1275
1272
  },
1276
1273
 
@@ -1,4 +1,5 @@
1
1
  import type { Account } from '../types';
2
+ import { isValidCNPJ, isValidCPF } from '../../../utils/validators/common';
2
3
 
3
4
  export type BillingDataSnapshot = Pick<
4
5
  Account,
@@ -59,6 +60,20 @@ function isFilled(value: string | null | undefined): boolean {
59
60
  return typeof value === 'string' && value.trim().length > 0;
60
61
  }
61
62
 
63
+ /**
64
+ * Documento preenchido não é documento bom. O caso que originou isto: uma conta pagava desde
65
+ * junho/2025 com um CNPJ de um dígito trocado, a prefeitura recusava a NFS-e todo mês e o modal
66
+ * nunca abria, porque o campo estava preenchido. Conferir o dígito verificador aqui é o que faz
67
+ * o cliente ser levado a corrigir — o backend já recusa a escrita de um documento inválido desde
68
+ * 28/05/2026, então o formulário não tem como salvar um valor ruim por cima.
69
+ */
70
+ function hasValidDocument(document: string | null | undefined): boolean {
71
+ const digits = (document ?? '').replace(/\D/g, '');
72
+ if (digits.length === 11) return isValidCPF(digits);
73
+ if (digits.length === 14) return isValidCNPJ(digits);
74
+ return false;
75
+ }
76
+
62
77
  /**
63
78
  * A conta tem todos os dados de cobrança/fiscais necessários pra emissão de nota.
64
79
  * Retorna `false` quando a conta ainda não carregou — quem decide bloquear é o
@@ -73,5 +88,5 @@ export function hasCompleteBillingData(
73
88
  if (!isFilled(account[field])) return false;
74
89
  }
75
90
 
76
- return true;
91
+ return hasValidDocument(account.financial_document);
77
92
  }