add scorm
This commit is contained in:
@@ -53,7 +53,7 @@ export async function loader({ context, request, params }: Route.LoaderArgs) {
|
||||
}).then((r) => r.json())
|
||||
|
||||
return {
|
||||
data: Promise.all([users, new Promise((r) => setTimeout(r, 5000))])
|
||||
data: users
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function Route({ loaderData: { data } }) {
|
||||
|
||||
<Suspense fallback={<Skeleton />}>
|
||||
<Await resolve={data}>
|
||||
{([{ items }, _]) => {
|
||||
{({ items }) => {
|
||||
return (
|
||||
<div className="grid gap-4 lg:gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{items.map(({ sk, name, email }: Admin) => {
|
||||
|
||||
@@ -6,6 +6,12 @@ import { useForm } from 'react-hook-form'
|
||||
import { PatternFormat } from 'react-number-format'
|
||||
import { Link, useFetcher } from 'react-router'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
adjectives,
|
||||
colors,
|
||||
NumberDictionary,
|
||||
uniqueNamesGenerator
|
||||
} from 'unique-names-generator'
|
||||
import { z } from 'zod'
|
||||
|
||||
import {
|
||||
@@ -34,7 +40,6 @@ import {
|
||||
FormMessage
|
||||
} from '@repo/ui/components/ui/form'
|
||||
import { Input } from '@repo/ui/components/ui/input'
|
||||
import { Label } from '@repo/ui/components/ui/label'
|
||||
import { Spinner } from '@repo/ui/components/ui/spinner'
|
||||
|
||||
import { useWorksapce } from '@/components/workspace-switcher'
|
||||
@@ -49,10 +54,11 @@ export const formSchema = z.object({
|
||||
.trim()
|
||||
.nonempty('Digite um nome')
|
||||
.refine(isName, { message: 'Nome inválido' }),
|
||||
email: z.email('Email inválido').trim().toLowerCase(),
|
||||
email: z.email('Email inválido').trim().toLowerCase().optional(),
|
||||
cpf: z
|
||||
.string('CPF obrigatório')
|
||||
.refine(isValidCPF, { message: 'CPF inválido' })
|
||||
.refine(isValidCPF, { message: 'CPF inválido' }),
|
||||
given_email: z.coerce.boolean()
|
||||
})
|
||||
|
||||
export type Schema = z.infer<typeof formSchema>
|
||||
@@ -87,7 +93,8 @@ export default function Route() {
|
||||
const form = useForm({
|
||||
resolver: zodResolver(formSchema)
|
||||
})
|
||||
const { handleSubmit, control, formState, reset } = form
|
||||
const { handleSubmit, control, formState, reset, watch } = form
|
||||
const givenEmail = watch('given_email') as boolean
|
||||
|
||||
const onSubmit = async (user: Schema) => {
|
||||
await fetcher.submit(JSON.stringify({ user, org: activeWorkspace }), {
|
||||
@@ -108,6 +115,8 @@ export default function Route() {
|
||||
}
|
||||
}, [fetcher.data])
|
||||
|
||||
// console.log(randomEmail())
|
||||
|
||||
return (
|
||||
<div className="space-y-2.5">
|
||||
<Breadcrumb>
|
||||
@@ -157,6 +166,7 @@ export default function Route() {
|
||||
<FormField
|
||||
control={control}
|
||||
name="email"
|
||||
disabled={givenEmail}
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
@@ -168,12 +178,27 @@ export default function Route() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox id="terms" tabIndex={-1} />
|
||||
<Label htmlFor="terms">
|
||||
|
||||
<FormField
|
||||
control={control}
|
||||
name="given_email"
|
||||
defaultValue={false}
|
||||
render={({ field: { value, onChange, ...field } }) => (
|
||||
<FormItem className="flex items-center gap-2">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={Boolean(value)}
|
||||
onCheckedChange={onChange}
|
||||
tabIndex={-1}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="cursor-pointer">
|
||||
Usar um email fornecido pela plataforma.
|
||||
</Label>
|
||||
</div>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
@@ -219,3 +244,14 @@ export default function Route() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function randomEmail() {
|
||||
const numberDict = NumberDictionary.generate({ min: 100, max: 999 })
|
||||
const randomName: string = uniqueNamesGenerator({
|
||||
dictionaries: [adjectives, colors, numberDict],
|
||||
length: 3,
|
||||
separator: '-'
|
||||
})
|
||||
|
||||
return `${randomName}@users.noreply.saladeaula.digital`
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router": "^7.9.5",
|
||||
"unique-names-generator": "^4.7.1",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
|
||||
14
apps/saladeaula.digital/app/components/container.tsx
Normal file
14
apps/saladeaula.digital/app/components/container.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { cn } from '@repo/ui/lib/utils'
|
||||
|
||||
type ContainerProps = {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Container({ children, className }: ContainerProps) {
|
||||
return (
|
||||
<main>
|
||||
<div className={cn('container mx-auto', className)}>{children}</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function initials(s: string): string {
|
||||
const initials = s
|
||||
.split(' ')
|
||||
.map((word) => word.charAt(0).toUpperCase()) as string[]
|
||||
|
||||
if (initials.length == 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const first = initials[0]
|
||||
const last = initials[initials.length - 1]
|
||||
return first + last
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export const middleware: Route.MiddlewareFunction[] = [loggingMiddleware]
|
||||
|
||||
export function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="pt-br" suppressHydrationWarning>
|
||||
<html lang="pt-br" className="h-full" suppressHydrationWarning>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
@@ -26,7 +26,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body>
|
||||
<body className="h-full">
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
|
||||
@@ -11,13 +11,15 @@ import {
|
||||
BreadcrumbSeparator
|
||||
} from '@repo/ui/components/ui/breadcrumb'
|
||||
|
||||
import { Container } from '@/components/container'
|
||||
|
||||
export function meta({}: Route.MetaArgs) {
|
||||
return [{ title: 'Certificados' }]
|
||||
}
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<div className="space-y-2.5">
|
||||
<Container className="space-y-2.5">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
@@ -39,6 +41,6 @@ export default function Component() {
|
||||
acompanhe seus cursos concluídos.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import { Await, useSearchParams } from 'react-router'
|
||||
import placeholder from '@/assets/placeholder.webp'
|
||||
import { createSearch } from '@/lib/meili'
|
||||
|
||||
import { Container } from '@/components/container'
|
||||
import type { User } from '@repo/auth/auth'
|
||||
import { userContext } from '@repo/auth/context'
|
||||
import { FacetedFilter } from '@repo/ui/components/faceted-filter'
|
||||
@@ -102,7 +103,7 @@ export default function Component({
|
||||
const term = searchParams.get('term') as string
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Container className="space-y-4">
|
||||
<div className="space-y-0.5 mb-8">
|
||||
<h1 className="text-2xl font-bold tracking-tight">Meus cursos</h1>
|
||||
<p className="text-muted-foreground">
|
||||
@@ -164,7 +165,7 @@ export default function Component({
|
||||
</Await>
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function Component({ loaderData }: Route.ComponentProps) {
|
||||
const [isOpen, { toggle }] = useToggle()
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col flex-1 min-w-0">
|
||||
<div className="relative flex flex-col flex-1 min-w-0 h-full">
|
||||
<header
|
||||
className="bg-background/15 backdrop-blur-sm
|
||||
px-4 py-2 lg:py-4 sticky top-0 z-5"
|
||||
@@ -118,11 +118,7 @@ export default function Component({ loaderData }: Route.ComponentProps) {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="p-4">
|
||||
<div className="container mx-auto">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,13 +11,15 @@ import {
|
||||
BreadcrumbSeparator
|
||||
} from '@repo/ui/components/ui/breadcrumb'
|
||||
|
||||
import { Container } from '@/components/container'
|
||||
|
||||
export function meta({}: Route.MetaArgs) {
|
||||
return [{ title: 'Histórico de compras' }]
|
||||
}
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<div className="space-y-2.5">
|
||||
<Container className="space-y-2.5">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
@@ -41,6 +43,6 @@ export default function Component() {
|
||||
o controle financeiro.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,16 +4,18 @@ import { ScormPlayer } from '@/components/scorm-player'
|
||||
import { useLocalStorage } from '@/hooks/useLocalStorage'
|
||||
import SHA256 from 'crypto-js/sha256'
|
||||
|
||||
import { data } from './index'
|
||||
|
||||
export function meta({ params }: Route.MetaArgs) {
|
||||
const course = data.find((course) => course.id === params.course)
|
||||
|
||||
return [{ title: course.courseName }]
|
||||
return [{ title: '' }]
|
||||
}
|
||||
|
||||
export default function Home({ params }: Route.ComponentProps) {
|
||||
const course = data.find((course) => course.id === params.course)
|
||||
export default function Route({ params }: Route.ComponentProps) {
|
||||
const course = {
|
||||
id: 'fbad867a-0022-4605-814f-db8ebe2b17fb',
|
||||
courseName: 'All Golf',
|
||||
scormContentPath:
|
||||
'nr-33-espacos-confinados-conteudo-de-demonstracao-scorm12/scormdriver/indexAPI.html'
|
||||
}
|
||||
// const course = data.find((course) => course.id === params.course)
|
||||
const hash = SHA256(course.scormContentPath).toString()
|
||||
const [scormState] = useLocalStorage(`scormState.${hash}`, {})
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Link } from 'react-router'
|
||||
|
||||
import { request as req } from '@/lib/request'
|
||||
|
||||
import { Container } from '@/components/container'
|
||||
import type { User } from '@repo/auth/auth'
|
||||
import { userContext } from '@repo/auth/context'
|
||||
import {
|
||||
@@ -48,7 +49,7 @@ export async function loader({ context, request }: Route.ActionArgs) {
|
||||
return { user: await r.json() }
|
||||
}
|
||||
|
||||
export default function Component({ loaderData: { user } }) {
|
||||
export default function Route({ loaderData: { user } }) {
|
||||
const form = useForm({ defaultValues: user })
|
||||
const { handleSubmit, control, formState } = form
|
||||
|
||||
@@ -57,7 +58,7 @@ export default function Component({ loaderData: { user } }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2.5">
|
||||
<Container className="space-y-2.5">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
@@ -144,7 +145,7 @@ export default function Component({ loaderData: { user } }) {
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-lime-400 cursor-pointer"
|
||||
className="cursor-pointer"
|
||||
disabled={formState.isSubmitting}
|
||||
>
|
||||
{formState.isSubmitting && <Spinner />}
|
||||
@@ -154,6 +155,6 @@ export default function Component({ loaderData: { user } }) {
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -41,6 +41,7 @@
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router": "^7.9.5",
|
||||
"unique-names-generator": "^4.7.1",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
@@ -7247,6 +7248,15 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unique-names-generator": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.7.1.tgz",
|
||||
"integrity": "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
|
||||
@@ -17,7 +17,7 @@ Globals:
|
||||
Architectures:
|
||||
- x86_64
|
||||
Layers:
|
||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:79
|
||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:100
|
||||
Environment:
|
||||
Variables:
|
||||
TZ: America/Sao_Paulo
|
||||
@@ -110,7 +110,7 @@ Resources:
|
||||
InvocationType: RequestResponse
|
||||
- S3Action:
|
||||
BucketName: !Ref BucketName
|
||||
ObjectKeyPrefix: "mailbox"
|
||||
ObjectKeyPrefix: 'mailbox'
|
||||
ScanEnabled: true
|
||||
|
||||
EventAddTenantFunction:
|
||||
|
||||
Reference in New Issue
Block a user