Files
saladeaula.digital/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/route.tsx

274 lines
8.1 KiB
TypeScript

import type { Route } from './+types/route'
import { useMount } from 'ahooks'
import {
BookSearchIcon,
CircleCheckBigIcon,
MegaphoneIcon,
PlusIcon,
WalletIcon
} from 'lucide-react'
import { use, useEffect, useState } from 'react'
import { Link, NavLink, redirect, useFetcher } from 'react-router'
import { cloudflareContext, userContext } from '@repo/auth/context'
import { Skeleton } from '@repo/ui/components/skeleton'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator
} from '@repo/ui/components/ui/breadcrumb'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle
} from '@repo/ui/components/ui/card'
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle
} from '@repo/ui/components/ui/item'
import { Label } from '@repo/ui/components/ui/label'
import { Switch } from '@repo/ui/components/ui/switch'
import { createSearch } from '@repo/util/meili'
import { HttpMethod, request as req } from '@repo/util/request'
import { Step, StepItem, StepSeparator } from '@/components/step'
import { Wizard, WizardStep } from '@/components/wizard'
import { useWorksapce } from '@/components/workspace-switcher'
import { INTERNAL_EMAIL_DOMAIN } from '@/conf'
import { workspaceContext } from '@/middleware/workspace'
import { Button } from '@repo/ui/components/ui/button'
import { Spinner } from '@repo/ui/components/ui/spinner'
import type { Course } from '../_.$orgid.enrollments.add/data'
import { Assigned } from './assigned'
import { Bulk } from './bulk'
import { Payment } from './payment'
import { Review } from './review'
import { useWizardStore } from './store'
export function meta({}: Route.MetaArgs) {
return [{ title: 'Comprar matrículas' }]
}
export async function loader({ context, params, request }: Route.LoaderArgs) {
const { subscription } = context.get(workspaceContext)
// If there's subscription for the org, redirect it
if (subscription) {
throw redirect('../enrollments/add')
}
const cloudflare = context.get(cloudflareContext)
const courses = createSearch<Course>({
index: 'saladeaula_courses',
sort: ['created_at:desc'],
filter: 'unlisted = false',
hitsPerPage: 100,
env: cloudflare.env
})
const seats = req({
url: `/orgs/${params.orgid}/seats`,
request,
context
}).then((r) => r.json() as any)
return { courses, seats }
}
export async function action({ request, context }: Route.ActionArgs) {
const body = (await request.json()) as object
const user = context.get(userContext)!
const { activeWorkspace } = context.get(workspaceContext)
const { id: org_id, name, cnpj } = activeWorkspace
const r = await req({
url: '/orders',
headers: new Headers({ 'Content-Type': 'application/json' }),
method: HttpMethod.POST,
body: JSON.stringify({
org_id,
name,
cnpj,
email: `org+${cnpj}@${INTERNAL_EMAIL_DOMAIN}`,
created_by: { id: user.sub, name: user.name },
...body
}),
request,
context
})
if (!r.ok) {
const error = await r.json().catch(() => ({}))
return { ok: false, error }
}
const { id } = (await r.json()) as { id: string }
throw redirect(`../payments/${id}`)
}
export default function Route({
loaderData: { courses, seats: seats_ }
}: Route.ComponentProps) {
const seats = use(seats_)
const fetcher = useFetcher()
const [mounted, setMounted] = useState(false)
const { address } = useWorksapce()
const { index, kind, setIndex, setKind, reset, update, ...state } =
useWizardStore()
const onSubmit = async () => {
const items = state.items.map(({ course, quantity }) => ({
...course,
quantity
}))
await fetcher.submit(JSON.stringify({ ...state, items }), {
method: 'post',
encType: 'application/json'
})
// reset()
}
useMount(() => {
setMounted(true)
})
useEffect(() => {
if (address) {
update({ address })
}
}, [address])
if (!mounted) {
return <Skeleton />
}
return (
<div className="space-y-2.5">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link to="../enrollments">Matrículas</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Comprar matrículas</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="lg:max-w-4xl space-y-4 mx-auto">
{seats?.items?.length > 0 && (
<Item
variant="outline"
className="bg-green-500/15 border-green-600/60 rounded-2xl"
>
<ItemMedia
className="bg-green-500/30 border-green-600/50"
variant="icon"
>
<MegaphoneIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Matrículas em aberto</ItemTitle>
<ItemDescription>
Existem matrículas em aberto de cursos adquiridos e
disponíveis para uso.
</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm" variant="outline" asChild>
<NavLink to="../enrollments/seats">
{({ isPending }) => (
<>{isPending ? <Spinner /> : <PlusIcon />} Matricular</>
)}
</NavLink>
</Button>
</ItemActions>
</Item>
)}
<Card>
<CardHeader>
<CardTitle className="text-2xl">Comprar matrículas</CardTitle>
<CardDescription>
Siga os passos abaixo para comprar novas matrículas.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Step className="mb-6" activeIndex={index}>
<StepItem index={0} icon={BookSearchIcon}>
Escolher cursos
</StepItem>
<StepSeparator />
<StepItem index={1} icon={WalletIcon}>
Pagamento
</StepItem>
<StepSeparator />
<StepItem index={2} icon={CircleCheckBigIcon}>
Revisão &amp; confirmação
</StepItem>
</Step>
<Wizard index={index} onChange={setIndex}>
{/* Cart */}
<WizardStep name="cart">
<Label
className="flex flex-row items-center justify-between cursor-pointer
bg-accent/50 hover:bg-accent rounded-lg border p-4
dark:has-aria-checked:border-blue-900
dark:has-aria-checked:bg-blue-950"
>
<div className="grid gap-1.5 font-normal">
<p className="text-sm leading-none font-medium">
Adicionar colaboradores
</p>
<p className="text-muted-foreground text-sm">
Você pode adicionar agora os colaboradores que irão fazer
o curso ou deixar isso para depois.
</p>
</div>
{/* Toggle button x*/}
<Switch
checked={kind === 'assigned'}
onCheckedChange={(checked) =>
setKind(checked ? 'assigned' : 'bulk')
}
className="cursor-pointer"
/>
</Label>
{kind === 'assigned' ? (
<Assigned courses={courses} />
) : (
<Bulk courses={courses} />
)}
</WizardStep>
{/* Payment */}
<WizardStep name="payment">
<Payment />
</WizardStep>
{/* Review */}
<WizardStep name="review">
<Review onSubmit={onSubmit} />
</WizardStep>
</Wizard>
</CardContent>
</Card>
</div>
</div>
)
}