From 5f53ffb4a78b8c0f03efd49d85b7f243350c7594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Thu, 15 Jan 2026 14:21:11 -0300 Subject: [PATCH] update payment details --- .../routes/_.$orgid.enrollments.add/route.tsx | 76 +++---- .../routes/_.$orgid.enrollments.buy/bulk.tsx | 24 +- .../_.$orgid.enrollments.buy/review.tsx | 15 +- .../_.$orgid.payments.$id._index/route.tsx | 209 +++++++++++++----- .../_.$orgid.payments._index/columns.tsx | 8 +- .../app/events/payments/charge_credit_card.py | 32 +-- packages/ui/src/routes/orders/columns.tsx | 6 +- 7 files changed, 241 insertions(+), 129 deletions(-) diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/route.tsx index 9bf75c4..e2cfa76 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/route.tsx @@ -1,21 +1,40 @@ import type { Route } from './+types/route' -import { Fragment, use, useEffect, type ReactNode } from 'react' -import { useRequest, useToggle } from 'ahooks' import { ErrorMessage } from '@hookform/error-message' +import { zodResolver } from '@hookform/resolvers/zod' +import { useRequest, useToggle } from 'ahooks' import { + CircleQuestionMarkIcon, CopyIcon, CopyPlusIcon, - Trash2Icon, - PlusIcon, EllipsisIcon, - CircleQuestionMarkIcon + PlusIcon, + Trash2Icon } from 'lucide-react' -import { redirect, Link, useParams, useFetcher } from 'react-router' -import { Controller, useFieldArray, useForm } from 'react-hook-form' -import { zodResolver } from '@hookform/resolvers/zod' import { pick } from 'ramda' +import { Fragment, use, useEffect, type ReactNode } from 'react' +import { Controller, useFieldArray, useForm } from 'react-hook-form' +import { Link, redirect, useFetcher, useParams } from 'react-router' +import { cloudflareContext } from '@repo/auth/context' +import { DateTime } from '@repo/ui/components/datetime' +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator +} from '@repo/ui/components/ui/breadcrumb' +import { Button } from '@repo/ui/components/ui/button' +import { + Card, + CardAction, + CardContent, + CardDescription, + CardHeader, + CardTitle +} from '@repo/ui/components/ui/card' import { Command, CommandEmpty, @@ -28,51 +47,32 @@ import { HoverCardContent, HoverCardTrigger } from '@repo/ui/components/ui/hover-card' -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator -} from '@repo/ui/components/ui/breadcrumb' -import { - Card, - CardAction, - CardContent, - CardDescription, - CardHeader, - CardTitle -} from '@repo/ui/components/ui/card' -import { DateTime } from '@repo/ui/components/datetime' -import { Spinner } from '@repo/ui/components/ui/spinner' import { Input } from '@repo/ui/components/ui/input' -import { Button } from '@repo/ui/components/ui/button' -import { Separator } from '@repo/ui/components/ui/separator' +import { Kbd } from '@repo/ui/components/ui/kbd' +import { Label } from '@repo/ui/components/ui/label' import { Popover, PopoverContent, PopoverTrigger } from '@repo/ui/components/ui/popover' -import { Kbd } from '@repo/ui/components/ui/kbd' -import { Label } from '@repo/ui/components/ui/label' +import { Separator } from '@repo/ui/components/ui/separator' +import { Spinner } from '@repo/ui/components/ui/spinner' +import { useIsMobile } from '@repo/ui/hooks/use-mobile' import { createSearch } from '@repo/util/meili' import { HttpMethod, request as req } from '@repo/util/request' -import { useIsMobile } from '@repo/ui/hooks/use-mobile' -import { cloudflareContext } from '@repo/auth/context' +import { workspaceContext } from '@/middleware/workspace' +import { cn } from '@repo/ui/lib/utils' +import { CoursePicker } from './course-picker' import { - MAX_ITEMS, formSchema, + MAX_ITEMS, + type Enrolled, type Schema, - type User, - type Enrolled + type User } from './data' import { ScheduledForInput } from './scheduled-for' -import { CoursePicker } from './course-picker' import { UserPicker } from './user-picker' -import { cn } from '@repo/ui/lib/utils' -import { workspaceContext } from '@/middleware/workspace' const emptyRow = { user: undefined, diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/bulk.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/bulk.tsx index bd1edfd..a7caade 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/bulk.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/bulk.tsx @@ -1,4 +1,5 @@ -import { Fragment, useEffect } from 'react' +import { ErrorMessage } from '@hookform/error-message' +import { zodResolver } from '@hookform/resolvers/zod' import { ArrowRightIcon, MinusIcon, @@ -6,31 +7,30 @@ import { Trash2Icon, XIcon } from 'lucide-react' -import { useForm, useFieldArray, Controller, useWatch } from 'react-hook-form' -import { ErrorMessage } from '@hookform/error-message' -import { zodResolver } from '@hookform/resolvers/zod' +import { Fragment, useEffect } from 'react' +import { Controller, useFieldArray, useForm, useWatch } from 'react-hook-form' import { z } from 'zod' +import { Abbr } from '@repo/ui/components/abbr' +import { Button } from '@repo/ui/components/ui/button' +import { Form } from '@repo/ui/components/ui/form' import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@repo/ui/components/ui/input-group' -import { Form } from '@repo/ui/components/ui/form' -import { Button } from '@repo/ui/components/ui/button' +import { Kbd } from '@repo/ui/components/ui/kbd' import { Separator } from '@repo/ui/components/ui/separator' import { Spinner } from '@repo/ui/components/ui/spinner' -import { Kbd } from '@repo/ui/components/ui/kbd' -import { Abbr } from '@repo/ui/components/abbr' -import { Cell } from '../_.$orgid.enrollments.add/route' +import { useWizard } from '@/components/wizard' import { CoursePicker } from '../_.$orgid.enrollments.add/course-picker' import { MAX_ITEMS, type Course } from '../_.$orgid.enrollments.add/data' -import { Discount, applyDiscount, type Coupon } from './discount' -import { currency } from './utils' -import { useWizard } from '@/components/wizard' +import { Cell } from '../_.$orgid.enrollments.add/route' +import { Discount } from './discount' import { useWizardStore } from './store' +import { currency } from './utils' const emptyRow = { course: undefined as any, diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/review.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/review.tsx index 2d1480e..9e4214b 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/review.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/review.tsx @@ -8,6 +8,8 @@ import { z } from 'zod' import valid from 'card-validator' import { Currency } from '@repo/ui/components/currency' +import { Kbd } from '@repo/ui/components/ui/kbd' +import { Abbr } from '@repo/ui/components/abbr' import { Button } from '@repo/ui/components/ui/button' import { Separator } from '@repo/ui/components/ui/separator' import { Spinner } from '@repo/ui/components/ui/spinner' @@ -73,7 +75,7 @@ type ReviewProps = { export function Review({ onSubmit }: ReviewProps) { const wizard = useWizard() - const { items, summary, address } = useWizardStore() + const { items, summary, address, coupon } = useWizardStore() const { subtotal, discount, interest_amount, total } = summary() const [loading, { set }] = useToggle() @@ -127,8 +129,15 @@ export function Review({ onSubmit }: ReviewProps) { {/* Discount */} - - Descontos + + + Descontos + {coupon && ( + + {coupon.code} + + )} + {discount} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx index 6540f2f..c5f0107 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx @@ -1,12 +1,15 @@ import type { Route } from './+types/route' import { formatCEP } from '@brazilian-utils/brazilian-utils' -import { CalendarClockIcon, CalendarIcon, UserIcon } from 'lucide-react' +import { + AlertCircleIcon, + ArrowLeftRightIcon, + HelpCircleIcon +} from 'lucide-react' import { useEffect } from 'react' import { Link } from 'react-router' import { Currency } from '@repo/ui/components/currency' -import { DateTime } from '@repo/ui/components/datetime' import { Breadcrumb, BreadcrumbItem, @@ -18,15 +21,12 @@ import { import { Card, CardContent, - CardDescription, - CardFooter, CardHeader, CardTitle } from '@repo/ui/components/ui/card' import { Item, ItemContent, - ItemDescription, ItemGroup, ItemTitle } from '@repo/ui/components/ui/item' @@ -39,8 +39,21 @@ import { TableHeader, TableRow } from '@repo/ui/components/ui/table' -import { paymentMethods } from '@repo/ui/routes/orders/data' import { request as req } from '@repo/util/request' + +import { Abbr } from '@repo/ui/components/abbr' +import { Badge } from '@repo/ui/components/ui/badge' +import { Button } from '@repo/ui/components/ui/button' +import { Kbd } from '@repo/ui/components/ui/kbd' +import { cn } from '@repo/ui/lib/utils' +import { + labels, + statuses, + type Order as Order_ +} from '@repo/ui/routes/orders/data' + +import type { CreditCard as CreditCard_ } from '../_.$orgid.enrollments.buy/payment' +import type { Address } from '../_.$orgid.enrollments.buy/review' import { useWizardStore } from '../_.$orgid.enrollments.buy/store' export function meta() { @@ -51,12 +64,44 @@ export function meta() { ] } +const PaymentMethodComponent = { + PIX: Pix, + BANK_SLIP: BankSlip, + CREDIT_CARD: CreditCard +} + +type Item_ = { + id: string + name: string + unit_price: number + quantity: number +} + +type User = { + id: string + name: string +} + +type Order = Order_ & { + items: Item_[] + interest_amount: number + due_date: string + created_at: string + subtotal: number + discount: number + address: Address + credit_card?: CreditCard_ + coupon?: string + installments?: number + created_by?: User +} + export async function loader({ context, request, params }: Route.LoaderArgs) { - const order = await req({ + const order = (await req({ url: `/orders/${params.id}`, context, request - }).then((r) => r.json()) + }).then((r) => r.json())) as Order return { order } } @@ -64,22 +109,18 @@ export async function loader({ context, request, params }: Route.LoaderArgs) { export default function Route({ loaderData: { order } }: Route.ComponentProps) { const { reset } = useWizardStore() const { - status, + coupon, address, total, - credit_card, payment_method, - due_date, - created_at, - invoice, - installments, interest_amount, discount, subtotal, - items = [], - created_by + items = [] } = order + const Component = PaymentMethodComponent[payment_method] + useEffect(() => { reset() }, []) @@ -103,14 +144,6 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) { Detalhes do pagamento - {status} - -
    -
  • - {due_date} -
  • -
-
@@ -134,26 +167,12 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) { Forma de pagamento - - {credit_card ? ( - <> - {credit_card.brand} (Crédito) **** {credit_card.last4} -
- {installments}x{' '} - {total / Number(installments)} - - ) : ( - <> - {payment_method - ? paymentMethods[payment_method] - : payment_method} - - )} -
+
+ {Component && } +
- {/*
{JSON.stringify(order, null, 2)}
*/} @@ -193,8 +212,15 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) { {/* Discount */} - - Descontos + + + Descontos + {coupon && ( + + {coupon} + + )} + {discount} @@ -225,18 +251,93 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
- - {/* -
    -
  • - {created_at} -
  • {' '} -
  • - {created_by.name} -
  • -
-
*/}
+ + {/*
{JSON.stringify(order, null, 2)}
*/} ) } + +function Status({ status: s }: { status: string }) { + const status = labels[s] ?? s + const { icon: Icon, color } = statuses?.[s] ?? { icon: HelpCircleIcon } + + return ( + + + {status} + + ) +} + +type PaymentMethodProps = { + status: string + total: number + installments: number +} + +type CreditCardProps = PaymentMethodProps & { + stats: { last_attempt_succeeded: boolean } + credit_card: { last4: string; brand: string } +} + +function CreditCard({ + status, + total, + credit_card, + installments, + stats +}: CreditCardProps) { + return ( + <> +
    +
  • + {credit_card.brand} (Crédito) **** {credit_card.last4} +
  • +
  • + {!stats.last_attempt_succeeded ? ( + + Transação negada + + ) : ( + + )} +
  • +
+ +

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

+ + {!stats.last_attempt_succeeded ? ( +
+ +
+ ) : null} + + ) +} + +type BankSlipProps = PaymentMethodProps & {} + +function BankSlip({ status }: BankSlipProps) { + return ( +
    +
  • Boleto bancário
  • +
  • + +
  • +
+ ) +} + +type PixProps = PaymentMethodProps & {} + +function Pix({}: PixProps) { + return <>Pix +} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments._index/columns.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments._index/columns.tsx index e94291b..d0a09a9 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments._index/columns.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments._index/columns.tsx @@ -2,10 +2,10 @@ import { type ColumnDef } from '@tanstack/react-table' -import { - DataTableColumnHeaderSelect, - DataTableColumnSelect -} from '@repo/ui/components/data-table' +// import { +// DataTableColumnHeaderSelect, +// DataTableColumnSelect +// } from '@repo/ui/components/data-table' import { columns as columns_, type Order } from '@repo/ui/routes/orders/columns' export type { Order } diff --git a/orders-events/app/events/payments/charge_credit_card.py b/orders-events/app/events/payments/charge_credit_card.py index fe8e5a6..ccb4b2b 100644 --- a/orders-events/app/events/payments/charge_credit_card.py +++ b/orders-events/app/events/payments/charge_credit_card.py @@ -44,21 +44,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: ) with dyn.transact_writer() as transact: - transact.delete(key=KeyPair(order_id, 'TRANSACTION')) - transact.update( - key=KeyPair(order_id, 'TRANSACTION#STATS'), - update_expr='SET #count = if_not_exists(#count, :zero) + :one, \ - updated_at = :now', - expr_attr_names={ - '#count': 'payment_attempts', - }, - expr_attr_values={ - ':zero': 0, - ':one': 1, - ':now': now(), - }, - ) - if charge['success'] is True: transact.update( key=KeyPair(order_id, '0'), @@ -97,4 +82,21 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: }, ) + transact.delete(key=KeyPair(order_id, 'TRANSACTION')) + transact.update( + key=KeyPair(order_id, 'TRANSACTION#STATS'), + update_expr='SET #count = if_not_exists(#count, :zero) + :one, \ + last_attempt_succeeded = :succeeded, \ + updated_at = :now', + expr_attr_names={ + '#count': 'payment_attempts', + }, + expr_attr_values={ + ':succeeded': charge['success'], + ':zero': 0, + ':one': 1, + ':now': now(), + }, + ) + return charge['success'] diff --git a/packages/ui/src/routes/orders/columns.tsx b/packages/ui/src/routes/orders/columns.tsx index 86524c4..c3f79a3 100644 --- a/packages/ui/src/routes/orders/columns.tsx +++ b/packages/ui/src/routes/orders/columns.tsx @@ -1,15 +1,15 @@ 'use client' import { - DataTableColumnDatetime, DataTableColumnCurrency, + DataTableColumnDatetime, DataTableColumnHeaderSort } from '@repo/ui/components/data-table' import { type ColumnDef } from '@tanstack/react-table' import { HelpCircleIcon } from 'lucide-react' -import { cn } from '@repo/ui/lib/utils' import { Badge } from '@repo/ui/components/ui/badge' +import { cn } from '@repo/ui/lib/utils' import { labels, paymentMethods, statuses, type Order } from './data' @@ -33,7 +33,7 @@ export const columns: ColumnDef[] = [ const { icon: Icon, color } = statuses?.[s] ?? { icon: HelpCircleIcon } return ( - + {status}