@greatapps/common 1.1.804 → 1.1.806
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/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 +2 -0
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +2 -0
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +2 -0
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/modules/accounts/services/account.service.mjs +13 -8
- package/dist/modules/accounts/services/account.service.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/modules/users/schema.mjs.map +1 -1
- package/dist/modules/users/services/user.service.mjs +2 -1
- package/dist/modules/users/services/user.service.mjs.map +1 -1
- package/dist/providers/query.provider.mjs +1 -1
- package/dist/providers/query.provider.mjs.map +1 -1
- package/package.json +1 -1
- 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 +2 -0
- package/src/i18n/messages/es-es.ts +2 -0
- package/src/i18n/messages/pt-br.ts +2 -0
- package/src/modules/accounts/services/account.service.ts +16 -8
- package/src/modules/accounts/types.ts +1 -3
- package/src/modules/auth/schema.ts +0 -1
- package/src/modules/subscriptions/types/pending-pix-status.type.ts +1 -0
- package/src/modules/users/schema.ts +1 -1
- package/src/modules/users/services/user.service.ts +3 -1
- package/src/providers/query.provider.tsx +1 -1
|
@@ -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":"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","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":[]}
|
|
@@ -1035,6 +1035,8 @@ const messages = {
|
|
|
1035
1035
|
paidToast: "Payment processed successfully.",
|
|
1036
1036
|
waitingPayment: "Waiting for payment confirmation...",
|
|
1037
1037
|
paidTitle: "PIX paid",
|
|
1038
|
+
authorizedTitle: "Automatic PIX authorized",
|
|
1039
|
+
authorizedMessage: "The charge will be made automatically on the due date.",
|
|
1038
1040
|
rejectedTitle: "PIX rejected",
|
|
1039
1041
|
expiredTitle: "PIX code expired",
|
|
1040
1042
|
rejectedOrExpiredDescription: "Click below to generate a new PIX code",
|