diff --git a/package.json b/package.json index 975ef5f..42ca0e3 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "clsx": "^2.1.1", "lucide-react": "^0.454.0", "motion": "^12.4.0", + "qrcode.react": "^4.2.0", "react": "^19.1.0", "react-dom": "^19.1.0", "tailwind-merge": "^2.5.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b50890f..79df99e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: motion: specifier: ^12.4.0 version: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + qrcode.react: + specifier: ^4.2.0 + version: 4.2.0(react@19.2.5) react: specifier: ^19.1.0 version: 19.2.5 @@ -1604,6 +1607,11 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qrcode.react@4.2.0: + resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom@19.2.5: resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} peerDependencies: @@ -3307,6 +3315,10 @@ snapshots: punycode@2.3.1: {} + qrcode.react@4.2.0(react@19.2.5): + dependencies: + react: 19.2.5 + react-dom@19.2.5(react@19.2.5): dependencies: react: 19.2.5 diff --git a/src/components/checkout/PixCheckout.tsx b/src/components/checkout/PixCheckout.tsx new file mode 100644 index 0000000..037733b --- /dev/null +++ b/src/components/checkout/PixCheckout.tsx @@ -0,0 +1,88 @@ +import { useEffect, useState } from 'react' +import { QRCodeSVG } from 'qrcode.react' +import { chatwootApi, type CreateReservationResponse } from '@/lib/chatwootApi' +import { Button } from '@/components/ui/button' +import { formatBRL } from '@/lib/formatters' + +interface Props { + reservation: CreateReservationResponse + depositCents: number + onPaid: () => void + onCancel: () => void +} + +export function PixCheckout({ reservation, depositCents, onPaid, onCancel }: Props) { + const [copied, setCopied] = useState(false) + const [statusMsg, setStatusMsg] = useState('Aguardando pagamento...') + + useEffect(() => { + let canceled = false + const interval = setInterval(async () => { + try { + const s = await chatwootApi.getStatus(reservation.reservation_id) + if (canceled) return + if (s.status === 'paid') { + setStatusMsg('Pagamento confirmado!') + clearInterval(interval) + onPaid() + } else if (s.status === 'expired' || s.status === 'canceled') { + setStatusMsg(`Reserva ${s.status}`) + clearInterval(interval) + } + } catch (err) { + console.error('Erro no polling:', err) + } + }, 3000) + + return () => { + canceled = true + clearInterval(interval) + } + }, [reservation.reservation_id, onPaid]) + + const handleCopy = async () => { + await navigator.clipboard.writeText(reservation.pix.copia_e_cola) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + return ( +
+
+

Pagamento

+

+ {formatBRL(depositCents)} +

+

{statusMsg}

+
+ +
+ +
+ +
+ +
+ + +
+
+ +

+ Expira em 1h. Após o pagamento, esta tela atualiza automaticamente. +

+ + +
+ ) +}