@greatapps/common 1.1.806 → 1.1.808
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/account/sections/affiliates/ReceivingAccountModal.mjs +56 -18
- package/dist/components/account/sections/affiliates/ReceivingAccountModal.mjs.map +1 -1
- package/dist/components/account/sections/affiliates/RequestWithdrawalSheet.mjs +30 -3
- package/dist/components/account/sections/affiliates/RequestWithdrawalSheet.mjs.map +1 -1
- package/dist/i18n/messages/en-us.mjs +9 -1
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +9 -1
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +9 -1
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/index.mjs +7 -1
- package/dist/index.mjs.map +1 -1
- package/dist/testing/factories/affiliate.factory.mjs +24 -0
- package/dist/testing/factories/affiliate.factory.mjs.map +1 -0
- package/dist/testing/factories/index.mjs +4 -0
- package/dist/testing/factories/index.mjs.map +1 -1
- package/dist/testing/factories/withdrawal-limits.factory.mjs +18 -0
- package/dist/testing/factories/withdrawal-limits.factory.mjs.map +1 -0
- package/dist/utils/format/masks.mjs +6 -1
- package/dist/utils/format/masks.mjs.map +1 -1
- package/dist/utils/validators/pix-key.mjs +31 -0
- package/dist/utils/validators/pix-key.mjs.map +1 -0
- package/package.json +10 -9
- package/src/components/account/sections/affiliates/ReceivingAccountModal.tsx +75 -20
- package/src/components/account/sections/affiliates/RequestWithdrawalSheet.tsx +36 -3
- package/src/i18n/messages/en-us.ts +11 -0
- package/src/i18n/messages/es-es.ts +11 -0
- package/src/i18n/messages/pt-br.ts +11 -0
- package/src/index.ts +2 -0
- package/src/testing/factories/affiliate.factory.ts +22 -0
- package/src/testing/factories/index.ts +2 -0
- package/src/testing/factories/withdrawal-limits.factory.ts +16 -0
- package/src/utils/format/masks.ts +6 -2
- package/src/utils/validators/pix-key.ts +29 -0
|
@@ -23,6 +23,11 @@ import {
|
|
|
23
23
|
parseCurrencyToNumber,
|
|
24
24
|
} from '../../../../utils/format/currency';
|
|
25
25
|
import { formatDocument, formatPixKey } from '../../../../utils/format/masks';
|
|
26
|
+
import {
|
|
27
|
+
AMBIGUOUS_PIX_KEY,
|
|
28
|
+
isPixKeyFromAnotherDocument,
|
|
29
|
+
normalizePixKeyForStorage,
|
|
30
|
+
} from '../../../../utils/validators/pix-key';
|
|
26
31
|
import { formatFileSize, isPDFFile } from '../../../../utils/file/index';
|
|
27
32
|
import { useUploadProgress } from '../../../../hooks/useUploadProgress';
|
|
28
33
|
import useIsMobile from '../../../../hooks/useIsMobile';
|
|
@@ -144,6 +149,12 @@ export function RequestWithdrawalSheet() {
|
|
|
144
149
|
const requiresInvoice = limits?.requiresInvoice ?? false;
|
|
145
150
|
const hasReceivingAccount = !!(affiliate?.pix && affiliate?.holder_name && affiliate?.holder_document);
|
|
146
151
|
|
|
152
|
+
const holderDocument = affiliate?.holder_document ?? '';
|
|
153
|
+
const storedPixKey = affiliate?.pix ? normalizePixKeyForStorage(affiliate.pix, holderDocument) : undefined;
|
|
154
|
+
const isPixKeyInvalid = storedPixKey === null || storedPixKey === AMBIGUOUS_PIX_KEY;
|
|
155
|
+
const isPixKeyMismatch = typeof storedPixKey === 'string' && isPixKeyFromAnotherDocument(storedPixKey, holderDocument);
|
|
156
|
+
const hasPixKeyIssue = isPixKeyInvalid || isPixKeyMismatch;
|
|
157
|
+
|
|
147
158
|
const minValueLabel = minWithdrawal.toFixed(2).replace('.', ',');
|
|
148
159
|
|
|
149
160
|
const withdrawalSchema = useMemo(
|
|
@@ -309,8 +320,13 @@ export function RequestWithdrawalSheet() {
|
|
|
309
320
|
<span className="w-40 text-zinc-950 paragraph-small-medium">
|
|
310
321
|
{translateSheet('pixKeyLabel')}
|
|
311
322
|
</span>
|
|
312
|
-
<span
|
|
313
|
-
{
|
|
323
|
+
<span
|
|
324
|
+
className={cn(
|
|
325
|
+
'w-full paragraph-small-regular truncate',
|
|
326
|
+
hasPixKeyIssue ? 'text-red-600' : 'text-zinc-600'
|
|
327
|
+
)}
|
|
328
|
+
>
|
|
329
|
+
{affiliate?.pix ? (hasPixKeyIssue ? affiliate.pix : formatPixKey(affiliate.pix)) : '—'}
|
|
314
330
|
</span>
|
|
315
331
|
</div>
|
|
316
332
|
<div className="flex items-center gap-3">
|
|
@@ -322,9 +338,24 @@ export function RequestWithdrawalSheet() {
|
|
|
322
338
|
</span>
|
|
323
339
|
</div>
|
|
324
340
|
</div>
|
|
341
|
+
{hasPixKeyIssue && (
|
|
342
|
+
<div role="alert" className="flex flex-col gap-3 rounded-lg bg-red-50 p-4">
|
|
343
|
+
<div className="flex items-start gap-3">
|
|
344
|
+
<IconAlertTriangle size={20} className="text-red-600 shrink-0 mt-0.5" />
|
|
345
|
+
<p className="paragraph-small-medium text-zinc-950">
|
|
346
|
+
{isPixKeyInvalid
|
|
347
|
+
? translateSheet('pixKeyInvalidWarning')
|
|
348
|
+
: translateSheet('pixKeyMismatchWarning')}
|
|
349
|
+
</p>
|
|
350
|
+
</div>
|
|
351
|
+
<Button className="h-10! w-fit" type="button" onClick={openReceivingAccount}>
|
|
352
|
+
{translateSheet('updateReceivingAccountButton')}
|
|
353
|
+
</Button>
|
|
354
|
+
</div>
|
|
355
|
+
)}
|
|
325
356
|
</div>
|
|
326
357
|
|
|
327
|
-
{limits && !limits.canWithdraw ? (
|
|
358
|
+
{limits && !limits.canWithdraw && !hasPixKeyIssue ? (
|
|
328
359
|
<div className="flex flex-col gap-3 bg-zinc-50 rounded-lg p-4">
|
|
329
360
|
<div className="size-8 bg-white rounded-lg shadow-[0px_2px_10px_0px_rgba(26,26,26,0.05)] flex items-center justify-center">
|
|
330
361
|
<IconAlertTriangle size={18} className="text-amber-500" />
|
|
@@ -350,6 +381,7 @@ export function RequestWithdrawalSheet() {
|
|
|
350
381
|
render={({ field, fieldState }) => (
|
|
351
382
|
<FormField
|
|
352
383
|
required
|
|
384
|
+
disabled={hasPixKeyIssue}
|
|
353
385
|
label={translateSheet('withdrawalValueLabel')}
|
|
354
386
|
placeholder={translateSheet('withdrawalValuePlaceholder')}
|
|
355
387
|
inputMode="numeric"
|
|
@@ -481,6 +513,7 @@ export function RequestWithdrawalSheet() {
|
|
|
481
513
|
!affiliate ||
|
|
482
514
|
!limits ||
|
|
483
515
|
!hasReceivingAccount ||
|
|
516
|
+
hasPixKeyIssue ||
|
|
484
517
|
!withdrawalAmount ||
|
|
485
518
|
withdrawalAmount < minWithdrawal ||
|
|
486
519
|
toCents(withdrawalAmount) > toCents(availableToWithdraw) ||
|
|
@@ -537,6 +537,11 @@ const messages = {
|
|
|
537
537
|
blockedDescription:
|
|
538
538
|
'The registered Pix key is a tax ID that differs from the account holder document. Update the key or the document to request withdrawals again.',
|
|
539
539
|
blockedEditAccountButton: 'Edit receiving account',
|
|
540
|
+
pixKeyInvalidWarning:
|
|
541
|
+
'Your Pix key is invalid. Update your receiving details to request a withdrawal.',
|
|
542
|
+
pixKeyMismatchWarning:
|
|
543
|
+
'The Pix key does not match the account holder CPF/CNPJ. Update your receiving details to request a withdrawal.',
|
|
544
|
+
updateReceivingAccountButton: 'Update receiving details',
|
|
540
545
|
},
|
|
541
546
|
bankAccount: {
|
|
542
547
|
dialogTitle: 'Receiving account',
|
|
@@ -558,6 +563,12 @@ const messages = {
|
|
|
558
563
|
validationCnpjChecksum: 'Invalid CNPJ — check the verification digits',
|
|
559
564
|
validationPixDocumentMismatch:
|
|
560
565
|
'The Pix key is a tax ID that differs from the account holder document. Use a key for the same document or fix the holder tax ID.',
|
|
566
|
+
validationPixInvalid:
|
|
567
|
+
'Invalid Pix key. Use a CPF, CNPJ, mobile number with area code, email or random key.',
|
|
568
|
+
pixKeyPreview: 'Will be saved as: {value}',
|
|
569
|
+
pixAmbiguousQuestion: 'Is this number a mobile phone or a CPF?',
|
|
570
|
+
pixAmbiguousMobile: 'Mobile',
|
|
571
|
+
pixAmbiguousCpf: 'CPF',
|
|
561
572
|
pixCnpjInvoiceNotice:
|
|
562
573
|
'Company account: withdrawals require a PDF invoice and do not use the individual monthly limit.',
|
|
563
574
|
},
|
|
@@ -540,6 +540,11 @@ const messages = {
|
|
|
540
540
|
blockedDescription:
|
|
541
541
|
'La clave Pix registrada es un CPF/CNPJ distinto del documento del titular. Ajusta la clave o el documento para volver a solicitar retiros.',
|
|
542
542
|
blockedEditAccountButton: 'Editar cuenta de cobro',
|
|
543
|
+
pixKeyInvalidWarning:
|
|
544
|
+
'Tu clave Pix no es válida. Actualiza tus datos de cobro para solicitar el retiro.',
|
|
545
|
+
pixKeyMismatchWarning:
|
|
546
|
+
'La clave Pix no coincide con el CPF/CNPJ del titular. Actualiza tus datos de cobro para solicitar el retiro.',
|
|
547
|
+
updateReceivingAccountButton: 'Actualizar datos de cobro',
|
|
543
548
|
},
|
|
544
549
|
bankAccount: {
|
|
545
550
|
dialogTitle: 'Cuenta de recepción',
|
|
@@ -561,6 +566,12 @@ const messages = {
|
|
|
561
566
|
validationCnpjChecksum: 'CNPJ inválido — verifica los dígitos verificadores',
|
|
562
567
|
validationPixDocumentMismatch:
|
|
563
568
|
'La clave Pix es un CPF/CNPJ distinto del documento del titular. Usa una clave del mismo documento o corrige el CPF/CNPJ del titular.',
|
|
569
|
+
validationPixInvalid:
|
|
570
|
+
'Clave Pix inválida. Usa CPF, CNPJ, celular con código de área, correo electrónico o clave aleatoria.',
|
|
571
|
+
pixKeyPreview: 'Se guardará como: {value}',
|
|
572
|
+
pixAmbiguousQuestion: '¿Este número es un celular o un CPF?',
|
|
573
|
+
pixAmbiguousMobile: 'Celular',
|
|
574
|
+
pixAmbiguousCpf: 'CPF',
|
|
564
575
|
pixCnpjInvoiceNotice:
|
|
565
576
|
'Cuenta CNPJ: el retiro exige factura en PDF y no usa el límite mensual de CPF.',
|
|
566
577
|
},
|
|
@@ -548,6 +548,11 @@ const messages = {
|
|
|
548
548
|
blockedDescription:
|
|
549
549
|
'A chave Pix cadastrada é um CPF/CNPJ diferente do documento do titular. Ajuste a chave ou o documento para voltar a solicitar saques.',
|
|
550
550
|
blockedEditAccountButton: 'Editar conta de recebimento',
|
|
551
|
+
pixKeyInvalidWarning:
|
|
552
|
+
'Sua chave Pix está inválida. Atualize seus dados de recebimento para solicitar o saque.',
|
|
553
|
+
pixKeyMismatchWarning:
|
|
554
|
+
'A chave Pix não confere com o CPF/CNPJ do titular. Atualize seus dados de recebimento para solicitar o saque.',
|
|
555
|
+
updateReceivingAccountButton: 'Atualizar dados de recebimento',
|
|
551
556
|
},
|
|
552
557
|
bankAccount: {
|
|
553
558
|
dialogTitle: 'Conta de recebimento',
|
|
@@ -569,6 +574,12 @@ const messages = {
|
|
|
569
574
|
validationCnpjChecksum: 'CNPJ inválido — confira os dígitos verificadores',
|
|
570
575
|
validationPixDocumentMismatch:
|
|
571
576
|
'A chave Pix informada é um CPF/CNPJ diferente do documento do titular. Use uma chave do mesmo documento ou corrija o CPF/CNPJ do titular.',
|
|
577
|
+
validationPixInvalid:
|
|
578
|
+
'Chave Pix inválida. Use CPF, CNPJ, celular com DDD, e-mail ou chave aleatória.',
|
|
579
|
+
pixKeyPreview: 'Será salva como: {value}',
|
|
580
|
+
pixAmbiguousQuestion: 'Esse número é um celular ou um CPF?',
|
|
581
|
+
pixAmbiguousMobile: 'Celular',
|
|
582
|
+
pixAmbiguousCpf: 'CPF',
|
|
572
583
|
pixCnpjInvoiceNotice:
|
|
573
584
|
'Conta CNPJ: o saque passa a exigir nota fiscal em PDF e não usa o limite mensal de CPF.',
|
|
574
585
|
},
|
package/src/index.ts
CHANGED
|
@@ -559,7 +559,9 @@ export {
|
|
|
559
559
|
formatCPF,
|
|
560
560
|
formatCNPJ,
|
|
561
561
|
formatCardNumber,
|
|
562
|
+
formatPixKey,
|
|
562
563
|
} from "./utils/format/masks";
|
|
564
|
+
export { AMBIGUOUS_PIX_KEY, isPixKeyFromAnotherDocument, normalizePixKeyForStorage } from "./utils/validators/pix-key";
|
|
563
565
|
export {
|
|
564
566
|
formatCurrency,
|
|
565
567
|
formatCurrencyNumber,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import type { Affiliate } from '../../modules/affiliates/types/affiliate.type';
|
|
3
|
+
import { TEST_IDS } from '../constants';
|
|
4
|
+
import { createMock } from './create-mock';
|
|
5
|
+
|
|
6
|
+
export const createAffiliateMock = createMock<Affiliate, Partial<Affiliate>>((overrides) => ({
|
|
7
|
+
id: overrides?.id ?? 1,
|
|
8
|
+
id_wl: overrides?.id_wl ?? TEST_IDS.wl,
|
|
9
|
+
id_account: overrides?.id_account ?? TEST_IDS.account,
|
|
10
|
+
active: overrides?.active ?? true,
|
|
11
|
+
deleted: overrides?.deleted ?? false,
|
|
12
|
+
balance: overrides?.balance ?? 0,
|
|
13
|
+
datetime_add: overrides?.datetime_add ?? null,
|
|
14
|
+
datetime_alt: overrides?.datetime_alt ?? null,
|
|
15
|
+
datetime_del: overrides?.datetime_del ?? null,
|
|
16
|
+
holder_name: overrides?.holder_name || faker.person.fullName(),
|
|
17
|
+
pix: overrides?.pix ?? null,
|
|
18
|
+
holder_document: overrides?.holder_document ?? null,
|
|
19
|
+
bank: overrides?.bank ?? null,
|
|
20
|
+
agency: overrides?.agency ?? null,
|
|
21
|
+
account: overrides?.account ?? null,
|
|
22
|
+
}));
|
|
@@ -8,3 +8,5 @@ export { createPlanMock, createPlanItemMock } from './plan.factory';
|
|
|
8
8
|
export { createProjectMock } from './project.factory';
|
|
9
9
|
export { createProjectUserMock } from './project-user.factory';
|
|
10
10
|
export { createCouponMock } from './coupon.factory';
|
|
11
|
+
export { createAffiliateMock } from './affiliate.factory';
|
|
12
|
+
export { createWithdrawalLimitsMock } from './withdrawal-limits.factory';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WithdrawalLimits } from '../../modules/affiliates/types/affiliate.type';
|
|
2
|
+
import { createMock } from './create-mock';
|
|
3
|
+
|
|
4
|
+
export const createWithdrawalLimitsMock = createMock<WithdrawalLimits, Partial<WithdrawalLimits>>((overrides) => ({
|
|
5
|
+
personType: overrides?.personType ?? 'cpf',
|
|
6
|
+
financialDocumentType: overrides?.financialDocumentType ?? 1,
|
|
7
|
+
requiresInvoice: overrides?.requiresInvoice ?? false,
|
|
8
|
+
minValue: overrides?.minValue ?? 25,
|
|
9
|
+
maxValuePerMonth: overrides?.maxValuePerMonth ?? 350,
|
|
10
|
+
monthlyUsed: overrides?.monthlyUsed ?? 0,
|
|
11
|
+
monthlyRemaining: overrides?.monthlyRemaining ?? 350,
|
|
12
|
+
balance: overrides?.balance ?? 500,
|
|
13
|
+
availableToWithdraw: overrides?.availableToWithdraw ?? 350,
|
|
14
|
+
canWithdraw: overrides?.canWithdraw ?? true,
|
|
15
|
+
blockReason: overrides?.blockReason ?? null,
|
|
16
|
+
}));
|
|
@@ -92,8 +92,12 @@ function formatIfCpfCnpjShaped(value: string): string {
|
|
|
92
92
|
/** Documento do titular: CPF/CNPJ mascarado; qualquer outro conteúdo volta intacto. */
|
|
93
93
|
export const formatDocument = formatIfCpfCnpjShaped;
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
export function formatPixKey(value: string): string {
|
|
96
|
+
if (/^\+55\d{11}$/.test(value)) return `+55 ${formatPhone(value.slice(3))}`;
|
|
97
|
+
if (/^\d{11}$/.test(value)) return formatCPF(value);
|
|
98
|
+
if (/^\d{14}$/.test(value)) return formatCNPJ(value);
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
97
101
|
|
|
98
102
|
/** Tira a máscara antes de gravar; valor que não é CPF/CNPJ volta como veio. */
|
|
99
103
|
export function normalizeCpfCnpjForStorage(value: string): string {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isValidCNPJ, isValidCPF, isValidEmail } from './common';
|
|
2
|
+
|
|
3
|
+
const RANDOM_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
|
+
const PIX_KEY_CHARS = /^[\d ().\/+-]+$/;
|
|
5
|
+
const MOBILE_WITH_COUNTRY_CODE = /^55[1-9]{2}9\d{8}$/;
|
|
6
|
+
const MOBILE = /^[1-9]{2}9\d{8}$/;
|
|
7
|
+
|
|
8
|
+
export const AMBIGUOUS_PIX_KEY = Symbol('ambiguous-pix-key');
|
|
9
|
+
|
|
10
|
+
export function normalizePixKeyForStorage(value: string, holderDocument: string): string | typeof AMBIGUOUS_PIX_KEY | null {
|
|
11
|
+
const key = value.trim();
|
|
12
|
+
if (isValidEmail(key) || RANDOM_KEY.test(key)) return key;
|
|
13
|
+
if (!PIX_KEY_CHARS.test(key)) return null;
|
|
14
|
+
const digits = key.replace(/\D/g, '');
|
|
15
|
+
if (key.startsWith('+')) return MOBILE_WITH_COUNTRY_CODE.test(digits) ? `+${digits}` : null;
|
|
16
|
+
if (digits.length === 14) return isValidCNPJ(digits) ? digits : null;
|
|
17
|
+
if (digits.length === 13) return MOBILE_WITH_COUNTRY_CODE.test(digits) ? `+${digits}` : null;
|
|
18
|
+
if (digits.length !== 11) return null;
|
|
19
|
+
if (digits === holderDocument.replace(/\D/g, '')) return digits;
|
|
20
|
+
const isMobile = MOBILE.test(digits);
|
|
21
|
+
const isCpf = isValidCPF(digits);
|
|
22
|
+
if (isMobile && isCpf) return AMBIGUOUS_PIX_KEY;
|
|
23
|
+
if (isMobile) return `+55${digits}`;
|
|
24
|
+
return isCpf ? digits : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isPixKeyFromAnotherDocument(canonicalPixKey: string, holderDocument: string): boolean {
|
|
28
|
+
return /^\d+$/.test(canonicalPixKey) && canonicalPixKey !== holderDocument.replace(/\D/g, '');
|
|
29
|
+
}
|