add retries to order

This commit is contained in:
2026-01-15 19:58:57 -03:00
parent ca52384b53
commit 466936acf4
9 changed files with 402 additions and 273 deletions

View File

@@ -1,23 +1,14 @@
import { useForm, Controller, useWatch, type Control } from 'react-hook-form'
import { PatternFormat } from 'react-number-format'
import { zodResolver } from '@hookform/resolvers/zod'
import { ErrorMessage } from '@hookform/error-message'
import { z } from 'zod'
import { ArrowRightIcon, CircleQuestionMarkIcon } from 'lucide-react'
import { zodResolver } from '@hookform/resolvers/zod'
import valid from 'card-validator'
import { ArrowRightIcon, CircleQuestionMarkIcon } from 'lucide-react'
import { Controller, useForm, useWatch, type Control } from 'react-hook-form'
import { PatternFormat } from 'react-number-format'
import { z } from 'zod'
import { Currency } from '@repo/ui/components/currency'
import { Button } from '@repo/ui/components/ui/button'
import { Kbd } from '@repo/ui/components/ui/kbd'
import { Label } from '@repo/ui/components/ui/label'
import { RadioGroup, RadioGroupItem } from '@repo/ui/components/ui/radio-group'
import { Separator } from '@repo/ui/components/ui/separator'
import { Input } from '@repo/ui/components/ui/input'
import { Card, CardContent } from '@repo/ui/components/ui/card'
import {
HoverCard,
HoverCardContent,
HoverCardTrigger
} from '@repo/ui/components/ui/hover-card'
import {
Field,
FieldError,
@@ -25,18 +16,27 @@ import {
FieldLabel,
FieldSet
} from '@repo/ui/components/ui/field'
import {
HoverCard,
HoverCardContent,
HoverCardTrigger
} from '@repo/ui/components/ui/hover-card'
import { Input } from '@repo/ui/components/ui/input'
import { Kbd } from '@repo/ui/components/ui/kbd'
import { Label } from '@repo/ui/components/ui/label'
import {
NativeSelect,
NativeSelectOption
} from '@repo/ui/components/ui/native-select'
import { Currency } from '@repo/ui/components/currency'
import { RadioGroup, RadioGroupItem } from '@repo/ui/components/ui/radio-group'
import { Separator } from '@repo/ui/components/ui/separator'
import { useWizard } from '@/components/wizard'
import { isName } from '../_.$orgid.users.add/data'
import { applyDiscount } from './discount'
import { useWizardStore } from './store'
const creditCard = z.object({
export const creditCardSchema = z.object({
holder_name: z
.string()
.trim()
@@ -71,7 +71,7 @@ const formSchema = z.discriminatedUnion(
z.object({
payment_method: z.literal('CREDIT_CARD'),
credit_card: creditCard,
credit_card: creditCardSchema,
installments: z.coerce.number().int().min(1).max(12)
})
],
@@ -80,7 +80,7 @@ const formSchema = z.discriminatedUnion(
type Schema = z.input<typeof formSchema>
export type CreditCard = z.infer<typeof creditCard>
export type CreditCard = z.infer<typeof creditCardSchema>
export function Payment({}) {
const wizard = useWizard()
@@ -95,6 +95,7 @@ export function Payment({}) {
resolver: zodResolver(formSchema)
})
const paymentMethod = useWatch({ control, name: 'payment_method' })
const total = subtotal + discount
const onSubmit = async ({ payment_method, ...data }: Schema) => {
if (payment_method === 'CREDIT_CARD') {
@@ -156,191 +157,9 @@ export function Payment({}) {
/>
{paymentMethod === 'CREDIT_CARD' ? (
<CreditCard control={control} total={subtotal + discount} />
) : null}
<Separator />
<div className="flex justify-between gap-4 *:cursor-pointer">
<Button
type="button"
variant="link"
className="text-black dark:text-white"
onClick={() => wizard('cart')}
tabIndex={-1}
>
Voltar
</Button>
<Button type="submit" variant="secondary">
Continuar <ArrowRightIcon />
</Button>
</div>
</form>
)
}
export function CreditCard({
total,
control
}: {
total: number
control: Control<Schema>
}) {
const currentYear = new Date().getFullYear()
const years = Array.from({ length: 10 }, (_, i) => currentYear + i)
return (
<Card className="lg:w-1/2">
<CardContent>
<FieldGroup>
<FieldSet>
{/* Credir card number */}
<Controller
control={control}
defaultValue=""
name="credit_card.number"
render={({ field: { onChange, ref, ...field }, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Número do cartão</FieldLabel>
<PatternFormat
id={field.name}
format="#### #### #### ####"
mask="_"
placeholder="•••• •••• •••• ••••"
customInput={Input}
getInputRef={ref}
aria-invalid={fieldState.invalid}
onValueChange={({ value }) => {
onChange(value)
}}
{...field}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
{/* Holder name */}
<Controller
control={control}
name="credit_card.holder_name"
defaultValue=""
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Nome do titular</FieldLabel>
<Input
id={field.name}
aria-invalid={fieldState.invalid}
{...field}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
<FieldSet className="grid grid-cols-3 gap-4">
<Controller
control={control}
name="credit_card.exp_month"
defaultValue=""
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Mês</FieldLabel>
<NativeSelect
id={field.name}
value={field.value}
onChange={field.onChange}
aria-invalid={fieldState.invalid}
>
<NativeSelectOption value="" disabled>
Selecione
</NativeSelectOption>
{Array.from({ length: 12 }, (_, i) => {
const v = String(i + 1).padStart(2, '0')
return (
<NativeSelectOption key={v} value={v}>
{v}
</NativeSelectOption>
)
})}
</NativeSelect>
</Field>
)}
/>
<Controller
control={control}
name="credit_card.exp_year"
defaultValue=""
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Ano</FieldLabel>
<NativeSelect
id={field.name}
value={field.value}
onChange={field.onChange}
aria-invalid={fieldState.invalid}
>
<NativeSelectOption value="" disabled>
Selecione
</NativeSelectOption>
{years.map((year) => (
<NativeSelectOption key={year} value={String(year)}>
{year}
</NativeSelectOption>
))}
</NativeSelect>
</Field>
)}
/>
<Controller
control={control}
name="credit_card.cvv"
defaultValue=""
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>
CVC
<HoverCard openDelay={0}>
<HoverCardTrigger asChild>
<button type="button" tabIndex={-1}>
<CircleQuestionMarkIcon className="size-4 text-muted-foreground" />
</button>
</HoverCardTrigger>
<HoverCardContent
align="end"
className="text-sm space-y-1.5 lg:w-78"
>
<p>
O <Kbd>CVC</Kbd> é o código de segurança do cartão
de crédito.
</p>
<p>
Ele fica no verso do cartão e geralmente possui{' '}
<Kbd>3 dígitos</Kbd> (ou <Kbd>4 dígitos</Kbd> na
frente, no caso do American Express).
</p>
</HoverCardContent>
</HoverCard>
</FieldLabel>
<Input
id={field.name}
aria-invalid={fieldState.invalid}
{...field}
/>
</Field>
)}
/>
</FieldSet>
<Card className="lg:w-1/2">
<CardContent className="space-y-6">
<CreditCard control={control} />
<Controller
control={control}
@@ -384,10 +203,182 @@ export function CreditCard({
</Field>
)}
/>
</FieldSet>
</FieldGroup>
</CardContent>
</Card>
</CardContent>
</Card>
) : null}
<Separator />
<div className="flex justify-between gap-4 *:cursor-pointer">
<Button
type="button"
variant="link"
className="text-black dark:text-white"
onClick={() => wizard('cart')}
tabIndex={-1}
>
Voltar
</Button>
<Button type="submit" variant="secondary">
Continuar <ArrowRightIcon />
</Button>
</div>
</form>
)
}
export function CreditCard({ control }: { control: Control<Schema> }) {
const currentYear = new Date().getFullYear()
const years = Array.from({ length: 10 }, (_, i) => currentYear + i)
return (
<FieldGroup>
<FieldSet>
{/* Credir card number */}
<Controller
control={control}
defaultValue=""
name="credit_card.number"
render={({ field: { onChange, ref, ...field }, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Número do cartão</FieldLabel>
<PatternFormat
id={field.name}
format="#### #### #### ####"
mask="_"
placeholder="•••• •••• •••• ••••"
customInput={Input}
getInputRef={ref}
aria-invalid={fieldState.invalid}
onValueChange={({ value }) => {
onChange(value)
}}
{...field}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
{/* Holder name */}
<Controller
control={control}
name="credit_card.holder_name"
defaultValue=""
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Nome do titular</FieldLabel>
<Input
id={field.name}
aria-invalid={fieldState.invalid}
{...field}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
<FieldSet className="grid grid-cols-3 gap-4">
<Controller
control={control}
name="credit_card.exp_month"
defaultValue=""
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Mês</FieldLabel>
<NativeSelect
id={field.name}
value={field.value}
onChange={field.onChange}
aria-invalid={fieldState.invalid}
>
<NativeSelectOption value="" disabled>
Selecione
</NativeSelectOption>
{Array.from({ length: 12 }, (_, i) => {
const v = String(i + 1).padStart(2, '0')
return (
<NativeSelectOption key={v} value={v}>
{v}
</NativeSelectOption>
)
})}
</NativeSelect>
</Field>
)}
/>
<Controller
control={control}
name="credit_card.exp_year"
defaultValue=""
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Ano</FieldLabel>
<NativeSelect
id={field.name}
value={field.value}
onChange={field.onChange}
aria-invalid={fieldState.invalid}
>
<NativeSelectOption value="" disabled>
Selecione
</NativeSelectOption>
{years.map((year) => (
<NativeSelectOption key={year} value={String(year)}>
{year}
</NativeSelectOption>
))}
</NativeSelect>
</Field>
)}
/>
<Controller
control={control}
name="credit_card.cvv"
defaultValue=""
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>
CVC
<HoverCard openDelay={0}>
<HoverCardTrigger asChild>
<button type="button" tabIndex={-1}>
<CircleQuestionMarkIcon className="size-4 text-muted-foreground" />
</button>
</HoverCardTrigger>
<HoverCardContent
align="end"
className="text-sm space-y-1.5 lg:w-78"
>
<p>
O <Kbd>CVC</Kbd> é o código de segurança do cartão de
crédito.
</p>
<p>
Ele fica no verso do cartão e geralmente possui{' '}
<Kbd>3 dígitos</Kbd> (ou <Kbd>4 dígitos</Kbd> na frente,
no caso do American Express).
</p>
</HoverCardContent>
</HoverCard>
</FieldLabel>
<Input
id={field.name}
aria-invalid={fieldState.invalid}
{...field}
/>
</Field>
)}
/>
</FieldSet>
</FieldSet>
</FieldGroup>
)
}