@greatapps/common 1.1.795 → 1.1.796
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/dist/components/modals/billing/BillingDataForm.mjs +406 -0
- package/dist/components/modals/billing/BillingDataForm.mjs.map +1 -0
- package/dist/components/modals/billing/RequiredBillingDataModal.mjs +5 -405
- package/dist/components/modals/billing/RequiredBillingDataModal.mjs.map +1 -1
- package/dist/i18n/resolve-locale.mjs +1 -1
- package/dist/i18n/resolve-locale.mjs.map +1 -1
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -1
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs +3 -0
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs.map +1 -1
- package/dist/modules/whitelabel/actions/find-whitelabel.action.mjs +1 -5
- package/dist/modules/whitelabel/actions/find-whitelabel.action.mjs.map +1 -1
- package/dist/modules/whitelabel/constants/whitelabel.constants.mjs +11 -0
- package/dist/modules/whitelabel/constants/whitelabel.constants.mjs.map +1 -0
- package/dist/modules/whitelabel/services/whitelabel.service.mjs +69 -11
- package/dist/modules/whitelabel/services/whitelabel.service.mjs.map +1 -1
- package/package.json +10 -9
- package/src/components/modals/billing/BillingDataForm.tsx +506 -0
- package/src/components/modals/billing/RequiredBillingDataModal.tsx +3 -475
- package/src/i18n/resolve-locale.ts +1 -1
- package/src/index.ts +3 -0
- package/src/modules/ia-credits/types/ai-credits-offer.type.ts +3 -0
- package/src/modules/whitelabel/actions/find-whitelabel.action.ts +1 -7
- package/src/modules/whitelabel/constants/whitelabel.constants.ts +14 -0
- package/src/modules/whitelabel/services/whitelabel.service.ts +282 -209
|
@@ -1,291 +1,21 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any -- o generic Translator do next-intl não é
|
|
2
|
-
expressável estruturalmente; o `any` do TranslateFn é intencional e está contido neste arquivo
|
|
3
|
-
(mesmo padrão de modules/plans/utils/map-api-plan-to-ui.ts). */
|
|
4
1
|
'use client';
|
|
5
2
|
|
|
6
|
-
import {
|
|
7
|
-
import { Controller, useForm } from 'react-hook-form';
|
|
8
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
3
|
+
import { useState } from 'react';
|
|
9
4
|
import { useTranslations } from 'next-intl';
|
|
10
|
-
import { toast } from 'sonner';
|
|
11
|
-
import z from 'zod';
|
|
12
5
|
import { IconFileInvoice } from '@tabler/icons-react';
|
|
13
6
|
|
|
14
7
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../ui/overlay/Dialog';
|
|
15
|
-
import { Button } from '../../ui/buttons/Button';
|
|
16
|
-
import { FormField } from '../../ui/form/FormField';
|
|
17
|
-
import { SelectField } from '../../ui/form/SelectField';
|
|
18
|
-
import { Toast } from '../../ui/feedback/Toast';
|
|
19
8
|
import { useAuth } from '../../../providers/auth.provider';
|
|
20
|
-
import { useViaCep } from '../../../modules/accounts/hooks/useViaCep';
|
|
21
|
-
import { useUpdateBillingData } from '../../../modules/accounts/hooks/useAccountManagement';
|
|
22
9
|
import { useRequiredBillingData } from '../../../modules/accounts/hooks/use-required-billing-data.hook';
|
|
23
|
-
import {
|
|
24
|
-
import { readGeoCountry } from '../../../utils/geo-country';
|
|
25
|
-
import { BR_STATE_OPTIONS } from '../../../utils/constants/br-states';
|
|
26
|
-
import { formatCNPJ, formatCPF, formatPostalCode } from '../../../utils/format/masks';
|
|
27
|
-
import { isValidCNPJ, isValidCPF } from '../../../utils/validators/common';
|
|
28
|
-
import type { UpdateBillingDataRequest } from '../../../modules/accounts/types';
|
|
29
|
-
|
|
30
|
-
type TranslateFn = (key: any, values?: Record<string, string | number>) => string;
|
|
31
|
-
|
|
32
|
-
type BillingDataFormValues = {
|
|
33
|
-
personType: 'pf' | 'pj';
|
|
34
|
-
document: string;
|
|
35
|
-
financialName: string;
|
|
36
|
-
financialEmail: string;
|
|
37
|
-
cep: string;
|
|
38
|
-
street: string;
|
|
39
|
-
streetNumber: string;
|
|
40
|
-
complement?: string;
|
|
41
|
-
neighborhood: string;
|
|
42
|
-
city: string;
|
|
43
|
-
state: string;
|
|
44
|
-
country: string;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const COUNTRY_OPTIONS = COUNTRIES.map((country: { iso: string; name: string }) => ({
|
|
48
|
-
value: country.iso,
|
|
49
|
-
label: country.name,
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
const BR_REQUIRED_ADDRESS_FIELDS: readonly (keyof BillingDataFormValues)[] = [
|
|
53
|
-
'street',
|
|
54
|
-
'streetNumber',
|
|
55
|
-
'neighborhood',
|
|
56
|
-
'city',
|
|
57
|
-
'state',
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
function buildBillingDataSchema(translate: TranslateFn) {
|
|
61
|
-
const base = z.object({
|
|
62
|
-
personType: z.enum(['pf', 'pj']),
|
|
63
|
-
document: z.string().trim(),
|
|
64
|
-
financialName: z
|
|
65
|
-
.string()
|
|
66
|
-
.trim()
|
|
67
|
-
.min(1, translate('common.billing.requiredData.requiredField')),
|
|
68
|
-
financialEmail: z
|
|
69
|
-
.string()
|
|
70
|
-
.trim()
|
|
71
|
-
.min(1, translate('common.billing.requiredData.requiredField'))
|
|
72
|
-
.email(translate('common.billing.requiredData.invalidEmail')),
|
|
73
|
-
cep: z.string().trim(),
|
|
74
|
-
street: z.string().trim(),
|
|
75
|
-
streetNumber: z.string().trim(),
|
|
76
|
-
complement: z.string().trim().optional(),
|
|
77
|
-
neighborhood: z.string().trim(),
|
|
78
|
-
city: z.string().trim(),
|
|
79
|
-
state: z.string().trim(),
|
|
80
|
-
country: z.string().trim().min(1, translate('common.billing.requiredData.requiredField')),
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
return base.superRefine((data, ctx) => {
|
|
84
|
-
if (data.country !== 'BR') return;
|
|
85
|
-
|
|
86
|
-
for (const field of BR_REQUIRED_ADDRESS_FIELDS) {
|
|
87
|
-
if (!data[field]) {
|
|
88
|
-
ctx.addIssue({
|
|
89
|
-
code: 'custom',
|
|
90
|
-
path: [field],
|
|
91
|
-
message: translate('common.billing.requiredData.requiredField'),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (data.cep.replace(/\D/g, '').length !== 8) {
|
|
97
|
-
ctx.addIssue({
|
|
98
|
-
code: 'custom',
|
|
99
|
-
path: ['cep'],
|
|
100
|
-
message: translate('common.billing.requiredData.invalidCep'),
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const isCompany = data.personType === 'pj';
|
|
105
|
-
const digits = data.document.replace(/\D/g, '');
|
|
106
|
-
|
|
107
|
-
if (digits.length !== (isCompany ? 14 : 11)) {
|
|
108
|
-
ctx.addIssue({
|
|
109
|
-
code: 'custom',
|
|
110
|
-
path: ['document'],
|
|
111
|
-
message: isCompany
|
|
112
|
-
? translate('common.billing.requiredData.invalidCnpj')
|
|
113
|
-
: translate('common.billing.requiredData.invalidCpf'),
|
|
114
|
-
});
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/* Dígito verificador, mesmo algoritmo do backend: sem isso 111.111.111-11 passa
|
|
119
|
-
* aqui e o PUT da conta é recusado depois, sem o usuário saber o motivo. */
|
|
120
|
-
const isValid = isCompany ? isValidCNPJ(digits) : isValidCPF(digits);
|
|
121
|
-
if (!isValid) {
|
|
122
|
-
ctx.addIssue({
|
|
123
|
-
code: 'custom',
|
|
124
|
-
path: ['document'],
|
|
125
|
-
message: isCompany
|
|
126
|
-
? translate('common.billing.requiredData.invalidCnpjChecksum')
|
|
127
|
-
: translate('common.billing.requiredData.invalidCpfChecksum'),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function formatDocument(document: string | null | undefined, isCompany: boolean) {
|
|
134
|
-
if (!document) return '';
|
|
135
|
-
return isCompany ? formatCNPJ(document) : formatCPF(document);
|
|
136
|
-
}
|
|
10
|
+
import { BillingDataForm } from './BillingDataForm';
|
|
137
11
|
|
|
138
12
|
function RequiredBillingDataModalContent() {
|
|
139
13
|
const translate = useTranslations();
|
|
140
|
-
const { user } = useAuth();
|
|
141
14
|
const { isRequired, account } = useRequiredBillingData();
|
|
142
|
-
const { mutateAsync: updateBillingData } = useUpdateBillingData();
|
|
143
|
-
const { mutateAsync: lookupCep } = useViaCep();
|
|
144
15
|
|
|
145
16
|
/* O modal fecha na hora do submit e não reabre enquanto o invalidate do account
|
|
146
17
|
* não volta — sem isso o usuário vê o modal piscar de novo com o cache antigo. */
|
|
147
18
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
148
|
-
const isPrefilled = useRef(false);
|
|
149
|
-
|
|
150
|
-
const schema = useMemo(
|
|
151
|
-
() => buildBillingDataSchema(translate),
|
|
152
|
-
[translate],
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
const form = useForm<BillingDataFormValues>({
|
|
156
|
-
resolver: zodResolver(schema),
|
|
157
|
-
mode: 'onChange',
|
|
158
|
-
defaultValues: {
|
|
159
|
-
personType: 'pf',
|
|
160
|
-
document: '',
|
|
161
|
-
financialName: '',
|
|
162
|
-
financialEmail: '',
|
|
163
|
-
cep: '',
|
|
164
|
-
street: '',
|
|
165
|
-
streetNumber: '',
|
|
166
|
-
complement: '',
|
|
167
|
-
neighborhood: '',
|
|
168
|
-
city: '',
|
|
169
|
-
state: '',
|
|
170
|
-
country: 'BR',
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const {
|
|
175
|
-
register,
|
|
176
|
-
control,
|
|
177
|
-
handleSubmit,
|
|
178
|
-
reset,
|
|
179
|
-
setValue,
|
|
180
|
-
watch,
|
|
181
|
-
formState: { errors, isSubmitting, isValid },
|
|
182
|
-
} = form;
|
|
183
|
-
|
|
184
|
-
const personType = watch('personType');
|
|
185
|
-
const isCompany = personType === 'pj';
|
|
186
|
-
const isBrazil = watch('country') === 'BR';
|
|
187
|
-
|
|
188
|
-
useEffect(() => {
|
|
189
|
-
if (!account || isPrefilled.current) return;
|
|
190
|
-
isPrefilled.current = true;
|
|
191
|
-
|
|
192
|
-
const accountIsCompany = account.financial_document_type === 2;
|
|
193
|
-
reset({
|
|
194
|
-
personType: accountIsCompany ? 'pj' : 'pf',
|
|
195
|
-
document: formatDocument(account.financial_document, accountIsCompany),
|
|
196
|
-
financialName: account.financial_name ?? '',
|
|
197
|
-
financialEmail: account.financial_email || user?.email || '',
|
|
198
|
-
cep: account.zipcode ?? '',
|
|
199
|
-
street: account.address ?? '',
|
|
200
|
-
streetNumber: account.address_number ?? '',
|
|
201
|
-
complement: account.address_complement ?? '',
|
|
202
|
-
neighborhood: account.neighborhood ?? '',
|
|
203
|
-
city: account.city ?? '',
|
|
204
|
-
state: account.state ?? '',
|
|
205
|
-
country: account.country || readGeoCountry() || 'BR',
|
|
206
|
-
});
|
|
207
|
-
}, [account, reset, user?.email]);
|
|
208
|
-
|
|
209
|
-
const handleDocumentChange = (rawValue: string) => {
|
|
210
|
-
setValue('document', isCompany ? formatCNPJ(rawValue) : formatCPF(rawValue), {
|
|
211
|
-
shouldValidate: true,
|
|
212
|
-
});
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const handlePersonTypeChange = (value: 'pf' | 'pj') => {
|
|
216
|
-
if (value === personType) return;
|
|
217
|
-
setValue('document', '');
|
|
218
|
-
/* shouldValidate reroda o resolver: sem isso o `isValid` fica preso no valor
|
|
219
|
-
* do documento antigo e o botão continua habilitado com o campo já limpo. */
|
|
220
|
-
setValue('personType', value, { shouldValidate: true });
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const handleCepChange = (rawValue: string) => {
|
|
224
|
-
setValue('cep', formatPostalCode(rawValue), { shouldValidate: true });
|
|
225
|
-
|
|
226
|
-
const digits = rawValue.replace(/\D/g, '');
|
|
227
|
-
if (digits.length !== 8) return;
|
|
228
|
-
|
|
229
|
-
lookupCep(digits)
|
|
230
|
-
.then((data) => {
|
|
231
|
-
setValue('street', data.logradouro, { shouldValidate: true });
|
|
232
|
-
setValue('neighborhood', data.bairro, { shouldValidate: true });
|
|
233
|
-
setValue('city', data.localidade, { shouldValidate: true });
|
|
234
|
-
setValue('state', data.uf, { shouldValidate: true });
|
|
235
|
-
})
|
|
236
|
-
.catch(() => {
|
|
237
|
-
// CEP não encontrado: o usuário preenche o endereço na mão.
|
|
238
|
-
});
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
async function onSubmit(data: BillingDataFormValues) {
|
|
242
|
-
const payload: UpdateBillingDataRequest =
|
|
243
|
-
data.country === 'BR'
|
|
244
|
-
? {
|
|
245
|
-
financial_document_type: data.personType === 'pf' ? 1 : 2,
|
|
246
|
-
financial_document: data.document,
|
|
247
|
-
financial_name: data.financialName,
|
|
248
|
-
financial_email: data.financialEmail,
|
|
249
|
-
zipcode: data.cep,
|
|
250
|
-
address: data.street,
|
|
251
|
-
address_number: data.streetNumber,
|
|
252
|
-
address_complement: data.complement ?? '',
|
|
253
|
-
neighborhood: data.neighborhood,
|
|
254
|
-
city: data.city,
|
|
255
|
-
state: data.state,
|
|
256
|
-
country: data.country,
|
|
257
|
-
}
|
|
258
|
-
: {
|
|
259
|
-
financial_name: data.financialName,
|
|
260
|
-
financial_email: data.financialEmail,
|
|
261
|
-
country: data.country,
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
await updateBillingData(payload);
|
|
266
|
-
setIsSubmitted(true);
|
|
267
|
-
toast.custom(
|
|
268
|
-
(t) => (
|
|
269
|
-
<Toast
|
|
270
|
-
variant="success"
|
|
271
|
-
message={translate('common.billing.requiredData.successToast')}
|
|
272
|
-
toastId={t}
|
|
273
|
-
/>
|
|
274
|
-
),
|
|
275
|
-
{ duration: 5000 },
|
|
276
|
-
);
|
|
277
|
-
} catch (error) {
|
|
278
|
-
/* O backend devolve a razão exata da recusa (ex.: documento fiscal inválido).
|
|
279
|
-
* Trocar por um texto genérico deixaria o usuário travado sem saber o que corrigir. */
|
|
280
|
-
const message =
|
|
281
|
-
error instanceof Error && error.message
|
|
282
|
-
? error.message
|
|
283
|
-
: translate('common.billing.requiredData.errorToast');
|
|
284
|
-
toast.custom((t) => <Toast variant="error" message={message} toastId={t} />, {
|
|
285
|
-
duration: 5000,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
19
|
|
|
290
20
|
if (!isRequired || isSubmitted) return null;
|
|
291
21
|
|
|
@@ -306,212 +36,10 @@ function RequiredBillingDataModalContent() {
|
|
|
306
36
|
<DialogTitle className="paragraph-medium-semibold text-zinc-950">
|
|
307
37
|
{translate('common.billing.requiredData.title')}
|
|
308
38
|
</DialogTitle>
|
|
309
|
-
<span className="paragraph-small-regular text-zinc-600">
|
|
310
|
-
{isBrazil
|
|
311
|
-
? translate('common.billing.requiredData.description')
|
|
312
|
-
: translate('common.billing.requiredData.descriptionInternational')}
|
|
313
|
-
</span>
|
|
314
39
|
</div>
|
|
315
40
|
</DialogHeader>
|
|
316
41
|
|
|
317
|
-
<
|
|
318
|
-
onSubmit={handleSubmit(onSubmit)}
|
|
319
|
-
className="flex flex-col flex-1 min-h-0"
|
|
320
|
-
noValidate
|
|
321
|
-
>
|
|
322
|
-
<div className="flex flex-col gap-6 p-4 lg:p-5 flex-1 overflow-y-auto">
|
|
323
|
-
<div className="flex flex-col gap-5">
|
|
324
|
-
<Controller
|
|
325
|
-
name="country"
|
|
326
|
-
control={control}
|
|
327
|
-
render={({ field }) => (
|
|
328
|
-
<SelectField
|
|
329
|
-
label={translate('common.billing.requiredData.country')}
|
|
330
|
-
placeholder={translate('common.billing.requiredData.select')}
|
|
331
|
-
value={field.value || undefined}
|
|
332
|
-
onChange={(value) =>
|
|
333
|
-
setValue('country', String(value), { shouldValidate: true })
|
|
334
|
-
}
|
|
335
|
-
options={COUNTRY_OPTIONS}
|
|
336
|
-
error={!!errors.country}
|
|
337
|
-
errorMessage={errors.country?.message}
|
|
338
|
-
/>
|
|
339
|
-
)}
|
|
340
|
-
/>
|
|
341
|
-
|
|
342
|
-
{isBrazil && (
|
|
343
|
-
<div className="flex gap-4">
|
|
344
|
-
<Controller
|
|
345
|
-
name="personType"
|
|
346
|
-
control={control}
|
|
347
|
-
render={({ field }) => (
|
|
348
|
-
<SelectField
|
|
349
|
-
label={translate('common.billing.requiredData.personType')}
|
|
350
|
-
placeholder={translate('common.billing.requiredData.select')}
|
|
351
|
-
value={field.value}
|
|
352
|
-
onChange={(value) => handlePersonTypeChange(value as 'pf' | 'pj')}
|
|
353
|
-
options={[
|
|
354
|
-
{
|
|
355
|
-
value: 'pf',
|
|
356
|
-
label: translate('common.billing.requiredData.personTypePf'),
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
value: 'pj',
|
|
360
|
-
label: translate('common.billing.requiredData.personTypePj'),
|
|
361
|
-
},
|
|
362
|
-
]}
|
|
363
|
-
containerClassName="flex-1"
|
|
364
|
-
/>
|
|
365
|
-
)}
|
|
366
|
-
/>
|
|
367
|
-
<div className="flex-1">
|
|
368
|
-
<FormField
|
|
369
|
-
label={
|
|
370
|
-
isCompany
|
|
371
|
-
? translate('common.billing.requiredData.cnpj')
|
|
372
|
-
: translate('common.billing.requiredData.cpf')
|
|
373
|
-
}
|
|
374
|
-
placeholder={isCompany ? '__.___.___/____-__' : '___.___.___-__'}
|
|
375
|
-
error={!!errors.document}
|
|
376
|
-
errorMessage={errors.document?.message}
|
|
377
|
-
{...register('document', {
|
|
378
|
-
onChange: (event) => handleDocumentChange(event.target.value),
|
|
379
|
-
})}
|
|
380
|
-
/>
|
|
381
|
-
</div>
|
|
382
|
-
</div>
|
|
383
|
-
)}
|
|
384
|
-
|
|
385
|
-
<FormField
|
|
386
|
-
label={
|
|
387
|
-
isBrazil && isCompany
|
|
388
|
-
? translate('common.billing.requiredData.companyName')
|
|
389
|
-
: translate('common.billing.requiredData.fullName')
|
|
390
|
-
}
|
|
391
|
-
placeholder={translate('common.billing.requiredData.typeHere')}
|
|
392
|
-
error={!!errors.financialName}
|
|
393
|
-
errorMessage={errors.financialName?.message}
|
|
394
|
-
{...register('financialName')}
|
|
395
|
-
/>
|
|
396
|
-
|
|
397
|
-
<FormField
|
|
398
|
-
label={translate('common.billing.requiredData.financialEmail')}
|
|
399
|
-
placeholder={translate('common.billing.requiredData.financialEmailPlaceholder')}
|
|
400
|
-
type="email"
|
|
401
|
-
error={!!errors.financialEmail}
|
|
402
|
-
errorMessage={errors.financialEmail?.message}
|
|
403
|
-
{...register('financialEmail')}
|
|
404
|
-
/>
|
|
405
|
-
</div>
|
|
406
|
-
|
|
407
|
-
{isBrazil && (
|
|
408
|
-
<>
|
|
409
|
-
<div className="h-px bg-zinc-200" />
|
|
410
|
-
|
|
411
|
-
<div className="flex flex-col gap-5">
|
|
412
|
-
<span className="paragraph-small-semibold text-zinc-950">
|
|
413
|
-
{translate('common.billing.requiredData.addressHeading')}
|
|
414
|
-
</span>
|
|
415
|
-
|
|
416
|
-
<FormField
|
|
417
|
-
label={translate('common.billing.requiredData.cep')}
|
|
418
|
-
placeholder="_____-___"
|
|
419
|
-
error={!!errors.cep}
|
|
420
|
-
errorMessage={errors.cep?.message}
|
|
421
|
-
{...register('cep', {
|
|
422
|
-
onChange: (event) => handleCepChange(event.target.value),
|
|
423
|
-
})}
|
|
424
|
-
/>
|
|
425
|
-
|
|
426
|
-
<div className="flex gap-4">
|
|
427
|
-
<div className="flex-[3]">
|
|
428
|
-
<FormField
|
|
429
|
-
label={translate('common.billing.requiredData.street')}
|
|
430
|
-
placeholder={translate('common.billing.requiredData.typeHere')}
|
|
431
|
-
error={!!errors.street}
|
|
432
|
-
errorMessage={errors.street?.message}
|
|
433
|
-
{...register('street')}
|
|
434
|
-
/>
|
|
435
|
-
</div>
|
|
436
|
-
<div className="flex-1">
|
|
437
|
-
<FormField
|
|
438
|
-
label={translate('common.billing.requiredData.streetNumber')}
|
|
439
|
-
placeholder="000"
|
|
440
|
-
error={!!errors.streetNumber}
|
|
441
|
-
errorMessage={errors.streetNumber?.message}
|
|
442
|
-
{...register('streetNumber')}
|
|
443
|
-
/>
|
|
444
|
-
</div>
|
|
445
|
-
</div>
|
|
446
|
-
|
|
447
|
-
<div className="flex gap-4">
|
|
448
|
-
<div className="flex-1">
|
|
449
|
-
<FormField
|
|
450
|
-
label={translate('common.billing.requiredData.neighborhood')}
|
|
451
|
-
placeholder={translate('common.billing.requiredData.typeHere')}
|
|
452
|
-
error={!!errors.neighborhood}
|
|
453
|
-
errorMessage={errors.neighborhood?.message}
|
|
454
|
-
{...register('neighborhood')}
|
|
455
|
-
/>
|
|
456
|
-
</div>
|
|
457
|
-
<div className="flex-1">
|
|
458
|
-
<FormField
|
|
459
|
-
label={translate('common.billing.requiredData.complement')}
|
|
460
|
-
optional
|
|
461
|
-
placeholder={translate('common.billing.requiredData.typeHere')}
|
|
462
|
-
{...register('complement')}
|
|
463
|
-
/>
|
|
464
|
-
</div>
|
|
465
|
-
</div>
|
|
466
|
-
|
|
467
|
-
<div className="flex gap-4">
|
|
468
|
-
<div className="flex-1">
|
|
469
|
-
<FormField
|
|
470
|
-
label={translate('common.billing.requiredData.city')}
|
|
471
|
-
placeholder={translate('common.billing.requiredData.typeHere')}
|
|
472
|
-
error={!!errors.city}
|
|
473
|
-
errorMessage={errors.city?.message}
|
|
474
|
-
{...register('city')}
|
|
475
|
-
/>
|
|
476
|
-
</div>
|
|
477
|
-
<div className="flex-1">
|
|
478
|
-
<Controller
|
|
479
|
-
name="state"
|
|
480
|
-
control={control}
|
|
481
|
-
render={({ field }) => (
|
|
482
|
-
<SelectField
|
|
483
|
-
label={translate('common.billing.requiredData.state')}
|
|
484
|
-
placeholder={translate('common.billing.requiredData.select')}
|
|
485
|
-
value={field.value || undefined}
|
|
486
|
-
onChange={(value) =>
|
|
487
|
-
setValue('state', String(value), { shouldValidate: true })
|
|
488
|
-
}
|
|
489
|
-
options={BR_STATE_OPTIONS}
|
|
490
|
-
error={!!errors.state}
|
|
491
|
-
errorMessage={errors.state?.message}
|
|
492
|
-
/>
|
|
493
|
-
)}
|
|
494
|
-
/>
|
|
495
|
-
</div>
|
|
496
|
-
</div>
|
|
497
|
-
</div>
|
|
498
|
-
</>
|
|
499
|
-
)}
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
<div className="flex items-center justify-end p-4 lg:p-5 border-t border-zinc-200 shrink-0">
|
|
503
|
-
<Button
|
|
504
|
-
type="submit"
|
|
505
|
-
className="h-10! w-full lg:w-fit"
|
|
506
|
-
disabled={!isValid || isSubmitting}
|
|
507
|
-
loading={isSubmitting}
|
|
508
|
-
>
|
|
509
|
-
{isSubmitting
|
|
510
|
-
? translate('common.billing.requiredData.submitting')
|
|
511
|
-
: translate('common.billing.requiredData.submit')}
|
|
512
|
-
</Button>
|
|
513
|
-
</div>
|
|
514
|
-
</form>
|
|
42
|
+
<BillingDataForm account={account} onSaved={() => setIsSubmitted(true)} />
|
|
515
43
|
</DialogContent>
|
|
516
44
|
</Dialog>
|
|
517
45
|
);
|
|
@@ -5,10 +5,10 @@ import { DEFAULT_LOCALE, Locale, LOCALE_COOKIE } from './config';
|
|
|
5
5
|
import { localeFromCountry } from './country-language';
|
|
6
6
|
import { normalizeLocale } from './normalize';
|
|
7
7
|
import { decodeJwtPayload } from '../utils/jwt-payload';
|
|
8
|
+
import { DEFAULT_WHITELABEL_ID } from '../modules/whitelabel/constants/whitelabel.constants';
|
|
8
9
|
|
|
9
10
|
const AUTH_COOKIE = 'greatapps';
|
|
10
11
|
const GEO_HEADER = 'cf-ipcountry';
|
|
11
|
-
const DEFAULT_WHITELABEL_ID = 1;
|
|
12
12
|
|
|
13
13
|
interface JwtLocaleInfo {
|
|
14
14
|
id_wl?: number;
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Types
|
|
2
2
|
export * from "./modules/auth/schema";
|
|
3
3
|
export * from "./modules/users/schema";
|
|
4
|
+
export { UserProfile } from "./modules/users/schema";
|
|
4
5
|
export * from "./modules/whitelabel/schema";
|
|
5
6
|
// Style types now come from whitelabel schema directly
|
|
6
7
|
export * from "./infra/api/types";
|
|
@@ -281,6 +282,8 @@ export { default as PaidPlanRequiredModal } from "./components/modals/PaidPlanRe
|
|
|
281
282
|
export { default as AccountFrozenModal } from "./components/modals/AccountFrozenModal";
|
|
282
283
|
export { default as AccountDeletionWarningModal } from "./components/modals/AccountDeletionWarningModal";
|
|
283
284
|
export { default as RequiredBillingDataModal } from "./components/modals/billing/RequiredBillingDataModal";
|
|
285
|
+
export { BillingDataForm } from "./components/modals/billing/BillingDataForm";
|
|
286
|
+
export type { BillingDataFormProps } from "./components/modals/billing/BillingDataForm";
|
|
284
287
|
export { default as AddCardModal } from "./components/modals/cards/AddCardModal";
|
|
285
288
|
export { DeleteCardModal } from "./components/modals/cards/DeleteCardModal";
|
|
286
289
|
export { CannotDeleteCardModal } from "./components/modals/cards/CannotDeleteCardModal";
|
|
@@ -71,6 +71,9 @@ const PurchaseIaCreditsPixDataSchema = z.object({
|
|
|
71
71
|
export const PurchaseIaCreditsResultSchema = z.object({
|
|
72
72
|
status: z.number(),
|
|
73
73
|
message: z.string().optional(),
|
|
74
|
+
/** Code estável da recusa (ex.: `FISCAL_DOCUMENT_REQUIRED`) — sem isso o `z.object` faz strip e o
|
|
75
|
+
* code do backend morre antes de chegar em quem decide reabrir um passo do fluxo por ele. */
|
|
76
|
+
code: z.string().optional(),
|
|
74
77
|
client_secret: z.string().optional(),
|
|
75
78
|
data: z
|
|
76
79
|
.array(
|
|
@@ -7,13 +7,7 @@ import { ApiError } from "../../../infra/api/types";
|
|
|
7
7
|
import { cache } from "react";
|
|
8
8
|
|
|
9
9
|
const findWhitelabelByHost = cache(async (host: string) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (!whitelabel) {
|
|
13
|
-
throw new ApiError('Whitelabel not found', 'WL_NOT_FOUND', 404);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return whitelabel;
|
|
10
|
+
return whitelabelService.getTokenByDomain(host);
|
|
17
11
|
});
|
|
18
12
|
|
|
19
13
|
export const findWhitelabel = async () => {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whitelabel 1 é a Great por definição — dono do domínio-mãe e o único destino
|
|
3
|
+
* seguro quando um host não tem whitelabel própria (ver `WhitelabelService.getDefault`).
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_WHITELABEL_ID = 1;
|
|
6
|
+
|
|
7
|
+
/** Domínio da whitelabel padrão, usado quando `WHITELABEL_DEFAULT_DOMAIN` não está configurada. */
|
|
8
|
+
export const DEFAULT_WHITELABEL_DOMAIN = 'greatpages.com.br';
|
|
9
|
+
|
|
10
|
+
/** TTL do cache positivo (token resolvido com sucesso) — uma semana. */
|
|
11
|
+
export const WHITELABEL_CACHE_TTL_SECONDS = 604800;
|
|
12
|
+
|
|
13
|
+
/** TTL do cache negativo (host sem whitelabel) — curto, pra não travar 5 minutos um domínio novo. */
|
|
14
|
+
export const MISSING_WHITELABEL_CACHE_TTL_SECONDS = 300;
|