@greatapps/common 1.1.805 → 1.1.807
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 +52 -18
- package/dist/components/account/sections/affiliates/ReceivingAccountModal.mjs.map +1 -1
- package/dist/components/account/sections/subscription/PixDetailsDialog.mjs +4 -6
- package/dist/components/account/sections/subscription/PixDetailsDialog.mjs.map +1 -1
- package/dist/components/account/sections/subscription/PixPaymentDialog.mjs +17 -24
- package/dist/components/account/sections/subscription/PixPaymentDialog.mjs.map +1 -1
- package/dist/hooks/useSecondsUntil.mjs +17 -0
- package/dist/hooks/useSecondsUntil.mjs.map +1 -0
- package/dist/i18n/messages/en-us.mjs +7 -0
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +7 -0
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +7 -0
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/index.mjs +6 -1
- package/dist/index.mjs.map +1 -1
- package/dist/modules/subscriptions/types/pending-pix-status.type.mjs +1 -0
- package/dist/modules/subscriptions/types/pending-pix-status.type.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 +2 -0
- package/dist/testing/factories/index.mjs.map +1 -1
- package/dist/utils/format/masks.mjs +6 -1
- package/dist/utils/format/masks.mjs.map +1 -1
- package/dist/utils/validators/pix-key.mjs +27 -0
- package/dist/utils/validators/pix-key.mjs.map +1 -0
- package/package.json +10 -9
- package/src/components/account/sections/affiliates/ReceivingAccountModal.tsx +71 -20
- package/src/components/account/sections/subscription/PixDetailsDialog.tsx +4 -9
- package/src/components/account/sections/subscription/PixPaymentDialog.tsx +18 -29
- package/src/hooks/useSecondsUntil.ts +17 -0
- package/src/i18n/messages/en-us.ts +8 -0
- package/src/i18n/messages/es-es.ts +8 -0
- package/src/i18n/messages/pt-br.ts +8 -0
- package/src/index.ts +2 -0
- package/src/modules/subscriptions/types/pending-pix-status.type.ts +1 -0
- package/src/testing/factories/affiliate.factory.ts +22 -0
- package/src/testing/factories/index.ts +1 -0
- package/src/utils/format/masks.ts +6 -2
- package/src/utils/validators/pix-key.ts +25 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useMemo } from "react";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
4
|
import { Controller, useForm } from "react-hook-form";
|
|
5
5
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
6
6
|
import { useTranslations } from "next-intl";
|
|
@@ -12,12 +12,15 @@ import { FormField } from "../../../ui/form/FormField";
|
|
|
12
12
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../../../ui/overlay/Dialog";
|
|
13
13
|
import { useCurrentAffiliate } from "../../../../modules/affiliates/hooks/use-current-affiliate.hook";
|
|
14
14
|
import { useUpdateAffiliate } from "../../../../modules/affiliates/hooks/use-update-affiliate.hook";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
formatCPFCNPJ,
|
|
17
|
+
formatDocument,
|
|
18
|
+
formatPixKey,
|
|
19
|
+
normalizeCpfCnpjForStorage
|
|
20
|
+
} from "../../../../utils/format/masks";
|
|
16
21
|
import { isValidCNPJ, isValidCPF } from "../../../../utils/validators/common";
|
|
22
|
+
import { AMBIGUOUS_PIX_KEY, normalizePixKeyForStorage } from "../../../../utils/validators/pix-key";
|
|
17
23
|
import { useAffiliateFlows } from "../../../../store/useAffiliateFlows";
|
|
18
|
-
const EMAIL_KEY = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
19
|
-
const RANDOM_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20
|
-
const PHONE_KEY = /^\+\d{11,14}$/;
|
|
21
24
|
function ReceivingAccountModal() {
|
|
22
25
|
const translate = useTranslations();
|
|
23
26
|
const translateModal = useTranslations("common.account.workspace.affiliates.bankAccount");
|
|
@@ -28,10 +31,25 @@ function ReceivingAccountModal() {
|
|
|
28
31
|
const updateAffiliate = useUpdateAffiliate();
|
|
29
32
|
const schema = useMemo(
|
|
30
33
|
() => z.object({
|
|
31
|
-
pix: z.string().min(1, translateModal("validationPix")),
|
|
34
|
+
pix: z.string().trim().min(1, translateModal("validationPix")),
|
|
32
35
|
holder_name: z.string().min(1, translateModal("validationHolder")),
|
|
33
36
|
holder_document: z.string().min(1, translateModal("validationDocument"))
|
|
34
37
|
}).superRefine((data, ctx) => {
|
|
38
|
+
const pix = normalizePixKeyForStorage(data.pix, data.holder_document);
|
|
39
|
+
if (data.pix && pix === null) {
|
|
40
|
+
ctx.addIssue({
|
|
41
|
+
code: z.ZodIssueCode.custom,
|
|
42
|
+
path: ["pix"],
|
|
43
|
+
message: translateModal("validationPixInvalid")
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (pix === AMBIGUOUS_PIX_KEY) {
|
|
47
|
+
ctx.addIssue({
|
|
48
|
+
code: z.ZodIssueCode.custom,
|
|
49
|
+
path: ["pix"],
|
|
50
|
+
message: translateModal("pixAmbiguousQuestion")
|
|
51
|
+
});
|
|
52
|
+
}
|
|
35
53
|
const digits = data.holder_document.replace(/\D/g, "");
|
|
36
54
|
if (!digits) return;
|
|
37
55
|
if (digits.length !== 11 && digits.length !== 14) {
|
|
@@ -51,14 +69,7 @@ function ReceivingAccountModal() {
|
|
|
51
69
|
});
|
|
52
70
|
return;
|
|
53
71
|
}
|
|
54
|
-
|
|
55
|
-
if (EMAIL_KEY.test(pix) || RANDOM_KEY.test(pix) || PHONE_KEY.test(pix.replace(/[\s().-]/g, ""))) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const pixDigits = pix.replace(/\D/g, "");
|
|
59
|
-
const pixIsCnpj = pixDigits.length === 14 && isValidCNPJ(pixDigits);
|
|
60
|
-
const pixIsCpf = pixDigits.length === 11 && isValidCPF(pixDigits);
|
|
61
|
-
if ((pixIsCnpj || pixIsCpf) && pixDigits !== digits) {
|
|
72
|
+
if (typeof pix === "string" && /^\d+$/.test(pix) && pix !== digits) {
|
|
62
73
|
ctx.addIssue({
|
|
63
74
|
code: z.ZodIssueCode.custom,
|
|
64
75
|
path: ["pix"],
|
|
@@ -73,6 +84,7 @@ function ReceivingAccountModal() {
|
|
|
73
84
|
mode: "onChange",
|
|
74
85
|
defaultValues: { pix: "", holder_name: "", holder_document: "" }
|
|
75
86
|
});
|
|
87
|
+
const [declaredCpfPix, setDeclaredCpfPix] = useState(null);
|
|
76
88
|
const resetForm = form.reset;
|
|
77
89
|
useEffect(() => {
|
|
78
90
|
if (!isOpen || !affiliate) return;
|
|
@@ -84,15 +96,17 @@ function ReceivingAccountModal() {
|
|
|
84
96
|
}, [isOpen, affiliate, resetForm]);
|
|
85
97
|
const handleClose = () => {
|
|
86
98
|
form.reset();
|
|
99
|
+
setDeclaredCpfPix(null);
|
|
87
100
|
close();
|
|
88
101
|
};
|
|
89
102
|
const onSubmit = async (data) => {
|
|
90
|
-
|
|
103
|
+
const pix = normalizePixKeyForStorage(data.pix, data.holder_document);
|
|
104
|
+
if (!affiliate || typeof pix !== "string") return;
|
|
91
105
|
try {
|
|
92
106
|
await updateAffiliate.mutateAsync({
|
|
93
107
|
...data,
|
|
94
108
|
holder_document: normalizeCpfCnpjForStorage(data.holder_document),
|
|
95
|
-
pix
|
|
109
|
+
pix,
|
|
96
110
|
bank: "",
|
|
97
111
|
agency: "",
|
|
98
112
|
account: ""
|
|
@@ -104,7 +118,21 @@ function ReceivingAccountModal() {
|
|
|
104
118
|
toast.custom((toastId) => /* @__PURE__ */ jsx(Toast, { variant: "error", message, toastId }));
|
|
105
119
|
}
|
|
106
120
|
};
|
|
107
|
-
const
|
|
121
|
+
const holderDocument = form.watch("holder_document");
|
|
122
|
+
const pixValue = form.watch("pix");
|
|
123
|
+
const isCnpjHolder = holderDocument.replace(/\D/g, "").length === 14;
|
|
124
|
+
const pixPreview = normalizePixKeyForStorage(pixValue, holderDocument);
|
|
125
|
+
const isPixAmbiguous = pixPreview === AMBIGUOUS_PIX_KEY;
|
|
126
|
+
const isPixDeclaredCpf = isPixAmbiguous && declaredCpfPix === pixValue;
|
|
127
|
+
const handleConfirmMobile = () => {
|
|
128
|
+
form.setValue("pix", formatPixKey(`+55${pixValue.replace(/\D/g, "")}`), {
|
|
129
|
+
shouldValidate: true,
|
|
130
|
+
shouldDirty: true
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
const handleConfirmCpf = () => {
|
|
134
|
+
setDeclaredCpfPix(pixValue);
|
|
135
|
+
};
|
|
108
136
|
return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: (nextOpen) => !nextOpen && handleClose(), children: /* @__PURE__ */ jsxs(
|
|
109
137
|
DialogContent,
|
|
110
138
|
{
|
|
@@ -121,10 +149,15 @@ function ReceivingAccountModal() {
|
|
|
121
149
|
placeholder: translateModal("pixKeyPlaceholder"),
|
|
122
150
|
required: true,
|
|
123
151
|
error: !!form.formState.errors.pix,
|
|
124
|
-
errorMessage: form.formState.errors.pix?.message,
|
|
152
|
+
errorMessage: isPixDeclaredCpf ? translateModal("validationPixDocumentMismatch") : form.formState.errors.pix?.message,
|
|
153
|
+
hintMessage: typeof pixPreview === "string" ? translateModal("pixKeyPreview", { value: formatPixKey(pixPreview) }) : void 0,
|
|
125
154
|
...form.register("pix")
|
|
126
155
|
}
|
|
127
156
|
),
|
|
157
|
+
isPixAmbiguous && !isPixDeclaredCpf && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 -mt-3", children: [
|
|
158
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: handleConfirmMobile, children: translateModal("pixAmbiguousMobile") }),
|
|
159
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: handleConfirmCpf, children: translateModal("pixAmbiguousCpf") })
|
|
160
|
+
] }),
|
|
128
161
|
/* @__PURE__ */ jsx("p", { className: "paragraph-xsmall-medium text-zinc-500 -mt-3", children: translateModal("pixKeyHint") }),
|
|
129
162
|
isCnpjHolder && /* @__PURE__ */ jsx("p", { className: "paragraph-xsmall-medium text-zinc-500 -mt-3", children: translateModal("pixCnpjInvoiceNotice") }),
|
|
130
163
|
/* @__PURE__ */ jsx(
|
|
@@ -143,6 +176,7 @@ function ReceivingAccountModal() {
|
|
|
143
176
|
{
|
|
144
177
|
name: "holder_document",
|
|
145
178
|
control: form.control,
|
|
179
|
+
rules: { deps: ["pix"] },
|
|
146
180
|
render: ({ field, fieldState }) => /* @__PURE__ */ jsx(
|
|
147
181
|
FormField,
|
|
148
182
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/account/sections/affiliates/ReceivingAccountModal.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo } from 'react';\nimport { Controller, useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport z from 'zod';\nimport { Button } from '../../../ui/buttons/Button';\nimport { Toast } from '../../../ui/feedback/Toast';\nimport { FormField } from '../../../ui/form/FormField';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../ui/overlay/Dialog';\nimport { useCurrentAffiliate } from '../../../../modules/affiliates/hooks/use-current-affiliate.hook';\nimport { useUpdateAffiliate } from '../../../../modules/affiliates/hooks/use-update-affiliate.hook';\nimport { formatCPFCNPJ, formatDocument, normalizeCpfCnpjForStorage } from '../../../../utils/format/masks';\nimport { isValidCNPJ, isValidCPF } from '../../../../utils/validators/common';\nimport { useAffiliateFlows } from '../../../../store/useAffiliateFlows';\n\ntype ReceivingAccountFormData = {\n pix: string;\n holder_name: string;\n holder_document: string;\n};\n\nconst EMAIL_KEY = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst RANDOM_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst PHONE_KEY = /^\\+\\d{11,14}$/;\n\n/** Cadastro da conta de recebimento do afiliado: chave PIX e titular. */\nexport function ReceivingAccountModal() {\n const translate = useTranslations();\n const translateModal = useTranslations('common.account.workspace.affiliates.bankAccount');\n const isOpen = useAffiliateFlows((state) => state.isReceivingAccountOpen);\n const close = useAffiliateFlows((state) => state.closeReceivingAccount);\n const { data: affiliateData } = useCurrentAffiliate();\n const affiliate = affiliateData?.data;\n const updateAffiliate = useUpdateAffiliate();\n\n const schema = useMemo(\n () =>\n z\n .object({\n pix: z.string().min(1, translateModal('validationPix')),\n holder_name: z.string().min(1, translateModal('validationHolder')),\n holder_document: z.string().min(1, translateModal('validationDocument')),\n })\n .superRefine((data, ctx) => {\n // O documento do titular decide PF x PJ pela contagem de dígitos, e é ele que vai para a\n // instituição no saque — tamanho certo com dígito verificador errado passa aqui e só\n // falha no pagamento.\n const digits = data.holder_document.replace(/\\D/g, '');\n if (!digits) return;\n\n if (digits.length !== 11 && digits.length !== 14) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['holder_document'],\n message: translateModal('validationDocumentLength'),\n });\n return;\n }\n\n const isValid = digits.length === 14 ? isValidCNPJ(digits) : isValidCPF(digits);\n if (!isValid) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['holder_document'],\n message:\n digits.length === 14\n ? translateModal('validationCnpjChecksum')\n : translateModal('validationCpfChecksum'),\n });\n return;\n }\n\n const pix = data.pix.trim();\n if (EMAIL_KEY.test(pix) || RANDOM_KEY.test(pix) || PHONE_KEY.test(pix.replace(/[\\s().-]/g, ''))) {\n return;\n }\n\n const pixDigits = pix.replace(/\\D/g, '');\n const pixIsCnpj = pixDigits.length === 14 && isValidCNPJ(pixDigits);\n const pixIsCpf = pixDigits.length === 11 && isValidCPF(pixDigits);\n if ((pixIsCnpj || pixIsCpf) && pixDigits !== digits) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['pix'],\n message: translateModal('validationPixDocumentMismatch'),\n });\n }\n }),\n [translateModal]\n );\n\n const form = useForm<ReceivingAccountFormData>({\n resolver: zodResolver(schema),\n mode: 'onChange',\n defaultValues: { pix: '', holder_name: '', holder_document: '' },\n });\n\n const resetForm = form.reset;\n\n useEffect(() => {\n if (!isOpen || !affiliate) return;\n resetForm({\n pix: affiliate.pix ?? '',\n holder_name: affiliate.holder_name ?? '',\n holder_document: affiliate.holder_document ? formatDocument(affiliate.holder_document) : '',\n });\n }, [isOpen, affiliate, resetForm]);\n\n const handleClose = () => {\n form.reset();\n close();\n };\n\n const onSubmit = async (data: ReceivingAccountFormData) => {\n if (!affiliate) return;\n\n try {\n await updateAffiliate.mutateAsync({\n ...data,\n holder_document: normalizeCpfCnpjForStorage(data.holder_document),\n pix: normalizeCpfCnpjForStorage(data.pix),\n bank: '',\n agency: '',\n account: '',\n });\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translateModal('saveSuccess')} toastId={toastId} />\n ));\n handleClose();\n } catch (error) {\n const message = error instanceof Error ? error.message : translateModal('saveError');\n toast.custom((toastId) => <Toast variant=\"error\" message={message} toastId={toastId} />);\n }\n };\n\n const isCnpjHolder = form.watch('holder_document').replace(/\\D/g, '').length === 14;\n\n return (\n <Dialog open={isOpen} onOpenChange={(nextOpen) => !nextOpen && handleClose()}>\n <DialogContent\n onOpenAutoFocus={(event) => event.preventDefault()}\n className=\"flex flex-col p-0 gap-0 max-w-full sm:max-w-full border-0 rounded-t-2xl rounded-b-none h-dvh top-0 bottom-0 left-0 right-0 translate-x-0 translate-y-0 lg:max-w-lg lg:h-auto lg:max-h-[90vh] lg:rounded-lg lg:border lg:top-[50%] lg:left-[50%] lg:right-auto lg:bottom-auto lg:translate-x-[-50%] lg:translate-y-[-50%] overflow-y-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300\"\n >\n <DialogHeader className=\"h-16 flex items-center justify-center px-5 border-b border-zinc-200 lg:border-b-0\">\n <DialogTitle className=\"text-center paragraph-medium-semibold\">\n {translateModal('dialogTitle')}\n </DialogTitle>\n </DialogHeader>\n\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col flex-1 lg:flex-none\">\n <div className=\"px-4 lg:px-5 pb-4 lg:pb-5 mt-4 lg:mt-5 flex-1 lg:flex-none flex flex-col gap-5\">\n <FormField\n label={translateModal('pixKeyLabel')}\n placeholder={translateModal('pixKeyPlaceholder')}\n required\n error={!!form.formState.errors.pix}\n errorMessage={form.formState.errors.pix?.message}\n {...form.register('pix')}\n />\n <p className=\"paragraph-xsmall-medium text-zinc-500 -mt-3\">{translateModal('pixKeyHint')}</p>\n {isCnpjHolder && (\n <p className=\"paragraph-xsmall-medium text-zinc-500 -mt-3\">\n {translateModal('pixCnpjInvoiceNotice')}\n </p>\n )}\n <FormField\n label={translateModal('holderNameLabel')}\n placeholder={translateModal('holderNamePlaceholder')}\n required\n error={!!form.formState.errors.holder_name}\n errorMessage={form.formState.errors.holder_name?.message}\n {...form.register('holder_name')}\n />\n <Controller\n name=\"holder_document\"\n control={form.control}\n render={({ field, fieldState }) => (\n <FormField\n label={translateModal('holderDocumentLabel')}\n placeholder={translateModal('holderDocumentPlaceholder')}\n required\n error={!!fieldState.error}\n errorMessage={fieldState.error?.message}\n value={field.value}\n onChange={(event) => field.onChange(formatCPFCNPJ(event.target.value))}\n />\n )}\n />\n </div>\n\n <div className=\"flex justify-between gap-2 p-4 lg:p-5 border-t border-zinc-200 mt-auto lg:mt-0\">\n <Button variant=\"secondary\" type=\"button\" onClick={handleClose} className=\"h-10!\">\n {translate('common.actions.cancel')}\n </Button>\n <Button\n type=\"submit\"\n className=\"h-10!\"\n disabled={!form.formState.isValid}\n loading={form.formState.isSubmitting}\n >\n {form.formState.isSubmitting\n ? translate('common.actions.saving')\n : translate('common.actions.save')}\n </Button>\n </div>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/account/sections/affiliates/ReceivingAccountModal.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { Controller, useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport z from 'zod';\nimport { Button } from '../../../ui/buttons/Button';\nimport { Toast } from '../../../ui/feedback/Toast';\nimport { FormField } from '../../../ui/form/FormField';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../ui/overlay/Dialog';\nimport { useCurrentAffiliate } from '../../../../modules/affiliates/hooks/use-current-affiliate.hook';\nimport { useUpdateAffiliate } from '../../../../modules/affiliates/hooks/use-update-affiliate.hook';\nimport {\n formatCPFCNPJ,\n formatDocument,\n formatPixKey,\n normalizeCpfCnpjForStorage,\n} from '../../../../utils/format/masks';\nimport { isValidCNPJ, isValidCPF } from '../../../../utils/validators/common';\nimport { AMBIGUOUS_PIX_KEY, normalizePixKeyForStorage } from '../../../../utils/validators/pix-key';\nimport { useAffiliateFlows } from '../../../../store/useAffiliateFlows';\n\ntype ReceivingAccountFormData = {\n pix: string;\n holder_name: string;\n holder_document: string;\n};\n\n/** Cadastro da conta de recebimento do afiliado: chave PIX e titular. */\nexport function ReceivingAccountModal() {\n const translate = useTranslations();\n const translateModal = useTranslations('common.account.workspace.affiliates.bankAccount');\n const isOpen = useAffiliateFlows((state) => state.isReceivingAccountOpen);\n const close = useAffiliateFlows((state) => state.closeReceivingAccount);\n const { data: affiliateData } = useCurrentAffiliate();\n const affiliate = affiliateData?.data;\n const updateAffiliate = useUpdateAffiliate();\n\n const schema = useMemo(\n () =>\n z\n .object({\n pix: z.string().trim().min(1, translateModal('validationPix')),\n holder_name: z.string().min(1, translateModal('validationHolder')),\n holder_document: z.string().min(1, translateModal('validationDocument')),\n })\n .superRefine((data, ctx) => {\n const pix = normalizePixKeyForStorage(data.pix, data.holder_document);\n if (data.pix && pix === null) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['pix'],\n message: translateModal('validationPixInvalid'),\n });\n }\n\n if (pix === AMBIGUOUS_PIX_KEY) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['pix'],\n message: translateModal('pixAmbiguousQuestion'),\n });\n }\n\n // O documento do titular decide PF x PJ pela contagem de dígitos, e é ele que vai para a\n // instituição no saque — tamanho certo com dígito verificador errado passa aqui e só\n // falha no pagamento.\n const digits = data.holder_document.replace(/\\D/g, '');\n if (!digits) return;\n\n if (digits.length !== 11 && digits.length !== 14) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['holder_document'],\n message: translateModal('validationDocumentLength'),\n });\n return;\n }\n\n const isValid = digits.length === 14 ? isValidCNPJ(digits) : isValidCPF(digits);\n if (!isValid) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['holder_document'],\n message:\n digits.length === 14\n ? translateModal('validationCnpjChecksum')\n : translateModal('validationCpfChecksum'),\n });\n return;\n }\n\n if (typeof pix === 'string' && /^\\d+$/.test(pix) && pix !== digits) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['pix'],\n message: translateModal('validationPixDocumentMismatch'),\n });\n }\n }),\n [translateModal]\n );\n\n const form = useForm<ReceivingAccountFormData>({\n resolver: zodResolver(schema),\n mode: 'onChange',\n defaultValues: { pix: '', holder_name: '', holder_document: '' },\n });\n\n const [declaredCpfPix, setDeclaredCpfPix] = useState<string | null>(null);\n\n const resetForm = form.reset;\n\n useEffect(() => {\n if (!isOpen || !affiliate) return;\n resetForm({\n pix: affiliate.pix ?? '',\n holder_name: affiliate.holder_name ?? '',\n holder_document: affiliate.holder_document ? formatDocument(affiliate.holder_document) : '',\n });\n }, [isOpen, affiliate, resetForm]);\n\n const handleClose = () => {\n form.reset();\n setDeclaredCpfPix(null);\n close();\n };\n\n const onSubmit = async (data: ReceivingAccountFormData) => {\n const pix = normalizePixKeyForStorage(data.pix, data.holder_document);\n if (!affiliate || typeof pix !== 'string') return;\n\n try {\n await updateAffiliate.mutateAsync({\n ...data,\n holder_document: normalizeCpfCnpjForStorage(data.holder_document),\n pix,\n bank: '',\n agency: '',\n account: '',\n });\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translateModal('saveSuccess')} toastId={toastId} />\n ));\n handleClose();\n } catch (error) {\n const message = error instanceof Error ? error.message : translateModal('saveError');\n toast.custom((toastId) => <Toast variant=\"error\" message={message} toastId={toastId} />);\n }\n };\n\n const holderDocument = form.watch('holder_document');\n const pixValue = form.watch('pix');\n const isCnpjHolder = holderDocument.replace(/\\D/g, '').length === 14;\n const pixPreview = normalizePixKeyForStorage(pixValue, holderDocument);\n const isPixAmbiguous = pixPreview === AMBIGUOUS_PIX_KEY;\n const isPixDeclaredCpf = isPixAmbiguous && declaredCpfPix === pixValue;\n\n const handleConfirmMobile = () => {\n form.setValue('pix', formatPixKey(`+55${pixValue.replace(/\\D/g, '')}`), {\n shouldValidate: true,\n shouldDirty: true,\n });\n };\n\n const handleConfirmCpf = () => {\n setDeclaredCpfPix(pixValue);\n };\n\n return (\n <Dialog open={isOpen} onOpenChange={(nextOpen) => !nextOpen && handleClose()}>\n <DialogContent\n onOpenAutoFocus={(event) => event.preventDefault()}\n className=\"flex flex-col p-0 gap-0 max-w-full sm:max-w-full border-0 rounded-t-2xl rounded-b-none h-dvh top-0 bottom-0 left-0 right-0 translate-x-0 translate-y-0 lg:max-w-lg lg:h-auto lg:max-h-[90vh] lg:rounded-lg lg:border lg:top-[50%] lg:left-[50%] lg:right-auto lg:bottom-auto lg:translate-x-[-50%] lg:translate-y-[-50%] overflow-y-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300\"\n >\n <DialogHeader className=\"h-16 flex items-center justify-center px-5 border-b border-zinc-200 lg:border-b-0\">\n <DialogTitle className=\"text-center paragraph-medium-semibold\">\n {translateModal('dialogTitle')}\n </DialogTitle>\n </DialogHeader>\n\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex flex-col flex-1 lg:flex-none\">\n <div className=\"px-4 lg:px-5 pb-4 lg:pb-5 mt-4 lg:mt-5 flex-1 lg:flex-none flex flex-col gap-5\">\n <FormField\n label={translateModal('pixKeyLabel')}\n placeholder={translateModal('pixKeyPlaceholder')}\n required\n error={!!form.formState.errors.pix}\n errorMessage={\n isPixDeclaredCpf\n ? translateModal('validationPixDocumentMismatch')\n : form.formState.errors.pix?.message\n }\n hintMessage={\n typeof pixPreview === 'string'\n ? translateModal('pixKeyPreview', { value: formatPixKey(pixPreview) })\n : undefined\n }\n {...form.register('pix')}\n />\n {isPixAmbiguous && !isPixDeclaredCpf && (\n <div className=\"flex gap-2 -mt-3\">\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={handleConfirmMobile}>\n {translateModal('pixAmbiguousMobile')}\n </Button>\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={handleConfirmCpf}>\n {translateModal('pixAmbiguousCpf')}\n </Button>\n </div>\n )}\n <p className=\"paragraph-xsmall-medium text-zinc-500 -mt-3\">{translateModal('pixKeyHint')}</p>\n {isCnpjHolder && (\n <p className=\"paragraph-xsmall-medium text-zinc-500 -mt-3\">\n {translateModal('pixCnpjInvoiceNotice')}\n </p>\n )}\n <FormField\n label={translateModal('holderNameLabel')}\n placeholder={translateModal('holderNamePlaceholder')}\n required\n error={!!form.formState.errors.holder_name}\n errorMessage={form.formState.errors.holder_name?.message}\n {...form.register('holder_name')}\n />\n <Controller\n name=\"holder_document\"\n control={form.control}\n rules={{ deps: ['pix'] }}\n render={({ field, fieldState }) => (\n <FormField\n label={translateModal('holderDocumentLabel')}\n placeholder={translateModal('holderDocumentPlaceholder')}\n required\n error={!!fieldState.error}\n errorMessage={fieldState.error?.message}\n value={field.value}\n onChange={(event) => field.onChange(formatCPFCNPJ(event.target.value))}\n />\n )}\n />\n </div>\n\n <div className=\"flex justify-between gap-2 p-4 lg:p-5 border-t border-zinc-200 mt-auto lg:mt-0\">\n <Button variant=\"secondary\" type=\"button\" onClick={handleClose} className=\"h-10!\">\n {translate('common.actions.cancel')}\n </Button>\n <Button\n type=\"submit\"\n className=\"h-10!\"\n disabled={!form.formState.isValid}\n loading={form.formState.isSubmitting}\n >\n {form.formState.isSubmitting\n ? translate('common.actions.saving')\n : translate('common.actions.save')}\n </Button>\n </div>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -10,7 +10,7 @@ import { Toast } from "../../../ui/feedback/Toast";
|
|
|
10
10
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../../../ui/overlay/Dialog";
|
|
11
11
|
import { useCurrencyFormatter } from "../../../../modules/accounts/hooks/use-currency-formatter.hook";
|
|
12
12
|
import { useRefreshChargePix } from "../../../../modules/charges/hooks/refresh-pix.hook";
|
|
13
|
-
import
|
|
13
|
+
import useSecondsUntil from "../../../../hooks/useSecondsUntil";
|
|
14
14
|
import { copyToClipboard } from "../../../../utils/browser/clipboard";
|
|
15
15
|
import { qrCodeToDataUrl } from "../../../../utils/qrcode/to-data-url";
|
|
16
16
|
function formatCountdown(seconds) {
|
|
@@ -56,9 +56,7 @@ function PixDetailsDialog({ open, charge, onOpenChange }) {
|
|
|
56
56
|
const expiresAtRaw = refreshedPix?.expires_at ?? (needsRefresh ? null : staleExpiresAt);
|
|
57
57
|
const isRefreshing = needsRefresh && refreshPix.isPending;
|
|
58
58
|
const refreshNotice = refreshPix.data && !refreshPix.data.pix ? refreshPix.data.message : null;
|
|
59
|
-
const
|
|
60
|
-
const shouldShowCountdown = typeof expiresInSeconds === "number" && expiresInSeconds > 0;
|
|
61
|
-
const { seconds } = useCountdownTimer({ initialSeconds: shouldShowCountdown ? expiresInSeconds : 1 });
|
|
59
|
+
const secondsUntilExpiry = useSecondsUntil(expiresAtRaw);
|
|
62
60
|
useEffect(() => {
|
|
63
61
|
if (!qrCode) {
|
|
64
62
|
setGeneratedQrImage(null);
|
|
@@ -125,10 +123,10 @@ function PixDetailsDialog({ open, charge, onOpenChange }) {
|
|
|
125
123
|
] })
|
|
126
124
|
] }),
|
|
127
125
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 p-5 border border-zinc-200 rounded-lg bg-white items-center", children: [
|
|
128
|
-
|
|
126
|
+
!!secondsUntilExpiry && /* @__PURE__ */ jsxs("p", { className: "paragraph-small-semibold text-zinc-950 text-center", children: [
|
|
129
127
|
translate("payIn"),
|
|
130
128
|
" ",
|
|
131
|
-
/* @__PURE__ */ jsx("span", { className: "text-red-600", children: formatCountdown(
|
|
129
|
+
/* @__PURE__ */ jsx("span", { className: "text-red-600", children: formatCountdown(secondsUntilExpiry) })
|
|
132
130
|
] }),
|
|
133
131
|
isRefreshing ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
|
|
134
132
|
/* @__PURE__ */ jsx(Skeleton, { className: "size-[198px] rounded-lg shrink-0" }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/account/sections/subscription/PixDetailsDialog.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\nimport { IconCalendar, IconCopy, IconCoin, IconQrcode } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport { Button } from '../../../ui/buttons/Button';\nimport { Skeleton } from '../../../ui/feedback/Skeleton';\nimport { Toast } from '../../../ui/feedback/Toast';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../ui/overlay/Dialog';\nimport { useCurrencyFormatter } from '../../../../modules/accounts/hooks/use-currency-formatter.hook';\nimport { useRefreshChargePix } from '../../../../modules/charges/hooks/refresh-pix.hook';\nimport type { Charge } from '../../../../modules/charges/types/charge.type';\nimport type { PixRefreshData } from '../../../../modules/charges/types/refresh-pix.type';\nimport useCountdownTimer from '../../../../hooks/useCountdownTimer';\nimport { copyToClipboard } from '../../../../utils/browser/clipboard';\nimport { qrCodeToDataUrl } from '../../../../utils/qrcode/to-data-url';\n\ntype PixDetailsDialogProps = {\n open: boolean;\n charge: Charge | null | undefined;\n onOpenChange: (open: boolean) => void;\n};\n\nfunction formatCountdown(seconds: number): string {\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const remaining = seconds % 60;\n const mm = String(minutes).padStart(2, '0');\n const ss = String(remaining).padStart(2, '0');\n\n return hours > 0 ? `${String(hours).padStart(2, '0')}:${mm}:${ss}` : `${mm}:${ss}`;\n}\n\nfunction isSameDay(a: Date, b: Date) {\n return (\n a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n );\n}\n\n/** QR code e copia-e-cola do PIX de uma cobrança em aberto, regenerando quando o código expirou. */\nexport function PixDetailsDialog({ open, charge, onOpenChange }: PixDetailsDialogProps) {\n const translate = useTranslations('common.account.workspace.subscription.pixDetails');\n const { formatCurrencyNumber } = useCurrencyFormatter();\n\n const paymentDetails = charge?.payment_details as Record<string, unknown> | null | undefined;\n const staleQrCode = paymentDetails?.qr_code as string | null | undefined;\n const staleExpiresAt = paymentDetails?.expires_at as string | null | undefined;\n // Flags do backend: `expired` = QR vencido; `refreshable` = pode regenerar. Regenera quando é\n // refreshable e não há QR utilizável (vencido, ou mandato pendente que nunca teve QR próprio).\n const isExpired = Boolean(paymentDetails?.expired);\n const isRefreshable = Boolean(paymentDetails?.refreshable);\n const needsRefresh = isRefreshable && (isExpired || !staleQrCode);\n\n const [refreshedPix, setRefreshedPix] = useState<PixRefreshData | null>(null);\n const [mountedAt, setMountedAt] = useState<number | null>(null);\n const [generatedQrImage, setGeneratedQrImage] = useState<string | null>(null);\n const refreshPix = useRefreshChargePix();\n\n useEffect(() => {\n if (!open) {\n setRefreshedPix(null);\n refreshPix.reset();\n return;\n }\n\n setMountedAt(Date.now());\n if (needsRefresh && charge?.id) {\n refreshPix.mutate(String(charge.id), {\n onSuccess: (result) => {\n if (result.pix) setRefreshedPix(result.pix);\n },\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n // Se regenerou, usa o novo; se está expirado e regenerável, esconde o vencido (shimmer até vir o\n // novo); senão usa o da listagem.\n const qrCode = refreshedPix?.qr_code ?? (needsRefresh ? null : staleQrCode);\n const expiresAtRaw = refreshedPix?.expires_at ?? (needsRefresh ? null : staleExpiresAt);\n const isRefreshing = needsRefresh && refreshPix.isPending;\n const refreshNotice = refreshPix.data && !refreshPix.data.pix ? refreshPix.data.message : null;\n\n const expiresInSeconds =\n expiresAtRaw && mountedAt\n ? Math.max(0, Math.floor((new Date(expiresAtRaw).getTime() - mountedAt) / 1000))\n : null;\n const shouldShowCountdown = typeof expiresInSeconds === 'number' && expiresInSeconds > 0;\n const { seconds } = useCountdownTimer({ initialSeconds: shouldShowCountdown ? expiresInSeconds : 1 });\n\n useEffect(() => {\n if (!qrCode) {\n setGeneratedQrImage(null);\n return;\n }\n\n let cancelled = false;\n qrCodeToDataUrl(qrCode, {\n width: 396,\n margin: 1,\n errorCorrectionLevel: 'M',\n color: { dark: '#171412', light: '#FFFFFF' },\n })\n .then((dataUrl) => {\n if (!cancelled) setGeneratedQrImage(dataUrl);\n })\n .catch(() => {\n if (!cancelled) setGeneratedQrImage(null);\n });\n\n return () => {\n cancelled = true;\n };\n }, [qrCode]);\n\n const handleCopyCode = async () => {\n if (!qrCode) return;\n await copyToClipboard(qrCode);\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translate('copiedToast')} toastId={toastId} />\n ));\n };\n\n if (!charge) return null;\n\n const dueDateLabel = (() => {\n if (!expiresAtRaw || !mountedAt) {\n if (!charge.date_due) return '—';\n return new Date(charge.date_due).toLocaleDateString('pt-BR', { timeZone: 'UTC' });\n }\n\n const expiresAt = new Date(expiresAtRaw);\n const today = new Date(mountedAt);\n const tomorrow = new Date(mountedAt);\n tomorrow.setDate(tomorrow.getDate() + 1);\n\n if (isSameDay(expiresAt, today)) return translate('dueDateToday');\n if (isSameDay(expiresAt, tomorrow)) return translate('dueDateTomorrow');\n return expiresAt.toLocaleDateString('pt-BR');\n })();\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"flex flex-col p-0 gap-0 w-full sm:max-w-130 lg:max-w-130 max-h-[90vh] overflow-y-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300\">\n <DialogHeader className=\"gap-3 text-left p-5 border-b border-zinc-200\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center size-10 bg-zinc-50 rounded-lg shrink-0\">\n <IconQrcode size={20} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <DialogTitle className=\"paragraph-medium-semibold text-zinc-950\">\n {translate('title')}\n </DialogTitle>\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('subtitle')}</span>\n </div>\n </div>\n </DialogHeader>\n\n <div className=\"flex flex-col gap-5 p-5\">\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 bg-zinc-50 rounded-lg flex items-center justify-center shrink-0\">\n <IconCalendar size={20} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-1\">\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('dueDate')}</span>\n <span className=\"paragraph-small-semibold text-zinc-950\">{dueDateLabel}</span>\n </div>\n </div>\n\n <div className=\"h-10 w-px bg-zinc-200\" />\n\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 bg-zinc-50 rounded-lg flex items-center justify-center shrink-0\">\n <IconCoin size={20} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-1\">\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('value')}</span>\n <span className=\"paragraph-small-semibold text-zinc-950\">\n {formatCurrencyNumber(charge.value)}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"flex flex-col gap-6 p-5 border border-zinc-200 rounded-lg bg-white items-center\">\n {shouldShowCountdown && (\n <p className=\"paragraph-small-semibold text-zinc-950 text-center\">\n {translate('payIn')} <span className=\"text-red-600\">{formatCountdown(seconds)}</span>\n </p>\n )}\n\n {isRefreshing ? (\n <div className=\"flex flex-col items-center gap-3\">\n <Skeleton className=\"size-[198px] rounded-lg shrink-0\" />\n <p className=\"paragraph-small-regular text-zinc-500 text-center\">\n {translate('generating')}\n </p>\n </div>\n ) : generatedQrImage ? (\n <div className=\"size-[198px] border border-zinc-200 rounded-lg overflow-hidden bg-white shrink-0 p-3\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={generatedQrImage}\n alt={translate('qrCodeAlt')}\n className=\"size-full object-contain\"\n />\n </div>\n ) : (\n <div className=\"size-[198px] border border-dashed border-zinc-300 rounded-lg bg-zinc-50 shrink-0 flex items-center justify-center p-6 text-center\">\n <p className=\"paragraph-small-regular text-zinc-500\">\n {refreshPix.isError\n ? refreshPix.error?.message || translate('refreshError')\n : (refreshNotice ?? translate('qrCodeUnavailable'))}\n </p>\n </div>\n )}\n\n {qrCode && (\n <>\n <div className=\"flex items-center w-full\">\n <div className=\"flex-1 h-px bg-zinc-200\" />\n <span className=\"px-2 paragraph-xsmall-medium text-zinc-500 whitespace-nowrap\">\n {translate('orCopyCode')}\n </span>\n <div className=\"flex-1 h-px bg-zinc-200\" />\n </div>\n\n <div className=\"flex flex-col items-center gap-4 w-full\">\n <p className=\"paragraph-small-medium text-zinc-950 text-center break-all w-full\">\n {qrCode}\n </p>\n <Button variant=\"secondary\" onClick={handleCopyCode} className=\"h-10 gap-1.5\">\n <IconCopy size={20} />\n {translate('copyCode')}\n </Button>\n </div>\n </>\n )}\n </div>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":";AAwHM,SAoGQ,UApGR,KA8BM,YA9BN;AAtHN,SAAS,WAAW,gBAAgB;AACpC,SAAS,cAAc,UAAU,UAAU,kBAAkB;AAC7D,SAAS,uBAAuB;AAChC,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,aAAa;AACtB,SAAS,QAAQ,eAAe,cAAc,mBAAmB;AACjE,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AAGpC,OAAO,uBAAuB;AAC9B,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAQhC,SAAS,gBAAgB,SAAyB;AAChD,QAAM,QAAQ,KAAK,MAAM,UAAU,IAAI;AACvC,QAAM,UAAU,KAAK,MAAO,UAAU,OAAQ,EAAE;AAChD,QAAM,YAAY,UAAU;AAC5B,QAAM,KAAK,OAAO,OAAO,EAAE,SAAS,GAAG,GAAG;AAC1C,QAAM,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG;AAE5C,SAAO,QAAQ,IAAI,GAAG,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,EAAE,IAAI,EAAE;AAClF;AAEA,SAAS,UAAU,GAAS,GAAS;AACnC,SACE,EAAE,YAAY,MAAM,EAAE,YAAY,KAAK,EAAE,SAAS,MAAM,EAAE,SAAS,KAAK,EAAE,QAAQ,MAAM,EAAE,QAAQ;AAEtG;AAGO,SAAS,iBAAiB,EAAE,MAAM,QAAQ,aAAa,GAA0B;AACtF,QAAM,YAAY,gBAAgB,kDAAkD;AACpF,QAAM,EAAE,qBAAqB,IAAI,qBAAqB;AAEtD,QAAM,iBAAiB,QAAQ;AAC/B,QAAM,cAAc,gBAAgB;AACpC,QAAM,iBAAiB,gBAAgB;AAGvC,QAAM,YAAY,QAAQ,gBAAgB,OAAO;AACjD,QAAM,gBAAgB,QAAQ,gBAAgB,WAAW;AACzD,QAAM,eAAe,kBAAkB,aAAa,CAAC;AAErD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAgC,IAAI;AAC5E,QAAM,CAAC,WAAW,YAAY,IAAI,SAAwB,IAAI;AAC9D,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,SAAwB,IAAI;AAC5E,QAAM,aAAa,oBAAoB;AAEvC,YAAU,MAAM;AACd,QAAI,CAAC,MAAM;AACT,sBAAgB,IAAI;AACpB,iBAAW,MAAM;AACjB;AAAA,IACF;AAEA,iBAAa,KAAK,IAAI,CAAC;AACvB,QAAI,gBAAgB,QAAQ,IAAI;AAC9B,iBAAW,OAAO,OAAO,OAAO,EAAE,GAAG;AAAA,QACnC,WAAW,CAAC,WAAW;AACrB,cAAI,OAAO,IAAK,iBAAgB,OAAO,GAAG;AAAA,QAC5C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EAEF,GAAG,CAAC,IAAI,CAAC;AAIT,QAAM,SAAS,cAAc,YAAY,eAAe,OAAO;AAC/D,QAAM,eAAe,cAAc,eAAe,eAAe,OAAO;AACxE,QAAM,eAAe,gBAAgB,WAAW;AAChD,QAAM,gBAAgB,WAAW,QAAQ,CAAC,WAAW,KAAK,MAAM,WAAW,KAAK,UAAU;AAE1F,QAAM,mBACJ,gBAAgB,YACZ,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,YAAY,EAAE,QAAQ,IAAI,aAAa,GAAI,CAAC,IAC7E;AACN,QAAM,sBAAsB,OAAO,qBAAqB,YAAY,mBAAmB;AACvF,QAAM,EAAE,QAAQ,IAAI,kBAAkB,EAAE,gBAAgB,sBAAsB,mBAAmB,EAAE,CAAC;AAEpG,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,0BAAoB,IAAI;AACxB;AAAA,IACF;AAEA,QAAI,YAAY;AAChB,oBAAgB,QAAQ;AAAA,MACtB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,sBAAsB;AAAA,MACtB,OAAO,EAAE,MAAM,WAAW,OAAO,UAAU;AAAA,IAC7C,CAAC,EACE,KAAK,CAAC,YAAY;AACjB,UAAI,CAAC,UAAW,qBAAoB,OAAO;AAAA,IAC7C,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,UAAW,qBAAoB,IAAI;AAAA,IAC1C,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,iBAAiB,YAAY;AACjC,QAAI,CAAC,OAAQ;AACb,UAAM,gBAAgB,MAAM;AAC5B,UAAM,OAAO,CAAC,YACZ,oBAAC,SAAM,SAAQ,WAAU,SAAS,UAAU,aAAa,GAAG,SAAkB,CAC/E;AAAA,EACH;AAEA,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,gBAAgB,MAAM;AAC1B,QAAI,CAAC,gBAAgB,CAAC,WAAW;AAC/B,UAAI,CAAC,OAAO,SAAU,QAAO;AAC7B,aAAO,IAAI,KAAK,OAAO,QAAQ,EAAE,mBAAmB,SAAS,EAAE,UAAU,MAAM,CAAC;AAAA,IAClF;AAEA,UAAM,YAAY,IAAI,KAAK,YAAY;AACvC,UAAM,QAAQ,IAAI,KAAK,SAAS;AAChC,UAAM,WAAW,IAAI,KAAK,SAAS;AACnC,aAAS,QAAQ,SAAS,QAAQ,IAAI,CAAC;AAEvC,QAAI,UAAU,WAAW,KAAK,EAAG,QAAO,UAAU,cAAc;AAChE,QAAI,UAAU,WAAW,QAAQ,EAAG,QAAO,UAAU,iBAAiB;AACtE,WAAO,UAAU,mBAAmB,OAAO;AAAA,EAC7C,GAAG;AAEH,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAU,4QACvB;AAAA,wBAAC,gBAAa,WAAU,gDACtB,+BAAC,SAAI,WAAU,2BACb;AAAA,0BAAC,SAAI,WAAU,2EACb,8BAAC,cAAW,MAAM,IAAI,WAAU,iBAAgB,GAClD;AAAA,MACA,qBAAC,SAAI,WAAU,yBACb;AAAA,4BAAC,eAAY,WAAU,2CACpB,oBAAU,OAAO,GACpB;AAAA,QACA,oBAAC,UAAK,WAAU,yCAAyC,oBAAU,UAAU,GAAE;AAAA,SACjF;AAAA,OACF,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,2BACb;AAAA,2BAAC,SAAI,WAAU,2BACb;AAAA,6BAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,SAAI,WAAU,2EACb,8BAAC,gBAAa,MAAM,IAAI,WAAU,iBAAgB,GACpD;AAAA,UACA,qBAAC,SAAI,WAAU,uBACb;AAAA,gCAAC,UAAK,WAAU,yCAAyC,oBAAU,SAAS,GAAE;AAAA,YAC9E,oBAAC,UAAK,WAAU,0CAA0C,wBAAa;AAAA,aACzE;AAAA,WACF;AAAA,QAEA,oBAAC,SAAI,WAAU,yBAAwB;AAAA,QAEvC,qBAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,SAAI,WAAU,2EACb,8BAAC,YAAS,MAAM,IAAI,WAAU,iBAAgB,GAChD;AAAA,UACA,qBAAC,SAAI,WAAU,uBACb;AAAA,gCAAC,UAAK,WAAU,yCAAyC,oBAAU,OAAO,GAAE;AAAA,YAC5E,oBAAC,UAAK,WAAU,0CACb,+BAAqB,OAAO,KAAK,GACpC;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAU,mFACZ;AAAA,+BACC,qBAAC,OAAE,WAAU,sDACV;AAAA,oBAAU,OAAO;AAAA,UAAE;AAAA,UAAC,oBAAC,UAAK,WAAU,gBAAgB,0BAAgB,OAAO,GAAE;AAAA,WAChF;AAAA,QAGD,eACC,qBAAC,SAAI,WAAU,oCACb;AAAA,8BAAC,YAAS,WAAU,oCAAmC;AAAA,UACvD,oBAAC,OAAE,WAAU,qDACV,oBAAU,YAAY,GACzB;AAAA,WACF,IACE,mBACF,oBAAC,SAAI,WAAU,wFAEb;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,KAAK,UAAU,WAAW;AAAA,YAC1B,WAAU;AAAA;AAAA,QACZ,GACF,IAEA,oBAAC,SAAI,WAAU,qIACb,8BAAC,OAAE,WAAU,yCACV,qBAAW,UACR,WAAW,OAAO,WAAW,UAAU,cAAc,IACpD,iBAAiB,UAAU,mBAAmB,GACrD,GACF;AAAA,QAGD,UACC,iCACE;AAAA,+BAAC,SAAI,WAAU,4BACb;AAAA,gCAAC,SAAI,WAAU,2BAA0B;AAAA,YACzC,oBAAC,UAAK,WAAU,gEACb,oBAAU,YAAY,GACzB;AAAA,YACA,oBAAC,SAAI,WAAU,2BAA0B;AAAA,aAC3C;AAAA,UAEA,qBAAC,SAAI,WAAU,2CACb;AAAA,gCAAC,OAAE,WAAU,qEACV,kBACH;AAAA,YACA,qBAAC,UAAO,SAAQ,aAAY,SAAS,gBAAgB,WAAU,gBAC7D;AAAA,kCAAC,YAAS,MAAM,IAAI;AAAA,cACnB,UAAU,UAAU;AAAA,eACvB;AAAA,aACF;AAAA,WACF;AAAA,SAEJ;AAAA,OACF;AAAA,KACF,GACF;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/account/sections/subscription/PixDetailsDialog.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\nimport { IconCalendar, IconCopy, IconCoin, IconQrcode } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport { Button } from '../../../ui/buttons/Button';\nimport { Skeleton } from '../../../ui/feedback/Skeleton';\nimport { Toast } from '../../../ui/feedback/Toast';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../ui/overlay/Dialog';\nimport { useCurrencyFormatter } from '../../../../modules/accounts/hooks/use-currency-formatter.hook';\nimport { useRefreshChargePix } from '../../../../modules/charges/hooks/refresh-pix.hook';\nimport type { Charge } from '../../../../modules/charges/types/charge.type';\nimport type { PixRefreshData } from '../../../../modules/charges/types/refresh-pix.type';\nimport useSecondsUntil from '../../../../hooks/useSecondsUntil';\nimport { copyToClipboard } from '../../../../utils/browser/clipboard';\nimport { qrCodeToDataUrl } from '../../../../utils/qrcode/to-data-url';\n\ntype PixDetailsDialogProps = {\n open: boolean;\n charge: Charge | null | undefined;\n onOpenChange: (open: boolean) => void;\n};\n\nfunction formatCountdown(seconds: number): string {\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const remaining = seconds % 60;\n const mm = String(minutes).padStart(2, '0');\n const ss = String(remaining).padStart(2, '0');\n\n return hours > 0 ? `${String(hours).padStart(2, '0')}:${mm}:${ss}` : `${mm}:${ss}`;\n}\n\nfunction isSameDay(a: Date, b: Date) {\n return (\n a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n );\n}\n\n/** QR code e copia-e-cola do PIX de uma cobrança em aberto, regenerando quando o código expirou. */\nexport function PixDetailsDialog({ open, charge, onOpenChange }: PixDetailsDialogProps) {\n const translate = useTranslations('common.account.workspace.subscription.pixDetails');\n const { formatCurrencyNumber } = useCurrencyFormatter();\n\n const paymentDetails = charge?.payment_details as Record<string, unknown> | null | undefined;\n const staleQrCode = paymentDetails?.qr_code as string | null | undefined;\n const staleExpiresAt = paymentDetails?.expires_at as string | null | undefined;\n // Flags do backend: `expired` = QR vencido; `refreshable` = pode regenerar. Regenera quando é\n // refreshable e não há QR utilizável (vencido, ou mandato pendente que nunca teve QR próprio).\n const isExpired = Boolean(paymentDetails?.expired);\n const isRefreshable = Boolean(paymentDetails?.refreshable);\n const needsRefresh = isRefreshable && (isExpired || !staleQrCode);\n\n const [refreshedPix, setRefreshedPix] = useState<PixRefreshData | null>(null);\n const [mountedAt, setMountedAt] = useState<number | null>(null);\n const [generatedQrImage, setGeneratedQrImage] = useState<string | null>(null);\n const refreshPix = useRefreshChargePix();\n\n useEffect(() => {\n if (!open) {\n setRefreshedPix(null);\n refreshPix.reset();\n return;\n }\n\n setMountedAt(Date.now());\n if (needsRefresh && charge?.id) {\n refreshPix.mutate(String(charge.id), {\n onSuccess: (result) => {\n if (result.pix) setRefreshedPix(result.pix);\n },\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n // Se regenerou, usa o novo; se está expirado e regenerável, esconde o vencido (shimmer até vir o\n // novo); senão usa o da listagem.\n const qrCode = refreshedPix?.qr_code ?? (needsRefresh ? null : staleQrCode);\n const expiresAtRaw = refreshedPix?.expires_at ?? (needsRefresh ? null : staleExpiresAt);\n const isRefreshing = needsRefresh && refreshPix.isPending;\n const refreshNotice = refreshPix.data && !refreshPix.data.pix ? refreshPix.data.message : null;\n\n const secondsUntilExpiry = useSecondsUntil(expiresAtRaw);\n\n useEffect(() => {\n if (!qrCode) {\n setGeneratedQrImage(null);\n return;\n }\n\n let cancelled = false;\n qrCodeToDataUrl(qrCode, {\n width: 396,\n margin: 1,\n errorCorrectionLevel: 'M',\n color: { dark: '#171412', light: '#FFFFFF' },\n })\n .then((dataUrl) => {\n if (!cancelled) setGeneratedQrImage(dataUrl);\n })\n .catch(() => {\n if (!cancelled) setGeneratedQrImage(null);\n });\n\n return () => {\n cancelled = true;\n };\n }, [qrCode]);\n\n const handleCopyCode = async () => {\n if (!qrCode) return;\n await copyToClipboard(qrCode);\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translate('copiedToast')} toastId={toastId} />\n ));\n };\n\n if (!charge) return null;\n\n const dueDateLabel = (() => {\n if (!expiresAtRaw || !mountedAt) {\n if (!charge.date_due) return '—';\n return new Date(charge.date_due).toLocaleDateString('pt-BR', { timeZone: 'UTC' });\n }\n\n const expiresAt = new Date(expiresAtRaw);\n const today = new Date(mountedAt);\n const tomorrow = new Date(mountedAt);\n tomorrow.setDate(tomorrow.getDate() + 1);\n\n if (isSameDay(expiresAt, today)) return translate('dueDateToday');\n if (isSameDay(expiresAt, tomorrow)) return translate('dueDateTomorrow');\n return expiresAt.toLocaleDateString('pt-BR');\n })();\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"flex flex-col p-0 gap-0 w-full sm:max-w-130 lg:max-w-130 max-h-[90vh] overflow-y-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300\">\n <DialogHeader className=\"gap-3 text-left p-5 border-b border-zinc-200\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center size-10 bg-zinc-50 rounded-lg shrink-0\">\n <IconQrcode size={20} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <DialogTitle className=\"paragraph-medium-semibold text-zinc-950\">\n {translate('title')}\n </DialogTitle>\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('subtitle')}</span>\n </div>\n </div>\n </DialogHeader>\n\n <div className=\"flex flex-col gap-5 p-5\">\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 bg-zinc-50 rounded-lg flex items-center justify-center shrink-0\">\n <IconCalendar size={20} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-1\">\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('dueDate')}</span>\n <span className=\"paragraph-small-semibold text-zinc-950\">{dueDateLabel}</span>\n </div>\n </div>\n\n <div className=\"h-10 w-px bg-zinc-200\" />\n\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 bg-zinc-50 rounded-lg flex items-center justify-center shrink-0\">\n <IconCoin size={20} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-1\">\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('value')}</span>\n <span className=\"paragraph-small-semibold text-zinc-950\">\n {formatCurrencyNumber(charge.value)}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"flex flex-col gap-6 p-5 border border-zinc-200 rounded-lg bg-white items-center\">\n {!!secondsUntilExpiry && (\n <p className=\"paragraph-small-semibold text-zinc-950 text-center\">\n {translate('payIn')} <span className=\"text-red-600\">{formatCountdown(secondsUntilExpiry)}</span>\n </p>\n )}\n\n {isRefreshing ? (\n <div className=\"flex flex-col items-center gap-3\">\n <Skeleton className=\"size-[198px] rounded-lg shrink-0\" />\n <p className=\"paragraph-small-regular text-zinc-500 text-center\">\n {translate('generating')}\n </p>\n </div>\n ) : generatedQrImage ? (\n <div className=\"size-[198px] border border-zinc-200 rounded-lg overflow-hidden bg-white shrink-0 p-3\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={generatedQrImage}\n alt={translate('qrCodeAlt')}\n className=\"size-full object-contain\"\n />\n </div>\n ) : (\n <div className=\"size-[198px] border border-dashed border-zinc-300 rounded-lg bg-zinc-50 shrink-0 flex items-center justify-center p-6 text-center\">\n <p className=\"paragraph-small-regular text-zinc-500\">\n {refreshPix.isError\n ? refreshPix.error?.message || translate('refreshError')\n : (refreshNotice ?? translate('qrCodeUnavailable'))}\n </p>\n </div>\n )}\n\n {qrCode && (\n <>\n <div className=\"flex items-center w-full\">\n <div className=\"flex-1 h-px bg-zinc-200\" />\n <span className=\"px-2 paragraph-xsmall-medium text-zinc-500 whitespace-nowrap\">\n {translate('orCopyCode')}\n </span>\n <div className=\"flex-1 h-px bg-zinc-200\" />\n </div>\n\n <div className=\"flex flex-col items-center gap-4 w-full\">\n <p className=\"paragraph-small-medium text-zinc-950 text-center break-all w-full\">\n {qrCode}\n </p>\n <Button variant=\"secondary\" onClick={handleCopyCode} className=\"h-10 gap-1.5\">\n <IconCopy size={20} />\n {translate('copyCode')}\n </Button>\n </div>\n </>\n )}\n </div>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -10,7 +10,7 @@ import { Toast } from "../../../ui/feedback/Toast";
|
|
|
10
10
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../../../ui/overlay/Dialog";
|
|
11
11
|
import { useCurrencyFormatter } from "../../../../modules/accounts/hooks/use-currency-formatter.hook";
|
|
12
12
|
import { usePendingPixStatus } from "../../../../modules/subscriptions/hooks/pending-pix-status.hook";
|
|
13
|
-
import
|
|
13
|
+
import useSecondsUntil from "../../../../hooks/useSecondsUntil";
|
|
14
14
|
import { copyToClipboard } from "../../../../utils/browser/clipboard";
|
|
15
15
|
import { qrCodeToDataUrl } from "../../../../utils/qrcode/to-data-url";
|
|
16
16
|
import { useSubscriptionFlows } from "../../../../store/useSubscriptionFlows";
|
|
@@ -30,17 +30,9 @@ function PixPaymentDialog() {
|
|
|
30
30
|
const pixPayment = useSubscriptionFlows((state) => state.pixPayment);
|
|
31
31
|
const close = useSubscriptionFlows((state) => state.closePixPayment);
|
|
32
32
|
const openPlansCatalog = useSubscriptionFlows((state) => state.openPlansCatalog);
|
|
33
|
-
const [mountedAt, setMountedAt] = useState(null);
|
|
34
33
|
const [generatedQrImage, setGeneratedQrImage] = useState(null);
|
|
35
34
|
const { data: statusData } = usePendingPixStatus(pixPayment?.correlationId);
|
|
36
35
|
const phase = statusData?.data?.phase;
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
if (!pixPayment) {
|
|
39
|
-
setMountedAt(null);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
setMountedAt(Date.now());
|
|
43
|
-
}, [pixPayment]);
|
|
44
36
|
useEffect(() => {
|
|
45
37
|
const qrCode = pixPayment?.qrCode;
|
|
46
38
|
if (!qrCode) {
|
|
@@ -62,15 +54,18 @@ function PixPaymentDialog() {
|
|
|
62
54
|
cancelled = true;
|
|
63
55
|
};
|
|
64
56
|
}, [pixPayment?.qrCode]);
|
|
65
|
-
const
|
|
57
|
+
const isCompleted = phase === "completed";
|
|
58
|
+
const isAuthorizedOnly = statusData?.data?.payment_collected === false;
|
|
59
|
+
const completedTitle = isAuthorizedOnly ? translate("authorizedTitle") : translate("paidTitle");
|
|
60
|
+
const completedMessage = isAuthorizedOnly ? translate("authorizedMessage") : translate("paidToast");
|
|
66
61
|
const isRejected = phase === "rejected";
|
|
67
62
|
const isExpired = phase === "expired";
|
|
68
63
|
const paidNotifiedRef = useRef(false);
|
|
69
64
|
useEffect(() => {
|
|
70
|
-
if (!
|
|
65
|
+
if (!isCompleted || paidNotifiedRef.current) return;
|
|
71
66
|
paidNotifiedRef.current = true;
|
|
72
|
-
toast.custom((toastId) => /* @__PURE__ */ jsx(Toast, { variant: "success", message:
|
|
73
|
-
}, [
|
|
67
|
+
toast.custom((toastId) => /* @__PURE__ */ jsx(Toast, { variant: "success", message: completedMessage, toastId }));
|
|
68
|
+
}, [isCompleted, completedMessage]);
|
|
74
69
|
useEffect(() => {
|
|
75
70
|
if (!pixPayment) paidNotifiedRef.current = false;
|
|
76
71
|
}, [pixPayment]);
|
|
@@ -78,9 +73,7 @@ function PixPaymentDialog() {
|
|
|
78
73
|
close();
|
|
79
74
|
openPlansCatalog();
|
|
80
75
|
};
|
|
81
|
-
const
|
|
82
|
-
const shouldShowCountdown = typeof expiresInSeconds === "number" && expiresInSeconds > 0;
|
|
83
|
-
const { seconds } = useCountdownTimer({ initialSeconds: shouldShowCountdown ? expiresInSeconds : 1 });
|
|
76
|
+
const secondsUntilExpiry = useSecondsUntil(pixPayment?.expiresAt);
|
|
84
77
|
const handleCopyCode = async () => {
|
|
85
78
|
if (!pixPayment?.qrCode) return;
|
|
86
79
|
await copyToClipboard(pixPayment.qrCode);
|
|
@@ -95,32 +88,32 @@ function PixPaymentDialog() {
|
|
|
95
88
|
/* @__PURE__ */ jsx("span", { className: "paragraph-xsmall-medium text-zinc-500", children: translate("subtitle") })
|
|
96
89
|
] })
|
|
97
90
|
] }) }),
|
|
98
|
-
|
|
91
|
+
isCompleted || isRejected || isExpired ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-4 p-8 text-center", children: [
|
|
99
92
|
/* @__PURE__ */ jsx(
|
|
100
93
|
"div",
|
|
101
94
|
{
|
|
102
95
|
className: cn(
|
|
103
96
|
"size-12 rounded-full flex items-center justify-center",
|
|
104
|
-
|
|
97
|
+
isCompleted ? "bg-green-50" : "bg-red-50"
|
|
105
98
|
),
|
|
106
|
-
children:
|
|
99
|
+
children: isCompleted ? /* @__PURE__ */ jsx(IconCircleCheck, { size: 24, className: "text-green-500" }) : /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, className: "text-red-500" })
|
|
107
100
|
}
|
|
108
101
|
),
|
|
109
102
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
110
|
-
/* @__PURE__ */ jsx("span", { className: "paragraph-xlarge-semibold text-zinc-950", children:
|
|
111
|
-
/* @__PURE__ */ jsx("span", { className: "paragraph-small-regular text-zinc-500", children:
|
|
103
|
+
/* @__PURE__ */ jsx("span", { className: "paragraph-xlarge-semibold text-zinc-950", children: isCompleted ? completedTitle : isRejected ? translate("rejectedTitle") : translate("expiredTitle") }),
|
|
104
|
+
/* @__PURE__ */ jsx("span", { className: "paragraph-small-regular text-zinc-500", children: isCompleted ? completedMessage : translate("rejectedOrExpiredDescription") })
|
|
112
105
|
] }),
|
|
113
|
-
|
|
106
|
+
isCompleted ? /* @__PURE__ */ jsx(Button, { variant: "secondary", className: "h-10", onClick: close, children: translateActions("common.actions.close") }) : /* @__PURE__ */ jsx(Button, { variant: "secondary", className: "h-10", onClick: handleGenerateAnother, children: translate("generateAnother") })
|
|
114
107
|
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5 p-5", children: [
|
|
115
108
|
payNowLabel && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
116
109
|
/* @__PURE__ */ jsx("span", { className: "paragraph-small-medium text-zinc-500", children: translate("payNowLabel") }),
|
|
117
110
|
/* @__PURE__ */ jsx("span", { className: "paragraph-medium-semibold text-zinc-950", children: payNowLabel })
|
|
118
111
|
] }),
|
|
119
112
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 p-5 border border-zinc-200 rounded-lg bg-white items-center", children: [
|
|
120
|
-
|
|
113
|
+
!!secondsUntilExpiry && /* @__PURE__ */ jsxs("p", { className: "paragraph-small-semibold text-zinc-950 text-center", children: [
|
|
121
114
|
translate("payIn"),
|
|
122
115
|
" ",
|
|
123
|
-
/* @__PURE__ */ jsx("span", { className: "text-red-600", children: formatCountdown(
|
|
116
|
+
/* @__PURE__ */ jsx("span", { className: "text-red-600", children: formatCountdown(secondsUntilExpiry) })
|
|
124
117
|
] }),
|
|
125
118
|
generatedQrImage ? /* @__PURE__ */ jsx("div", { className: "size-[198px] border border-zinc-200 rounded-lg overflow-hidden bg-white shrink-0 p-3", children: /* @__PURE__ */ jsx(
|
|
126
119
|
"img",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/account/sections/subscription/PixPaymentDialog.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\nimport { IconAlertTriangle, IconCircleCheck, IconCopy, IconQrcode } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport { Button } from '../../../ui/buttons/Button';\nimport { Skeleton } from '../../../ui/feedback/Skeleton';\nimport { Toast } from '../../../ui/feedback/Toast';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../ui/overlay/Dialog';\nimport { useCurrencyFormatter } from '../../../../modules/accounts/hooks/use-currency-formatter.hook';\nimport { usePendingPixStatus } from '../../../../modules/subscriptions/hooks/pending-pix-status.hook';\nimport useCountdownTimer from '../../../../hooks/useCountdownTimer';\nimport { copyToClipboard } from '../../../../utils/browser/clipboard';\nimport { qrCodeToDataUrl } from '../../../../utils/qrcode/to-data-url';\nimport { useSubscriptionFlows } from '../../../../store/useSubscriptionFlows';\nimport { cn } from '../../../../infra/utils/clsx';\n\nfunction formatCountdown(seconds: number): string {\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const remaining = seconds % 60;\n const mm = String(minutes).padStart(2, '0');\n const ss = String(remaining).padStart(2, '0');\n\n return hours > 0 ? `${String(hours).padStart(2, '0')}:${mm}:${ss}` : `${mm}:${ss}`;\n}\n\n/**\n * QR code do pix gerado na troca de plano ou reativação. Fica em polling até o backend confirmar\n * o pagamento — no gapps esse passo era uma página de checkout à parte.\n */\nexport function PixPaymentDialog() {\n const translate = useTranslations('common.account.workspace.subscription.pixPayment');\n const translateActions = useTranslations();\n const { formatCurrencyNumber } = useCurrencyFormatter();\n\n const pixPayment = useSubscriptionFlows((state) => state.pixPayment);\n const close = useSubscriptionFlows((state) => state.closePixPayment);\n const openPlansCatalog = useSubscriptionFlows((state) => state.openPlansCatalog);\n\n const [mountedAt, setMountedAt] = useState<number | null>(null);\n const [generatedQrImage, setGeneratedQrImage] = useState<string | null>(null);\n\n const { data: statusData } = usePendingPixStatus(pixPayment?.correlationId);\n const phase = statusData?.data?.phase;\n\n useEffect(() => {\n if (!pixPayment) {\n setMountedAt(null);\n return;\n }\n setMountedAt(Date.now());\n }, [pixPayment]);\n\n useEffect(() => {\n const qrCode = pixPayment?.qrCode;\n if (!qrCode) {\n setGeneratedQrImage(null);\n return;\n }\n\n let cancelled = false;\n qrCodeToDataUrl(qrCode, {\n width: 396,\n margin: 1,\n errorCorrectionLevel: 'M',\n color: { dark: '#171412', light: '#FFFFFF' },\n })\n .then((dataUrl) => {\n if (!cancelled) setGeneratedQrImage(dataUrl);\n })\n .catch(() => {\n if (!cancelled) setGeneratedQrImage(null);\n });\n\n return () => {\n cancelled = true;\n };\n }, [pixPayment?.qrCode]);\n\n const isPaid = phase === 'completed';\n const isRejected = phase === 'rejected';\n const isExpired = phase === 'expired';\n\n // Pago avisa uma vez; a janela fica com a confirmação na tela, como a página do gapps fazia.\n const paidNotifiedRef = useRef(false);\n useEffect(() => {\n if (!isPaid || paidNotifiedRef.current) return;\n paidNotifiedRef.current = true;\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translate('paidToast')} toastId={toastId} />\n ));\n }, [isPaid, translate]);\n\n useEffect(() => {\n if (!pixPayment) paidNotifiedRef.current = false;\n }, [pixPayment]);\n\n const handleGenerateAnother = () => {\n close();\n openPlansCatalog();\n };\n\n const expiresInSeconds =\n pixPayment?.expiresAt && mountedAt\n ? Math.max(0, Math.floor((new Date(pixPayment.expiresAt).getTime() - mountedAt) / 1000))\n : null;\n const shouldShowCountdown = typeof expiresInSeconds === 'number' && expiresInSeconds > 0;\n const { seconds } = useCountdownTimer({ initialSeconds: shouldShowCountdown ? expiresInSeconds : 1 });\n\n const handleCopyCode = async () => {\n if (!pixPayment?.qrCode) return;\n await copyToClipboard(pixPayment.qrCode);\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translate('copiedToast')} toastId={toastId} />\n ));\n };\n\n const payNowLabel =\n pixPayment?.payNowCents != null && pixPayment.payNowCents > 0\n ? formatCurrencyNumber(pixPayment.payNowCents / 100)\n : null;\n\n return (\n <Dialog open={!!pixPayment} onOpenChange={(nextOpen) => !nextOpen && close()}>\n <DialogContent className=\"flex flex-col p-0 gap-0 w-full sm:max-w-130 lg:max-w-130 max-h-[90vh] overflow-y-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300\">\n <DialogHeader className=\"gap-3 text-left p-5 border-b border-zinc-200\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center size-10 bg-zinc-50 rounded-lg shrink-0\">\n <IconQrcode size={20} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <DialogTitle className=\"paragraph-medium-semibold text-zinc-950\">\n {translate('title')}\n </DialogTitle>\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('subtitle')}</span>\n </div>\n </div>\n </DialogHeader>\n\n {/* Fechado o ciclo (pago, recusado ou expirado), o QR perde a função: a janela vira o\n desfecho, como a página de pix do gapps. */}\n {isPaid || isRejected || isExpired ? (\n <div className=\"flex flex-col items-center justify-center gap-4 p-8 text-center\">\n <div\n className={cn(\n 'size-12 rounded-full flex items-center justify-center',\n isPaid ? 'bg-green-50' : 'bg-red-50'\n )}\n >\n {isPaid ? (\n <IconCircleCheck size={24} className=\"text-green-500\" />\n ) : (\n <IconAlertTriangle size={24} className=\"text-red-500\" />\n )}\n </div>\n\n <div className=\"flex flex-col gap-1\">\n <span className=\"paragraph-xlarge-semibold text-zinc-950\">\n {isPaid\n ? translate('paidTitle')\n : isRejected\n ? translate('rejectedTitle')\n : translate('expiredTitle')}\n </span>\n <span className=\"paragraph-small-regular text-zinc-500\">\n {isPaid ? translate('paidToast') : translate('rejectedOrExpiredDescription')}\n </span>\n </div>\n\n {isPaid ? (\n <Button variant=\"secondary\" className=\"h-10\" onClick={close}>\n {translateActions('common.actions.close')}\n </Button>\n ) : (\n <Button variant=\"secondary\" className=\"h-10\" onClick={handleGenerateAnother}>\n {translate('generateAnother')}\n </Button>\n )}\n </div>\n ) : (\n <div className=\"flex flex-col gap-5 p-5\">\n {payNowLabel && (\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-small-medium text-zinc-500\">{translate('payNowLabel')}</span>\n <span className=\"paragraph-medium-semibold text-zinc-950\">{payNowLabel}</span>\n </div>\n )}\n\n <div className=\"flex flex-col gap-6 p-5 border border-zinc-200 rounded-lg bg-white items-center\">\n {shouldShowCountdown && (\n <p className=\"paragraph-small-semibold text-zinc-950 text-center\">\n {translate('payIn')} <span className=\"text-red-600\">{formatCountdown(seconds)}</span>\n </p>\n )}\n\n {generatedQrImage ? (\n <div className=\"size-[198px] border border-zinc-200 rounded-lg overflow-hidden bg-white shrink-0 p-3\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={generatedQrImage}\n alt={translate('qrCodeAlt')}\n className=\"size-full object-contain\"\n />\n </div>\n ) : (\n <Skeleton className=\"size-[198px] rounded-lg shrink-0\" />\n )}\n\n {pixPayment?.qrCode && (\n <>\n <div className=\"flex items-center w-full\">\n <div className=\"flex-1 h-px bg-zinc-200\" />\n <span className=\"px-2 paragraph-xsmall-medium text-zinc-500 whitespace-nowrap\">\n {translate('orCopyCode')}\n </span>\n <div className=\"flex-1 h-px bg-zinc-200\" />\n </div>\n\n <div className=\"flex flex-col items-center gap-4 w-full\">\n <p className=\"paragraph-small-medium text-zinc-950 text-center break-all w-full\">\n {pixPayment.qrCode}\n </p>\n <Button variant=\"secondary\" onClick={handleCopyCode} className=\"h-10 gap-1.5\">\n <IconCopy size={20} />\n {translate('copyCode')}\n </Button>\n </div>\n </>\n )}\n\n <p className=\"paragraph-xsmall-medium text-zinc-500 text-center\">\n {translate('waitingPayment')}\n </p>\n </div>\n </div>\n )}\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/account/sections/subscription/PixPaymentDialog.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\nimport { IconAlertTriangle, IconCircleCheck, IconCopy, IconQrcode } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport { Button } from '../../../ui/buttons/Button';\nimport { Skeleton } from '../../../ui/feedback/Skeleton';\nimport { Toast } from '../../../ui/feedback/Toast';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../ui/overlay/Dialog';\nimport { useCurrencyFormatter } from '../../../../modules/accounts/hooks/use-currency-formatter.hook';\nimport { usePendingPixStatus } from '../../../../modules/subscriptions/hooks/pending-pix-status.hook';\nimport useSecondsUntil from '../../../../hooks/useSecondsUntil';\nimport { copyToClipboard } from '../../../../utils/browser/clipboard';\nimport { qrCodeToDataUrl } from '../../../../utils/qrcode/to-data-url';\nimport { useSubscriptionFlows } from '../../../../store/useSubscriptionFlows';\nimport { cn } from '../../../../infra/utils/clsx';\n\nfunction formatCountdown(seconds: number): string {\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const remaining = seconds % 60;\n const mm = String(minutes).padStart(2, '0');\n const ss = String(remaining).padStart(2, '0');\n\n return hours > 0 ? `${String(hours).padStart(2, '0')}:${mm}:${ss}` : `${mm}:${ss}`;\n}\n\n/**\n * QR code do pix gerado na troca de plano ou reativação. Fica em polling até o backend confirmar\n * o pagamento — no gapps esse passo era uma página de checkout à parte.\n */\nexport function PixPaymentDialog() {\n const translate = useTranslations('common.account.workspace.subscription.pixPayment');\n const translateActions = useTranslations();\n const { formatCurrencyNumber } = useCurrencyFormatter();\n\n const pixPayment = useSubscriptionFlows((state) => state.pixPayment);\n const close = useSubscriptionFlows((state) => state.closePixPayment);\n const openPlansCatalog = useSubscriptionFlows((state) => state.openPlansCatalog);\n\n const [generatedQrImage, setGeneratedQrImage] = useState<string | null>(null);\n\n const { data: statusData } = usePendingPixStatus(pixPayment?.correlationId);\n const phase = statusData?.data?.phase;\n\n useEffect(() => {\n const qrCode = pixPayment?.qrCode;\n if (!qrCode) {\n setGeneratedQrImage(null);\n return;\n }\n\n let cancelled = false;\n qrCodeToDataUrl(qrCode, {\n width: 396,\n margin: 1,\n errorCorrectionLevel: 'M',\n color: { dark: '#171412', light: '#FFFFFF' },\n })\n .then((dataUrl) => {\n if (!cancelled) setGeneratedQrImage(dataUrl);\n })\n .catch(() => {\n if (!cancelled) setGeneratedQrImage(null);\n });\n\n return () => {\n cancelled = true;\n };\n }, [pixPayment?.qrCode]);\n\n const isCompleted = phase === 'completed';\n const isAuthorizedOnly = statusData?.data?.payment_collected === false;\n const completedTitle = isAuthorizedOnly ? translate('authorizedTitle') : translate('paidTitle');\n const completedMessage = isAuthorizedOnly ? translate('authorizedMessage') : translate('paidToast');\n const isRejected = phase === 'rejected';\n const isExpired = phase === 'expired';\n\n // Pago avisa uma vez; a janela fica com a confirmação na tela, como a página do gapps fazia.\n const paidNotifiedRef = useRef(false);\n useEffect(() => {\n if (!isCompleted || paidNotifiedRef.current) return;\n paidNotifiedRef.current = true;\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={completedMessage} toastId={toastId} />\n ));\n }, [isCompleted, completedMessage]);\n\n useEffect(() => {\n if (!pixPayment) paidNotifiedRef.current = false;\n }, [pixPayment]);\n\n const handleGenerateAnother = () => {\n close();\n openPlansCatalog();\n };\n\n const secondsUntilExpiry = useSecondsUntil(pixPayment?.expiresAt);\n\n const handleCopyCode = async () => {\n if (!pixPayment?.qrCode) return;\n await copyToClipboard(pixPayment.qrCode);\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translate('copiedToast')} toastId={toastId} />\n ));\n };\n\n const payNowLabel =\n pixPayment?.payNowCents != null && pixPayment.payNowCents > 0\n ? formatCurrencyNumber(pixPayment.payNowCents / 100)\n : null;\n\n return (\n <Dialog open={!!pixPayment} onOpenChange={(nextOpen) => !nextOpen && close()}>\n <DialogContent className=\"flex flex-col p-0 gap-0 w-full sm:max-w-130 lg:max-w-130 max-h-[90vh] overflow-y-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300\">\n <DialogHeader className=\"gap-3 text-left p-5 border-b border-zinc-200\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center size-10 bg-zinc-50 rounded-lg shrink-0\">\n <IconQrcode size={20} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <DialogTitle className=\"paragraph-medium-semibold text-zinc-950\">\n {translate('title')}\n </DialogTitle>\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('subtitle')}</span>\n </div>\n </div>\n </DialogHeader>\n\n {/* Fechado o ciclo (pago, recusado ou expirado), o QR perde a função: a janela vira o\n desfecho, como a página de pix do gapps. */}\n {isCompleted || isRejected || isExpired ? (\n <div className=\"flex flex-col items-center justify-center gap-4 p-8 text-center\">\n <div\n className={cn(\n 'size-12 rounded-full flex items-center justify-center',\n isCompleted ? 'bg-green-50' : 'bg-red-50'\n )}\n >\n {isCompleted ? (\n <IconCircleCheck size={24} className=\"text-green-500\" />\n ) : (\n <IconAlertTriangle size={24} className=\"text-red-500\" />\n )}\n </div>\n\n <div className=\"flex flex-col gap-1\">\n <span className=\"paragraph-xlarge-semibold text-zinc-950\">\n {isCompleted\n ? completedTitle\n : isRejected\n ? translate('rejectedTitle')\n : translate('expiredTitle')}\n </span>\n <span className=\"paragraph-small-regular text-zinc-500\">\n {isCompleted ? completedMessage : translate('rejectedOrExpiredDescription')}\n </span>\n </div>\n\n {isCompleted ? (\n <Button variant=\"secondary\" className=\"h-10\" onClick={close}>\n {translateActions('common.actions.close')}\n </Button>\n ) : (\n <Button variant=\"secondary\" className=\"h-10\" onClick={handleGenerateAnother}>\n {translate('generateAnother')}\n </Button>\n )}\n </div>\n ) : (\n <div className=\"flex flex-col gap-5 p-5\">\n {payNowLabel && (\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-small-medium text-zinc-500\">{translate('payNowLabel')}</span>\n <span className=\"paragraph-medium-semibold text-zinc-950\">{payNowLabel}</span>\n </div>\n )}\n\n <div className=\"flex flex-col gap-6 p-5 border border-zinc-200 rounded-lg bg-white items-center\">\n {!!secondsUntilExpiry && (\n <p className=\"paragraph-small-semibold text-zinc-950 text-center\">\n {translate('payIn')} <span className=\"text-red-600\">{formatCountdown(secondsUntilExpiry)}</span>\n </p>\n )}\n\n {generatedQrImage ? (\n <div className=\"size-[198px] border border-zinc-200 rounded-lg overflow-hidden bg-white shrink-0 p-3\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={generatedQrImage}\n alt={translate('qrCodeAlt')}\n className=\"size-full object-contain\"\n />\n </div>\n ) : (\n <Skeleton className=\"size-[198px] rounded-lg shrink-0\" />\n )}\n\n {pixPayment?.qrCode && (\n <>\n <div className=\"flex items-center w-full\">\n <div className=\"flex-1 h-px bg-zinc-200\" />\n <span className=\"px-2 paragraph-xsmall-medium text-zinc-500 whitespace-nowrap\">\n {translate('orCopyCode')}\n </span>\n <div className=\"flex-1 h-px bg-zinc-200\" />\n </div>\n\n <div className=\"flex flex-col items-center gap-4 w-full\">\n <p className=\"paragraph-small-medium text-zinc-950 text-center break-all w-full\">\n {pixPayment.qrCode}\n </p>\n <Button variant=\"secondary\" onClick={handleCopyCode} className=\"h-10 gap-1.5\">\n <IconCopy size={20} />\n {translate('copyCode')}\n </Button>\n </div>\n </>\n )}\n\n <p className=\"paragraph-xsmall-medium text-zinc-500 text-center\">\n {translate('waitingPayment')}\n </p>\n </div>\n </div>\n )}\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
function useSecondsUntil(deadline) {
|
|
4
|
+
const [now, setNow] = useState(null);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
if (!deadline) return;
|
|
7
|
+
setNow(Date.now());
|
|
8
|
+
const interval = setInterval(() => setNow(Date.now()), 1e3);
|
|
9
|
+
return () => clearInterval(interval);
|
|
10
|
+
}, [deadline]);
|
|
11
|
+
if (!deadline || now === null) return null;
|
|
12
|
+
return Math.max(0, Math.floor((new Date(deadline).getTime() - now) / 1e3));
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
useSecondsUntil as default
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=useSecondsUntil.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/useSecondsUntil.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\nexport default function useSecondsUntil(deadline: string | null | undefined): number | null {\n const [now, setNow] = useState<number | null>(null);\n\n useEffect(() => {\n if (!deadline) return;\n setNow(Date.now());\n const interval = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(interval);\n }, [deadline]);\n\n if (!deadline || now === null) return null;\n return Math.max(0, Math.floor((new Date(deadline).getTime() - now) / 1000));\n}\n"],"mappings":";AAEA,SAAS,WAAW,gBAAgB;AAErB,SAAR,gBAAiC,UAAoD;AAC1F,QAAM,CAAC,KAAK,MAAM,IAAI,SAAwB,IAAI;AAElD,YAAU,MAAM;AACd,QAAI,CAAC,SAAU;AACf,WAAO,KAAK,IAAI,CAAC;AACjB,UAAM,WAAW,YAAY,MAAM,OAAO,KAAK,IAAI,CAAC,GAAG,GAAI;AAC3D,WAAO,MAAM,cAAc,QAAQ;AAAA,EACrC,GAAG,CAAC,QAAQ,CAAC;AAEb,MAAI,CAAC,YAAY,QAAQ,KAAM,QAAO;AACtC,SAAO,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,QAAQ,EAAE,QAAQ,IAAI,OAAO,GAAI,CAAC;AAC5E;","names":[]}
|
|
@@ -529,6 +529,11 @@ const messages = {
|
|
|
529
529
|
validationCpfChecksum: "Invalid CPF \u2014 check the verification digits",
|
|
530
530
|
validationCnpjChecksum: "Invalid CNPJ \u2014 check the verification digits",
|
|
531
531
|
validationPixDocumentMismatch: "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.",
|
|
532
|
+
validationPixInvalid: "Invalid Pix key. Use a CPF, CNPJ, mobile number with area code, email or random key.",
|
|
533
|
+
pixKeyPreview: "Will be saved as: {value}",
|
|
534
|
+
pixAmbiguousQuestion: "Is this number a mobile phone or a CPF?",
|
|
535
|
+
pixAmbiguousMobile: "Mobile",
|
|
536
|
+
pixAmbiguousCpf: "CPF",
|
|
532
537
|
pixCnpjInvoiceNotice: "Company account: withdrawals require a PDF invoice and do not use the individual monthly limit."
|
|
533
538
|
},
|
|
534
539
|
noBankAccount: {
|
|
@@ -1035,6 +1040,8 @@ const messages = {
|
|
|
1035
1040
|
paidToast: "Payment processed successfully.",
|
|
1036
1041
|
waitingPayment: "Waiting for payment confirmation...",
|
|
1037
1042
|
paidTitle: "PIX paid",
|
|
1043
|
+
authorizedTitle: "Automatic PIX authorized",
|
|
1044
|
+
authorizedMessage: "The charge will be made automatically on the due date.",
|
|
1038
1045
|
rejectedTitle: "PIX rejected",
|
|
1039
1046
|
expiredTitle: "PIX code expired",
|
|
1040
1047
|
rejectedOrExpiredDescription: "Click below to generate a new PIX code",
|