@greatapps/common 1.1.763 → 1.1.764

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.
@@ -1,4 +1,11 @@
1
1
  import { NextResponse } from "next/server";
2
+ import { GEO_COUNTRY_COOKIE, GEO_UNKNOWN_COUNTRIES } from "../utils/geo-country";
3
+ function publishGeoCountry(request, response) {
4
+ const country = request.headers.get("cf-ipcountry")?.toUpperCase();
5
+ if (!country || GEO_UNKNOWN_COUNTRIES.includes(country)) return;
6
+ if (request.cookies.get(GEO_COUNTRY_COOKIE)?.value === country) return;
7
+ response.cookies.set(GEO_COUNTRY_COOKIE, country, { path: "/", sameSite: "lax" });
8
+ }
2
9
  function shouldProcessPath(pathname, config) {
3
10
  const { excludePaths, includePaths } = config;
4
11
  if (excludePaths?.length) {
@@ -28,7 +35,9 @@ function createMiddlewareChain(middlewares) {
28
35
  currentRequest = result.request;
29
36
  }
30
37
  }
31
- return NextResponse.next();
38
+ const response = NextResponse.next();
39
+ publishGeoCountry(request, response);
40
+ return response;
32
41
  };
33
42
  }
34
43
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/middlewares/chain.ts"],"sourcesContent":["import { NextRequest, NextResponse } from 'next/server';\r\n\r\nimport { MiddlewareConfig } from './types';\r\n\r\nfunction shouldProcessPath(pathname: string, config: MiddlewareConfig): boolean {\r\n const { excludePaths, includePaths } = config;\r\n\r\n if (excludePaths?.length) {\r\n const isExcluded = excludePaths.some(\r\n (path) => pathname === path || pathname.startsWith(`${path}/`)\r\n );\r\n if (isExcluded) return false;\r\n }\r\n\r\n if (includePaths?.length) {\r\n return includePaths.some((path) => pathname === path || pathname.startsWith(`${path}/`));\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport function createMiddlewareChain(middlewares: MiddlewareConfig[]) {\r\n return async function chainedMiddleware(request: NextRequest): Promise<NextResponse> {\r\n const { pathname } = request.nextUrl;\r\n let currentRequest = request;\r\n\r\n for (const middleware of middlewares) {\r\n if (!shouldProcessPath(pathname, middleware)) {\r\n continue;\r\n }\r\n\r\n const result = await middleware.handler(currentRequest);\r\n\r\n if (result.type === 'response') {\r\n return result.response;\r\n }\r\n\r\n if (result.request) {\r\n currentRequest = result.request;\r\n }\r\n }\r\n\r\n return NextResponse.next();\r\n };\r\n}\r\n"],"mappings":"AAAA,SAAsB,oBAAoB;AAI1C,SAAS,kBAAkB,UAAkB,QAAmC;AAC9E,QAAM,EAAE,cAAc,aAAa,IAAI;AAEvC,MAAI,cAAc,QAAQ;AACxB,UAAM,aAAa,aAAa;AAAA,MAC9B,CAAC,SAAS,aAAa,QAAQ,SAAS,WAAW,GAAG,IAAI,GAAG;AAAA,IAC/D;AACA,QAAI,WAAY,QAAO;AAAA,EACzB;AAEA,MAAI,cAAc,QAAQ;AACxB,WAAO,aAAa,KAAK,CAAC,SAAS,aAAa,QAAQ,SAAS,WAAW,GAAG,IAAI,GAAG,CAAC;AAAA,EACzF;AAEA,SAAO;AACT;AAEO,SAAS,sBAAsB,aAAiC;AACrE,SAAO,eAAe,kBAAkB,SAA6C;AACnF,UAAM,EAAE,SAAS,IAAI,QAAQ;AAC7B,QAAI,iBAAiB;AAErB,eAAW,cAAc,aAAa;AACpC,UAAI,CAAC,kBAAkB,UAAU,UAAU,GAAG;AAC5C;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,WAAW,QAAQ,cAAc;AAEtD,UAAI,OAAO,SAAS,YAAY;AAC9B,eAAO,OAAO;AAAA,MAChB;AAEA,UAAI,OAAO,SAAS;AAClB,yBAAiB,OAAO;AAAA,MAC1B;AAAA,IACF;AAEA,WAAO,aAAa,KAAK;AAAA,EAC3B;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/middlewares/chain.ts"],"sourcesContent":["import { NextRequest, NextResponse } from 'next/server';\r\n\r\nimport { MiddlewareConfig } from './types';\r\nimport { GEO_COUNTRY_COOKIE, GEO_UNKNOWN_COUNTRIES } from '../utils/geo-country';\r\n\r\nfunction publishGeoCountry(request: NextRequest, response: NextResponse): void {\r\n const country = request.headers.get('cf-ipcountry')?.toUpperCase();\r\n\r\n if (!country || GEO_UNKNOWN_COUNTRIES.includes(country)) return;\r\n if (request.cookies.get(GEO_COUNTRY_COOKIE)?.value === country) return;\r\n\r\n response.cookies.set(GEO_COUNTRY_COOKIE, country, { path: '/', sameSite: 'lax' });\r\n}\r\n\r\nfunction shouldProcessPath(pathname: string, config: MiddlewareConfig): boolean {\r\n const { excludePaths, includePaths } = config;\r\n\r\n if (excludePaths?.length) {\r\n const isExcluded = excludePaths.some(\r\n (path) => pathname === path || pathname.startsWith(`${path}/`)\r\n );\r\n if (isExcluded) return false;\r\n }\r\n\r\n if (includePaths?.length) {\r\n return includePaths.some((path) => pathname === path || pathname.startsWith(`${path}/`));\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport function createMiddlewareChain(middlewares: MiddlewareConfig[]) {\r\n return async function chainedMiddleware(request: NextRequest): Promise<NextResponse> {\r\n const { pathname } = request.nextUrl;\r\n let currentRequest = request;\r\n\r\n for (const middleware of middlewares) {\r\n if (!shouldProcessPath(pathname, middleware)) {\r\n continue;\r\n }\r\n\r\n const result = await middleware.handler(currentRequest);\r\n\r\n if (result.type === 'response') {\r\n return result.response;\r\n }\r\n\r\n if (result.request) {\r\n currentRequest = result.request;\r\n }\r\n }\r\n\r\n const response = NextResponse.next();\r\n publishGeoCountry(request, response);\r\n\r\n return response;\r\n };\r\n}\r\n"],"mappings":"AAAA,SAAsB,oBAAoB;AAG1C,SAAS,oBAAoB,6BAA6B;AAE1D,SAAS,kBAAkB,SAAsB,UAA8B;AAC7E,QAAM,UAAU,QAAQ,QAAQ,IAAI,cAAc,GAAG,YAAY;AAEjE,MAAI,CAAC,WAAW,sBAAsB,SAAS,OAAO,EAAG;AACzD,MAAI,QAAQ,QAAQ,IAAI,kBAAkB,GAAG,UAAU,QAAS;AAEhE,WAAS,QAAQ,IAAI,oBAAoB,SAAS,EAAE,MAAM,KAAK,UAAU,MAAM,CAAC;AAClF;AAEA,SAAS,kBAAkB,UAAkB,QAAmC;AAC9E,QAAM,EAAE,cAAc,aAAa,IAAI;AAEvC,MAAI,cAAc,QAAQ;AACxB,UAAM,aAAa,aAAa;AAAA,MAC9B,CAAC,SAAS,aAAa,QAAQ,SAAS,WAAW,GAAG,IAAI,GAAG;AAAA,IAC/D;AACA,QAAI,WAAY,QAAO;AAAA,EACzB;AAEA,MAAI,cAAc,QAAQ;AACxB,WAAO,aAAa,KAAK,CAAC,SAAS,aAAa,QAAQ,SAAS,WAAW,GAAG,IAAI,GAAG,CAAC;AAAA,EACzF;AAEA,SAAO;AACT;AAEO,SAAS,sBAAsB,aAAiC;AACrE,SAAO,eAAe,kBAAkB,SAA6C;AACnF,UAAM,EAAE,SAAS,IAAI,QAAQ;AAC7B,QAAI,iBAAiB;AAErB,eAAW,cAAc,aAAa;AACpC,UAAI,CAAC,kBAAkB,UAAU,UAAU,GAAG;AAC5C;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,WAAW,QAAQ,cAAc;AAEtD,UAAI,OAAO,SAAS,YAAY;AAC9B,eAAO,OAAO;AAAA,MAChB;AAEA,UAAI,OAAO,SAAS;AAClB,yBAAiB,OAAO;AAAA,MAC1B;AAAA,IACF;AAEA,UAAM,WAAW,aAAa,KAAK;AACnC,sBAAkB,SAAS,QAAQ;AAEnC,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,16 @@
1
+ import { COUNTRIES } from "./countries";
2
+ const GEO_COUNTRY_COOKIE = "gs-geo-country";
3
+ const GEO_UNKNOWN_COUNTRIES = ["XX", "T1"];
4
+ function readGeoCountry() {
5
+ if (typeof document === "undefined") return null;
6
+ const match = new RegExp(`(?:^|;\\s*)${GEO_COUNTRY_COOKIE}=([^;]+)`).exec(document.cookie);
7
+ if (!match) return null;
8
+ const iso = match[1].toUpperCase();
9
+ return COUNTRIES.some((country) => country.iso === iso) ? iso : null;
10
+ }
11
+ export {
12
+ GEO_COUNTRY_COOKIE,
13
+ GEO_UNKNOWN_COUNTRIES,
14
+ readGeoCountry
15
+ };
16
+ //# sourceMappingURL=geo-country.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/geo-country.ts"],"sourcesContent":["import { COUNTRIES } from './countries';\n\nexport const GEO_COUNTRY_COOKIE = 'gs-geo-country';\n\nexport const GEO_UNKNOWN_COUNTRIES = ['XX', 'T1'];\n\nexport function readGeoCountry(): string | null {\n if (typeof document === 'undefined') return null;\n\n const match = new RegExp(`(?:^|;\\\\s*)${GEO_COUNTRY_COOKIE}=([^;]+)`).exec(document.cookie);\n if (!match) return null;\n\n const iso = match[1].toUpperCase();\n return COUNTRIES.some((country: { iso: string }) => country.iso === iso) ? iso : null;\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAEnB,MAAM,qBAAqB;AAE3B,MAAM,wBAAwB,CAAC,MAAM,IAAI;AAEzC,SAAS,iBAAgC;AAC9C,MAAI,OAAO,aAAa,YAAa,QAAO;AAE5C,QAAM,QAAQ,IAAI,OAAO,cAAc,kBAAkB,UAAU,EAAE,KAAK,SAAS,MAAM;AACzF,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,MAAM,MAAM,CAAC,EAAE,YAAY;AACjC,SAAO,UAAU,KAAK,CAAC,YAA6B,QAAQ,QAAQ,GAAG,IAAI,MAAM;AACnF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.763",
3
+ "version": "1.1.764",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -21,6 +21,7 @@ import { useViaCep } from '../../../modules/accounts/hooks/useViaCep';
21
21
  import { useUpdateBillingData } from '../../../modules/accounts/hooks/useAccountManagement';
22
22
  import { useRequiredBillingData } from '../../../modules/accounts/hooks/use-required-billing-data.hook';
23
23
  import { COUNTRIES } from '../../../utils/countries';
24
+ import { readGeoCountry } from '../../../utils/geo-country';
24
25
  import { BR_STATE_OPTIONS } from '../../../utils/constants/br-states';
25
26
  import { formatCNPJ, formatCPF, formatPostalCode } from '../../../utils/format/masks';
26
27
  import { isValidCNPJ, isValidCPF } from '../../../utils/validators/common';
@@ -48,46 +49,48 @@ const COUNTRY_OPTIONS = COUNTRIES.map((country: { iso: string; name: string }) =
48
49
  label: country.name,
49
50
  }));
50
51
 
51
- function buildBillingDataSchema(translate: TranslateFn) {
52
- const requiredText = z
53
- .string()
54
- .trim()
55
- .min(1, translate('common.billing.requiredData.requiredField'));
52
+ const BR_REQUIRED_ADDRESS_FIELDS: readonly (keyof BillingDataFormValues)[] = [
53
+ 'street',
54
+ 'streetNumber',
55
+ 'neighborhood',
56
+ 'city',
57
+ 'state',
58
+ ];
56
59
 
60
+ function buildBillingDataSchema(translate: TranslateFn) {
57
61
  const base = z.object({
58
62
  personType: z.enum(['pf', 'pj']),
59
63
  document: z.string().trim(),
60
- financialName: requiredText,
64
+ financialName: z
65
+ .string()
66
+ .trim()
67
+ .min(1, translate('common.billing.requiredData.requiredField')),
61
68
  financialEmail: z
62
69
  .string()
63
70
  .trim()
64
71
  .min(1, translate('common.billing.requiredData.requiredField'))
65
72
  .email(translate('common.billing.requiredData.invalidEmail')),
66
- cep: requiredText,
67
- street: requiredText,
73
+ cep: z.string().trim(),
74
+ street: z.string().trim(),
68
75
  streetNumber: z.string().trim(),
69
76
  complement: z.string().trim().optional(),
70
77
  neighborhood: z.string().trim(),
71
- city: requiredText,
72
- state: requiredText,
73
- country: requiredText,
78
+ city: z.string().trim(),
79
+ state: z.string().trim(),
80
+ country: z.string().trim().min(1, translate('common.billing.requiredData.requiredField')),
74
81
  });
75
82
 
76
83
  return base.superRefine((data, ctx) => {
77
- if (!data.streetNumber) {
78
- ctx.addIssue({
79
- code: 'custom',
80
- path: ['streetNumber'],
81
- message: translate('common.billing.requiredData.requiredField'),
82
- });
83
- }
84
-
85
- if (!data.neighborhood) {
86
- ctx.addIssue({
87
- code: 'custom',
88
- path: ['neighborhood'],
89
- message: translate('common.billing.requiredData.requiredField'),
90
- });
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
+ }
91
94
  }
92
95
 
93
96
  if (data.cep.replace(/\D/g, '').length !== 8) {
@@ -180,6 +183,7 @@ function RequiredBillingDataModalContent() {
180
183
 
181
184
  const personType = watch('personType');
182
185
  const isCompany = personType === 'pj';
186
+ const isBrazil = watch('country') === 'BR';
183
187
 
184
188
  useEffect(() => {
185
189
  if (!account || isPrefilled.current) return;
@@ -198,7 +202,7 @@ function RequiredBillingDataModalContent() {
198
202
  neighborhood: account.neighborhood ?? '',
199
203
  city: account.city ?? '',
200
204
  state: account.state ?? '',
201
- country: account.country || 'BR',
205
+ country: account.country || readGeoCountry() || 'BR',
202
206
  });
203
207
  }, [account, reset, user?.email]);
204
208
 
@@ -235,20 +239,27 @@ function RequiredBillingDataModalContent() {
235
239
  };
236
240
 
237
241
  async function onSubmit(data: BillingDataFormValues) {
238
- const payload: UpdateBillingDataRequest = {
239
- financial_document_type: data.personType === 'pf' ? 1 : 2,
240
- financial_document: data.document,
241
- financial_name: data.financialName,
242
- financial_email: data.financialEmail,
243
- zipcode: data.cep,
244
- address: data.street,
245
- address_number: data.streetNumber,
246
- address_complement: data.complement ?? '',
247
- neighborhood: data.neighborhood,
248
- city: data.city,
249
- state: data.state,
250
- country: data.country,
251
- };
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
+ };
252
263
 
253
264
  try {
254
265
  await updateBillingData(payload);
@@ -296,7 +307,9 @@ function RequiredBillingDataModalContent() {
296
307
  {translate('common.billing.requiredData.title')}
297
308
  </DialogTitle>
298
309
  <span className="paragraph-small-regular text-zinc-600">
299
- {translate('common.billing.requiredData.description')}
310
+ {isBrazil
311
+ ? translate('common.billing.requiredData.description')
312
+ : translate('common.billing.requiredData.descriptionInternational')}
300
313
  </span>
301
314
  </div>
302
315
  </DialogHeader>
@@ -308,7 +321,26 @@ function RequiredBillingDataModalContent() {
308
321
  >
309
322
  <div className="flex flex-col gap-6 p-4 lg:p-5 flex-1 overflow-y-auto">
310
323
  <div className="flex flex-col gap-5">
311
- <div className="flex gap-4">
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">
312
344
  <Controller
313
345
  name="personType"
314
346
  control={control}
@@ -347,11 +379,12 @@ function RequiredBillingDataModalContent() {
347
379
  })}
348
380
  />
349
381
  </div>
350
- </div>
382
+ </div>
383
+ )}
351
384
 
352
385
  <FormField
353
386
  label={
354
- isCompany
387
+ isBrazil && isCompany
355
388
  ? translate('common.billing.requiredData.companyName')
356
389
  : translate('common.billing.requiredData.fullName')
357
390
  }
@@ -371,113 +404,99 @@ function RequiredBillingDataModalContent() {
371
404
  />
372
405
  </div>
373
406
 
374
- <div className="h-px bg-zinc-200" />
407
+ {isBrazil && (
408
+ <>
409
+ <div className="h-px bg-zinc-200" />
375
410
 
376
- <div className="flex flex-col gap-5">
377
- <span className="paragraph-small-semibold text-zinc-950">
378
- {translate('common.billing.requiredData.addressHeading')}
379
- </span>
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>
380
415
 
381
- <FormField
382
- label={translate('common.billing.requiredData.cep')}
383
- placeholder="_____-___"
384
- error={!!errors.cep}
385
- errorMessage={errors.cep?.message}
386
- {...register('cep', {
387
- onChange: (event) => handleCepChange(event.target.value),
388
- })}
389
- />
390
-
391
- <div className="flex gap-4">
392
- <div className="flex-[3]">
393
416
  <FormField
394
- label={translate('common.billing.requiredData.street')}
395
- placeholder={translate('common.billing.requiredData.typeHere')}
396
- error={!!errors.street}
397
- errorMessage={errors.street?.message}
398
- {...register('street')}
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
+ })}
399
424
  />
400
- </div>
401
- <div className="flex-1">
402
- <FormField
403
- label={translate('common.billing.requiredData.streetNumber')}
404
- placeholder="000"
405
- error={!!errors.streetNumber}
406
- errorMessage={errors.streetNumber?.message}
407
- {...register('streetNumber')}
408
- />
409
- </div>
410
- </div>
411
425
 
412
- <div className="flex gap-4">
413
- <div className="flex-1">
414
- <FormField
415
- label={translate('common.billing.requiredData.neighborhood')}
416
- placeholder={translate('common.billing.requiredData.typeHere')}
417
- error={!!errors.neighborhood}
418
- errorMessage={errors.neighborhood?.message}
419
- {...register('neighborhood')}
420
- />
421
- </div>
422
- <div className="flex-1">
423
- <FormField
424
- label={translate('common.billing.requiredData.complement')}
425
- optional
426
- placeholder={translate('common.billing.requiredData.typeHere')}
427
- {...register('complement')}
428
- />
429
- </div>
430
- </div>
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>
431
446
 
432
- <div className="flex gap-4">
433
- <div className="flex-1">
434
- <FormField
435
- label={translate('common.billing.requiredData.city')}
436
- placeholder={translate('common.billing.requiredData.typeHere')}
437
- error={!!errors.city}
438
- errorMessage={errors.city?.message}
439
- {...register('city')}
440
- />
441
- </div>
442
- <div className="flex-1">
443
- <Controller
444
- name="state"
445
- control={control}
446
- render={({ field }) => (
447
- <SelectField
448
- label={translate('common.billing.requiredData.state')}
449
- placeholder={translate('common.billing.requiredData.select')}
450
- value={field.value || undefined}
451
- onChange={(value) =>
452
- setValue('state', value as string, { shouldValidate: true })
453
- }
454
- options={BR_STATE_OPTIONS}
455
- error={!!errors.state}
456
- errorMessage={errors.state?.message}
457
- />
458
- )}
459
- />
460
- </div>
461
- </div>
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>
462
466
 
463
- <Controller
464
- name="country"
465
- control={control}
466
- render={({ field }) => (
467
- <SelectField
468
- label={translate('common.billing.requiredData.country')}
469
- placeholder={translate('common.billing.requiredData.select')}
470
- value={field.value || undefined}
471
- onChange={(value) =>
472
- setValue('country', value as string, { shouldValidate: true })
473
- }
474
- options={COUNTRY_OPTIONS}
475
- error={!!errors.country}
476
- errorMessage={errors.country?.message}
477
- />
478
- )}
479
- />
480
- </div>
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
+ )}
481
500
  </div>
482
501
 
483
502
  <div className="flex items-center justify-end p-4 lg:p-5 border-t border-zinc-200 shrink-0">
@@ -354,6 +354,8 @@ const messages = {
354
354
  title: 'Complete your billing information',
355
355
  description:
356
356
  'We need this information to issue the invoice for your payments. Filling it in is required to keep using the platform.',
357
+ descriptionInternational:
358
+ 'Since your billing address is outside Brazil, we only need your name and email for your payment receipts.',
357
359
  addressHeading: 'Billing address',
358
360
  select: 'Select',
359
361
  typeHere: 'Type here',
@@ -355,6 +355,8 @@ const messages = {
355
355
  title: 'Completa tus datos de facturación',
356
356
  description:
357
357
  'Necesitamos esta información para emitir la factura de tus pagos. Completarla es obligatorio para seguir usando la plataforma.',
358
+ descriptionInternational:
359
+ 'Como tu facturación es fuera de Brasil, solo necesitamos tu nombre y correo para los recibos de tus pagos.',
358
360
  addressHeading: 'Dirección de facturación',
359
361
  select: 'Selecciona',
360
362
  typeHere: 'Escribe aquí',
@@ -367,6 +367,8 @@ const messages = {
367
367
  title: 'Complete seus dados de cobrança',
368
368
  description:
369
369
  'Precisamos dessas informações para emitir a nota fiscal dos seus pagamentos. O preenchimento é obrigatório para continuar usando a plataforma.',
370
+ descriptionInternational:
371
+ 'Como sua cobrança é fora do Brasil, precisamos apenas do nome e do e-mail para os recibos dos seus pagamentos.',
370
372
  addressHeading: 'Endereço de cobrança',
371
373
  select: 'Selecione',
372
374
  typeHere: 'Digite aqui',
@@ -1,6 +1,16 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
 
3
3
  import { MiddlewareConfig } from './types';
4
+ import { GEO_COUNTRY_COOKIE, GEO_UNKNOWN_COUNTRIES } from '../utils/geo-country';
5
+
6
+ function publishGeoCountry(request: NextRequest, response: NextResponse): void {
7
+ const country = request.headers.get('cf-ipcountry')?.toUpperCase();
8
+
9
+ if (!country || GEO_UNKNOWN_COUNTRIES.includes(country)) return;
10
+ if (request.cookies.get(GEO_COUNTRY_COOKIE)?.value === country) return;
11
+
12
+ response.cookies.set(GEO_COUNTRY_COOKIE, country, { path: '/', sameSite: 'lax' });
13
+ }
4
14
 
5
15
  function shouldProcessPath(pathname: string, config: MiddlewareConfig): boolean {
6
16
  const { excludePaths, includePaths } = config;
@@ -40,6 +50,9 @@ export function createMiddlewareChain(middlewares: MiddlewareConfig[]) {
40
50
  }
41
51
  }
42
52
 
43
- return NextResponse.next();
53
+ const response = NextResponse.next();
54
+ publishGeoCountry(request, response);
55
+
56
+ return response;
44
57
  };
45
58
  }
@@ -0,0 +1,15 @@
1
+ import { COUNTRIES } from './countries';
2
+
3
+ export const GEO_COUNTRY_COOKIE = 'gs-geo-country';
4
+
5
+ export const GEO_UNKNOWN_COUNTRIES = ['XX', 'T1'];
6
+
7
+ export function readGeoCountry(): string | null {
8
+ if (typeof document === 'undefined') return null;
9
+
10
+ const match = new RegExp(`(?:^|;\\s*)${GEO_COUNTRY_COOKIE}=([^;]+)`).exec(document.cookie);
11
+ if (!match) return null;
12
+
13
+ const iso = match[1].toUpperCase();
14
+ return COUNTRIES.some((country: { iso: string }) => country.iso === iso) ? iso : null;
15
+ }