@greatapps/common 1.1.805 → 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.
@@ -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 useCountdownTimer from '../../../../hooks/useCountdownTimer';
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';
@@ -39,20 +39,11 @@ export function PixPaymentDialog() {
39
39
  const close = useSubscriptionFlows((state) => state.closePixPayment);
40
40
  const openPlansCatalog = useSubscriptionFlows((state) => state.openPlansCatalog);
41
41
 
42
- const [mountedAt, setMountedAt] = useState<number | null>(null);
43
42
  const [generatedQrImage, setGeneratedQrImage] = useState<string | null>(null);
44
43
 
45
44
  const { data: statusData } = usePendingPixStatus(pixPayment?.correlationId);
46
45
  const phase = statusData?.data?.phase;
47
46
 
48
- useEffect(() => {
49
- if (!pixPayment) {
50
- setMountedAt(null);
51
- return;
52
- }
53
- setMountedAt(Date.now());
54
- }, [pixPayment]);
55
-
56
47
  useEffect(() => {
57
48
  const qrCode = pixPayment?.qrCode;
58
49
  if (!qrCode) {
@@ -79,19 +70,22 @@ export function PixPaymentDialog() {
79
70
  };
80
71
  }, [pixPayment?.qrCode]);
81
72
 
82
- const isPaid = phase === 'completed';
73
+ const isCompleted = phase === 'completed';
74
+ const isAuthorizedOnly = statusData?.data?.payment_collected === false;
75
+ const completedTitle = isAuthorizedOnly ? translate('authorizedTitle') : translate('paidTitle');
76
+ const completedMessage = isAuthorizedOnly ? translate('authorizedMessage') : translate('paidToast');
83
77
  const isRejected = phase === 'rejected';
84
78
  const isExpired = phase === 'expired';
85
79
 
86
80
  // Pago avisa uma vez; a janela fica com a confirmação na tela, como a página do gapps fazia.
87
81
  const paidNotifiedRef = useRef(false);
88
82
  useEffect(() => {
89
- if (!isPaid || paidNotifiedRef.current) return;
83
+ if (!isCompleted || paidNotifiedRef.current) return;
90
84
  paidNotifiedRef.current = true;
91
85
  toast.custom((toastId) => (
92
- <Toast variant="success" message={translate('paidToast')} toastId={toastId} />
86
+ <Toast variant="success" message={completedMessage} toastId={toastId} />
93
87
  ));
94
- }, [isPaid, translate]);
88
+ }, [isCompleted, completedMessage]);
95
89
 
96
90
  useEffect(() => {
97
91
  if (!pixPayment) paidNotifiedRef.current = false;
@@ -102,12 +96,7 @@ export function PixPaymentDialog() {
102
96
  openPlansCatalog();
103
97
  };
104
98
 
105
- const expiresInSeconds =
106
- pixPayment?.expiresAt && mountedAt
107
- ? Math.max(0, Math.floor((new Date(pixPayment.expiresAt).getTime() - mountedAt) / 1000))
108
- : null;
109
- const shouldShowCountdown = typeof expiresInSeconds === 'number' && expiresInSeconds > 0;
110
- const { seconds } = useCountdownTimer({ initialSeconds: shouldShowCountdown ? expiresInSeconds : 1 });
99
+ const secondsUntilExpiry = useSecondsUntil(pixPayment?.expiresAt);
111
100
 
112
101
  const handleCopyCode = async () => {
113
102
  if (!pixPayment?.qrCode) return;
@@ -141,15 +130,15 @@ export function PixPaymentDialog() {
141
130
 
142
131
  {/* Fechado o ciclo (pago, recusado ou expirado), o QR perde a função: a janela vira o
143
132
  desfecho, como a página de pix do gapps. */}
144
- {isPaid || isRejected || isExpired ? (
133
+ {isCompleted || isRejected || isExpired ? (
145
134
  <div className="flex flex-col items-center justify-center gap-4 p-8 text-center">
146
135
  <div
147
136
  className={cn(
148
137
  'size-12 rounded-full flex items-center justify-center',
149
- isPaid ? 'bg-green-50' : 'bg-red-50'
138
+ isCompleted ? 'bg-green-50' : 'bg-red-50'
150
139
  )}
151
140
  >
152
- {isPaid ? (
141
+ {isCompleted ? (
153
142
  <IconCircleCheck size={24} className="text-green-500" />
154
143
  ) : (
155
144
  <IconAlertTriangle size={24} className="text-red-500" />
@@ -158,18 +147,18 @@ export function PixPaymentDialog() {
158
147
 
159
148
  <div className="flex flex-col gap-1">
160
149
  <span className="paragraph-xlarge-semibold text-zinc-950">
161
- {isPaid
162
- ? translate('paidTitle')
150
+ {isCompleted
151
+ ? completedTitle
163
152
  : isRejected
164
153
  ? translate('rejectedTitle')
165
154
  : translate('expiredTitle')}
166
155
  </span>
167
156
  <span className="paragraph-small-regular text-zinc-500">
168
- {isPaid ? translate('paidToast') : translate('rejectedOrExpiredDescription')}
157
+ {isCompleted ? completedMessage : translate('rejectedOrExpiredDescription')}
169
158
  </span>
170
159
  </div>
171
160
 
172
- {isPaid ? (
161
+ {isCompleted ? (
173
162
  <Button variant="secondary" className="h-10" onClick={close}>
174
163
  {translateActions('common.actions.close')}
175
164
  </Button>
@@ -189,9 +178,9 @@ export function PixPaymentDialog() {
189
178
  )}
190
179
 
191
180
  <div className="flex flex-col gap-6 p-5 border border-zinc-200 rounded-lg bg-white items-center">
192
- {shouldShowCountdown && (
181
+ {!!secondsUntilExpiry && (
193
182
  <p className="paragraph-small-semibold text-zinc-950 text-center">
194
- {translate('payIn')} <span className="text-red-600">{formatCountdown(seconds)}</span>
183
+ {translate('payIn')} <span className="text-red-600">{formatCountdown(secondsUntilExpiry)}</span>
195
184
  </p>
196
185
  )}
197
186
 
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+
5
+ export default function useSecondsUntil(deadline: string | null | undefined): number | null {
6
+ const [now, setNow] = useState<number | null>(null);
7
+
8
+ useEffect(() => {
9
+ if (!deadline) return;
10
+ setNow(Date.now());
11
+ const interval = setInterval(() => setNow(Date.now()), 1000);
12
+ return () => clearInterval(interval);
13
+ }, [deadline]);
14
+
15
+ if (!deadline || now === null) return null;
16
+ return Math.max(0, Math.floor((new Date(deadline).getTime() - now) / 1000));
17
+ }
@@ -1092,6 +1092,8 @@ const messages = {
1092
1092
  paidToast: 'Payment processed successfully.',
1093
1093
  waitingPayment: 'Waiting for payment confirmation...',
1094
1094
  paidTitle: 'PIX paid',
1095
+ authorizedTitle: 'Automatic PIX authorized',
1096
+ authorizedMessage: 'The charge will be made automatically on the due date.',
1095
1097
  rejectedTitle: 'PIX rejected',
1096
1098
  expiredTitle: 'PIX code expired',
1097
1099
  rejectedOrExpiredDescription: 'Click below to generate a new PIX code',
@@ -1098,6 +1098,8 @@ const messages = {
1098
1098
  paidToast: 'Pago realizado exitosamente.',
1099
1099
  waitingPayment: 'Esperando la confirmación del pago...',
1100
1100
  paidTitle: 'PIX pagado',
1101
+ authorizedTitle: 'PIX automático autorizado',
1102
+ authorizedMessage: 'El cobro se realizará automáticamente en la fecha de vencimiento.',
1101
1103
  rejectedTitle: 'PIX rechazado',
1102
1104
  expiredTitle: 'Código PIX expirado',
1103
1105
  rejectedOrExpiredDescription: 'Haz clic abajo para generar un nuevo código PIX',
@@ -1105,6 +1105,8 @@ const messages = {
1105
1105
  paidToast: 'Pagamento efetuado com sucesso.',
1106
1106
  waitingPayment: 'Aguardando a confirmação do pagamento...',
1107
1107
  paidTitle: 'PIX pago',
1108
+ authorizedTitle: 'PIX automático autorizado',
1109
+ authorizedMessage: 'A cobrança será feita automaticamente na data de vencimento.',
1108
1110
  rejectedTitle: 'Pix recusado',
1109
1111
  expiredTitle: 'Código PIX expirado',
1110
1112
  rejectedOrExpiredDescription: 'Clique abaixo para gerar um novo código PIX',
@@ -16,6 +16,7 @@ export const PendingPixStatusDataSchema = z
16
16
  payment_method: z.number().nullable().optional(),
17
17
  subscription_id: z.union([z.number(), z.string()]).optional(),
18
18
  completed_at: z.string().nullable().optional(),
19
+ payment_collected: z.boolean().optional(),
19
20
  pix: z
20
21
  .object({
21
22
  qr_code: z.string().nullable().optional(),