import type { Route } from './+types/route' import { formatCEP } from '@brazilian-utils/brazilian-utils' import { zodResolver } from '@hookform/resolvers/zod' import { useRequest, useToggle } from 'ahooks' import { AlertCircleIcon, ArrowLeftRightIcon, CircleCheckIcon, CircleXIcon, EllipsisIcon, ExternalLinkIcon, HelpCircleIcon } from 'lucide-react' import { useEffect } from 'react' import { useForm } from 'react-hook-form' import { Link, useRevalidator } from 'react-router' import { z } from 'zod' import { Abbr } from '@repo/ui/components/abbr' import { Currency } from '@repo/ui/components/currency' import { DateTime } from '@repo/ui/components/datetime' import { Badge } from '@repo/ui/components/ui/badge' import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@repo/ui/components/ui/breadcrumb' import { Button } from '@repo/ui/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@repo/ui/components/ui/card' import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@repo/ui/components/ui/dialog' import { Item, ItemActions, ItemContent, ItemGroup, ItemTitle } from '@repo/ui/components/ui/item' import { Kbd } from '@repo/ui/components/ui/kbd' import { Popover, PopoverContent, PopoverTrigger } from '@repo/ui/components/ui/popover' import { Separator } from '@repo/ui/components/ui/separator' import { Spinner } from '@repo/ui/components/ui/spinner' import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '@repo/ui/components/ui/table' import { cn } from '@repo/ui/lib/utils' import { labels, statuses, type Order as Order_ } from '@repo/ui/routes/orders/data' import { request as req } from '@repo/util/request' import { CreditCard, creditCardSchema, type CreditCard as CreditCardProps } from '../_.$orgid.enrollments.buy/payment' import type { Address } from '../_.$orgid.enrollments.buy/review' import { useWizardStore } from '../_.$orgid.enrollments.buy/store' import { Enrollments } from './enrollments' export function meta() { return [ { title: 'Detalhes do pagamento' } ] } const PaymentMethodComponent = { PIX: PixPaymentMethod, BANK_SLIP: BankSlipPaymentMethod, CREDIT_CARD: CreditCardPaymentMethod } type Item = { id: string name: string unit_price: number quantity: number } type User = { id: string name: string email: string } type Invoice = { invoice_id: string secure_url: string bank_slip?: { bank_slip_pdf_url: string } pix?: { qrcode: string qrcode_text: string } } type Attempts = { sk: string status: string brand: string last4: string } type Course = { id: string name: string } export type Enrollment = { status: 'PENDING' | 'EXECUTED' | 'ROLLBACK' | 'SCHEDULED' user: User course: Course executed_at?: string rollback_at?: string scheduled_at?: string reason?: string } export type Seat = { course: Course } type Order = Order_ & { items: Item[] interest_amount: number due_date: string created_at: string paid_at?: string canceled_at?: string expired_at?: string subtotal: number discount: number address: Address payment_attempts: Attempts[] credit_card?: CreditCardProps coupon?: string enrollments?: Enrollment[] seats?: Seat[] installments?: number created_by?: User invoice: Invoice } export async function loader({ context, request, params }: Route.LoaderArgs) { const r = await req({ url: `/orders/${params.id}`, context, request }) if (!r.ok) { throw new Response(null, { status: r.status }) } const order = (await r.json()) as Order return { order } } export default function Route({ loaderData: { order } }: Route.ComponentProps) { const { reset } = useWizardStore() const { coupon, address, total, payment_method, interest_amount, discount, invoice, payment_attempts = [], enrollments = [], seats = [], items = [], created_at, expired_at, paid_at, subtotal } = order const Component = (PaymentMethodComponent as Record>)[ payment_method ] ?? UnknownPaymentMethod useEffect(() => { reset() }, []) return (
Pagamentos Detalhes do pagamento Detalhes do pagamento {/* Billing address */} Endereço de cobrança
    {address?.address1} {address?.address2 ? <>, {address?.address2} : null}
    {address?.neighborhood}
    {address?.city}, {address?.state}
    {formatCEP(address?.postcode)}
{/* Payment method */} Forma de pagamento
{Component && }
    {paid_at && (
  • Pago em{' '} {paid_at}
  • )} {expired_at && (
  • Expirado em{' '} {expired_at}
  • )}
  • Comprado em{' '} {created_at}
{payment_attempts.length > 0 ? ( ) : null}
Curso Quantidade Valor unit. Total {items?.map(({ name, unit_price, quantity }, index) => { return ( {name} {quantity} {unit_price} {unit_price * quantity} ) })} {/* Summary */} Subtotal {subtotal} {/* Discount */} Descontos {coupon && ( {coupon} )} {discount} {/* Interest */} {interest_amount ? ( Juros {interest_amount} ) : ( <> )} {/* Total */} Total {total}
{enrollments.length > 0 ? ( ) : null}
) } function Status({ status: s }: { status: string }) { const status = labels[s] ?? s const { icon: Icon, color } = statuses?.[s] ?? { icon: HelpCircleIcon } return ( {status} ) } type PaymentMethodProps = { id: string status: string total: number invoice: Invoice installments: number } type BankSlipPaymentMethodProps = PaymentMethodProps & {} function BankSlipPaymentMethod({ status, invoice }: BankSlipPaymentMethodProps) { return (
{invoice?.bank_slip ? ( <> ) : null}
) } type PixPaymentMethodrops = PaymentMethodProps & {} function PixPaymentMethod({ invoice, status }: PixPaymentMethodrops) { return (
{invoice?.pix ? (
{invoice.pix.qrcode_text}
) : null}
) } function UnknownPaymentMethod() { return <>Deposito bancário } type CreditCardPaymentMethodProps = PaymentMethodProps & { stats?: { last_attempt_succeeded: boolean } credit_card: { last4: string; brand: string } } function CreditCardPaymentMethod({ id, status, total, credit_card, installments, invoice, stats }: CreditCardPaymentMethodProps) { return ( <>

{installments}x {total / Number(installments)}

{stats?.last_attempt_succeeded === false && status === 'PENDING' && invoice?.invoice_id ? (
) : null} ) } const formSchema = z.object({ credit_card: creditCardSchema }) type Schema = z.input function CreditCardPaymentDialog({ children, id, installments, invoice_id, total }) { const revalidator = useRevalidator() const [open, { set: setOpen, toggle }] = useToggle() const { runAsync } = useRequest( async ({ credit_card }) => { return await fetch(`/~/api/orders/${id}/payment-retries`, { method: 'POST', headers: new Headers({ 'Content-Type': 'application/json' }), body: JSON.stringify({ credit_card, installments, invoice_id }) }) }, { manual: true } ) const { control, handleSubmit, formState } = useForm({ resolver: zodResolver(formSchema) }) const onSubmit = async ({ credit_card }: Schema) => { await runAsync({ credit_card }) revalidator.revalidate() setOpen(false) } return ( {children} Novo cartão de crédito Use um novo cartão para concluir o pagamento. Nenhuma cobrança foi realizada no cartão anterior.
) } function PaymentAttemptsMenu({ payment_attempts }: { payment_attempts: Attempts[] }) { return (
{payment_attempts.map(({ sk, brand, last4, status }, index) => { const [, , created_at] = sk.split('#') return (
  • {created_at}
  • {brand}
  • **** {last4}
  • {status === 'FAILED' ? ( ) : ( )}
) })}
) }