Commit d389de98 authored by Wellton Quirino's avatar Wellton Quirino

add new modules and areas

parent 1e2e278b
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.13.0", "@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0", "@emotion/styled": "^11.13.0",
"@hookform/resolvers": "^3.9.0", "@hookform/resolvers": "^3.10.0",
"@mui/material": "^5.16.4", "@mui/material": "^5.16.4",
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.1.2",
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
"lucide-react": "^0.383.0", "lucide-react": "^0.383.0",
"next": "14.2.3", "next": "14.2.3",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"nookies": "^2.5.2",
"react": "^18", "react": "^18",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18", "react-dom": "^18",
...@@ -44,6 +45,7 @@ ...@@ -44,6 +45,7 @@
}, },
"devDependencies": { "devDependencies": {
"@rocketseat/eslint-config": "^2.2.2", "@rocketseat/eslint-config": "^2.2.2",
"@types/cookie": "^0.6.0",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
...@@ -543,9 +545,10 @@ ...@@ -543,9 +545,10 @@
"integrity": "sha512-XGndio0l5/Gvd6CLIABvsav9HHezgDFFhDfHk1bvLfr9ni8dojqLSvBbotJEjmIwNHL7vK4QzBJTdBRoB+c1ww==" "integrity": "sha512-XGndio0l5/Gvd6CLIABvsav9HHezgDFFhDfHk1bvLfr9ni8dojqLSvBbotJEjmIwNHL7vK4QzBJTdBRoB+c1ww=="
}, },
"node_modules/@hookform/resolvers": { "node_modules/@hookform/resolvers": {
"version": "3.9.0", "version": "3.10.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
"integrity": "sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==", "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==",
"license": "MIT",
"peerDependencies": { "peerDependencies": {
"react-hook-form": "^7.0.0" "react-hook-form": "^7.0.0"
} }
...@@ -2273,6 +2276,13 @@ ...@@ -2273,6 +2276,13 @@
"react": "^18 || ^19" "react": "^18 || ^19"
} }
}, },
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
...@@ -3162,6 +3172,15 @@ ...@@ -3162,6 +3172,15 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
}, },
"node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cosmiconfig": { "node_modules/cosmiconfig": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
...@@ -5571,6 +5590,16 @@ ...@@ -5571,6 +5590,16 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/nookies": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/nookies/-/nookies-2.5.2.tgz",
"integrity": "sha512-x0TRSaosAEonNKyCrShoUaJ5rrT5KHRNZ5DwPCuizjgrnkpE5DRf3VL7AyyQin4htict92X1EQ7ejDbaHDVdYA==",
"license": "MIT",
"dependencies": {
"cookie": "^0.4.1",
"set-cookie-parser": "^2.4.6"
}
},
"node_modules/normalize-path": { "node_modules/normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
...@@ -6480,6 +6509,12 @@ ...@@ -6480,6 +6509,12 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/set-function-length": { "node_modules/set-function-length": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.13.0", "@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0", "@emotion/styled": "^11.13.0",
"@hookform/resolvers": "^3.9.0", "@hookform/resolvers": "^3.10.0",
"@mui/material": "^5.16.4", "@mui/material": "^5.16.4",
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.1.2",
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
"lucide-react": "^0.383.0", "lucide-react": "^0.383.0",
"next": "14.2.3", "next": "14.2.3",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"nookies": "^2.5.2",
"react": "^18", "react": "^18",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18", "react-dom": "^18",
...@@ -45,6 +46,7 @@ ...@@ -45,6 +46,7 @@
}, },
"devDependencies": { "devDependencies": {
"@rocketseat/eslint-config": "^2.2.2", "@rocketseat/eslint-config": "^2.2.2",
"@types/cookie": "^0.6.0",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
......
import { api } from '@/lib/axios'
export type AccountsProps = {
id: string
name: string
email: string
}
export async function getAccounts() {
const accounts = await api.get<AccountsProps[]>('/accounts')
return accounts
}
export async function getAccountsId(id: string) {
const account = await api.get<AccountsProps>(`/accounts/${id}`)
return account
}
import { api } from '@/lib/axios' import { api } from '@/lib/axios'
interface FileBanner { // interface FileBanner {
name: string // name: string
} // }
export type AreasProps = { export type AreasProps = {
id: string id?: string
name: string name: string
desktopBanner?: { [key: string]: FileBanner } desktopBanner: string
mobileBanner?: { [key: string]: FileBanner } mobileBanner: string
// desktopBanner?: { [key: string]: FileBanner }
// mobileBanner?: { [key: string]: FileBanner }
} }
export async function createAreas({ export async function createAreas({
...@@ -47,8 +49,7 @@ export async function getAreasId(id: string) { ...@@ -47,8 +49,7 @@ export async function getAreasId(id: string) {
return area return area
} }
export async function deleteArea(id: string) { export async function deleteArea(id: string) {
const area = await api.delete(`/areas/${id}`) const area = await api.delete(`/areas/${id}`)
return area return area
} }
\ No newline at end of file
import { api } from '@/lib/axios'
export type SignInProps = {
email: string
password: string
}
export type SignInResponse = {
access_token: string
user: {
id: string
name: string
email: string
}
}
export async function login(data: SignInProps) {
const token = await api.post<SignInResponse>('/login', data)
return token
}
import { api } from '@/lib/axios'
export type ModulesProps = {
id: string
name: string
description: string
}
export async function createModules({ name, description }: ModulesProps) {
const modules = await api.post<ModulesProps>('/modules', {
name,
description,
})
return modules
}
export async function editModule({ id, name, description }: ModulesProps) {
const modules = await api.patch<ModulesProps[]>(`/modules/${id}`, {
name,
description,
})
return modules
}
export async function getModules() {
const modules = await api.get<ModulesProps[]>('/modules')
return modules
}
export async function getModulesId(id: string) {
const modules = await api.get<ModulesProps>(`/modules/${id}`)
return modules
}
export async function deleteModule(id: string) {
const modules = await api.delete(`/modules/${id}`)
return modules
}
'use client'
import { Search } from 'lucide-react' import { Search } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
...@@ -12,8 +14,11 @@ import { SignUp } from '@/components/sign-up' ...@@ -12,8 +14,11 @@ import { SignUp } from '@/components/sign-up'
import { SkeletonSerachParams } from '@/components/skeleton-serach-params' import { SkeletonSerachParams } from '@/components/skeleton-serach-params'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Suspense } from 'react' import { Suspense } from 'react'
import { useForm } from 'react-hook-form'
export default function Home() { export default function Home() {
const { register } = useForm()
return ( return (
<> <>
<div className="absolute h-[200px] w-full bg-gradient-to-b from-gray-900 from-5% z-10" /> <div className="absolute h-[200px] w-full bg-gradient-to-b from-gray-900 from-5% z-10" />
...@@ -31,6 +36,8 @@ export default function Home() { ...@@ -31,6 +36,8 @@ export default function Home() {
type="text" type="text"
className="w-full" className="w-full"
themeColor="#fafafa" themeColor="#fafafa"
name="learned-today"
register={register}
/> />
<Search size={24} /> <Search size={24} />
</div> </div>
......
...@@ -16,8 +16,8 @@ export default function Area() { ...@@ -16,8 +16,8 @@ export default function Area() {
const router = useRouter() const router = useRouter()
const data = watch() const data = watch()
const valueDesktopBanner = data?.desktopBanner?.['0']?.name
const valueMobileBanner = data?.mobileBanner?.['0']?.name const valueMobileBanner = data?.mobileBanner?.['0']?.name
const valueDesktopBanner = data?.desktopBanner?.['0']?.name
const queryClient = useQueryClient() const queryClient = useQueryClient()
...@@ -31,7 +31,24 @@ export default function Area() { ...@@ -31,7 +31,24 @@ export default function Area() {
}) })
function onSubmit(data: AreasProps) { function onSubmit(data: AreasProps) {
mutation.mutate(data) // console.log('🚀 ~ onSubmit ~ data:', data)
// const fileMobile = data.mobileBanner?.[0]
// const fileDesktop = data.desktopBanner?.[0]
// const formData = new FormData()
// formData.append('mobileBanner', fileMobile, fileMobile?.name)
// formData.append('desktopBanner', fileDesktop, fileDesktop?.name)
// console.log('🚀 ~ onSubmit ~ formData:', formData)
const dataArea = {
...data,
mobileBanner:
'/Users/wellton/Documents/www/sevenpro-frontend/public/images/banners/negocios-mobile.jpg',
desktopBanner:
'/Users/wellton/Documents/www/sevenpro-frontend/public/images/banners/negocios.jpg',
}
console.log('🚀 ~ onSubmit ~ data:', dataArea)
mutation.mutate(dataArea)
} }
return ( return (
...@@ -58,7 +75,7 @@ export default function Area() { ...@@ -58,7 +75,7 @@ export default function Area() {
<div className="flex flex-col flex-1 mt-6"> <div className="flex flex-col flex-1 mt-6">
<h6 className="text-xl text-purple-100">Banner Desktop</h6> <h6 className="text-xl text-purple-100">Banner Desktop</h6>
<span className="mt-4">Dimensões recomendadas: 1512 x 300</span> <span className="mt-4">Dimensões recomendadas: 1512 x 300</span>
<span>Formatos aceitos: .png, .jpg</span> <span>Formatos aceitos: .png, .jpg, .jpeg</span>
<InputFile <InputFile
id="desktopBanner" id="desktopBanner"
...@@ -70,7 +87,7 @@ export default function Area() { ...@@ -70,7 +87,7 @@ export default function Area() {
<div className="flex flex-col flex-1 mt-6"> <div className="flex flex-col flex-1 mt-6">
<h6 className="text-xl text-purple-100">Banner Mobile</h6> <h6 className="text-xl text-purple-100">Banner Mobile</h6>
<span className="mt-4">Dimensões recomendadas: 390 x 300</span> <span className="mt-4">Dimensões recomendadas: 390 x 300</span>
<span>Formatos aceitos: .png, .jpg</span> <span>Formatos aceitos: .png, .jpg, .jpeg</span>
<InputFile <InputFile
id="mobileBanner" id="mobileBanner"
values={valueMobileBanner} values={valueMobileBanner}
......
...@@ -16,6 +16,7 @@ import { MenuItem } from '@mui/material' ...@@ -16,6 +16,7 @@ import { MenuItem } from '@mui/material'
import { format } from 'date-fns' import { format } from 'date-fns'
import { CalendarIcon, PlusIcon, Trash } from 'lucide-react' import { CalendarIcon, PlusIcon, Trash } from 'lucide-react'
import { useState } from 'react' import { useState } from 'react'
import { useForm } from 'react-hook-form'
const options = [ const options = [
{ label: 'The Godfather', value: 1 }, { label: 'The Godfather', value: 1 },
...@@ -24,6 +25,7 @@ const options = [ ...@@ -24,6 +25,7 @@ const options = [
export default function EditCourse() { export default function EditCourse() {
const [date, setDate] = useState<Date>() const [date, setDate] = useState<Date>()
const { register } = useForm()
return ( return (
<section className="container py-10"> <section className="container py-10">
...@@ -55,6 +57,8 @@ export default function EditCourse() { ...@@ -55,6 +57,8 @@ export default function EditCourse() {
variant="standard" variant="standard"
type="text" type="text"
themeColor="#26AAA7" themeColor="#26AAA7"
name="name-course"
register={register}
/> />
<InputMui <InputMui
...@@ -63,6 +67,8 @@ export default function EditCourse() { ...@@ -63,6 +67,8 @@ export default function EditCourse() {
label="Área" label="Área"
select select
themeColor="#26AAA7" themeColor="#26AAA7"
name="area"
register={register}
> >
{options.map((option) => ( {options.map((option) => (
<MenuItem key={option.value} value={option.value}> <MenuItem key={option.value} value={option.value}>
...@@ -78,6 +84,8 @@ export default function EditCourse() { ...@@ -78,6 +84,8 @@ export default function EditCourse() {
multiline multiline
rows={4} rows={4}
themeColor="#26AAA7" themeColor="#26AAA7"
name="description-course"
register={register}
/> />
<InputMui <InputMui
...@@ -85,6 +93,8 @@ export default function EditCourse() { ...@@ -85,6 +93,8 @@ export default function EditCourse() {
variant="standard" variant="standard"
type="text" type="text"
themeColor="#26AAA7" themeColor="#26AAA7"
name="teachers"
register={register}
/> />
<div className="flex justify-between flex-wrap gap-6"> <div className="flex justify-between flex-wrap gap-6">
...@@ -207,6 +217,8 @@ export default function EditCourse() { ...@@ -207,6 +217,8 @@ export default function EditCourse() {
variant="standard" variant="standard"
type="text" type="text"
themeColor="#26AAA7" themeColor="#26AAA7"
name="title"
register={register}
/> />
<InputMui <InputMui
...@@ -216,6 +228,8 @@ export default function EditCourse() { ...@@ -216,6 +228,8 @@ export default function EditCourse() {
multiline multiline
rows={4} rows={4}
themeColor="#26AAA7" themeColor="#26AAA7"
name="description"
register={register}
/> />
</div> </div>
......
import { StyledInputs } from '@/components/mui/styled-inputs'
import { TextField } from '@mui/material'
import { useForm } from 'react-hook-form'
type FormModulesProps = {
index?: number
remove?: (index: number | number[]) => void
}
export function FormNewModules({ index, remove }: FormModulesProps) {
const { register } = useForm()
return (
<div className="flex flex-col p-4 gap-6 border border-gray-100">
<TextField
label="Título"
variant="standard"
type="text"
sx={StyledInputs({ color: '#26AAA7' })}
{...register(`newModule.${index}.name`)}
/>
<TextField
label="Descrição"
variant="standard"
type="text"
multiline
rows={4}
sx={StyledInputs({ color: '#26AAA7' })}
{...register(`newModule.${index}.description`)}
/>
</div>
)
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import { getAreas } from '@/api/areas' import { getAreas } from '@/api/areas'
import { getAudiences } from '@/api/audiences' import { getAudiences } from '@/api/audiences'
import { getCategories } from '@/api/categories' import { getCategories } from '@/api/categories'
import { createModules, getModules, ModulesProps } from '@/api/modules'
import InputFile from '@/components/input-file' import InputFile from '@/components/input-file'
import { LoadingSpinIcon } from '@/components/loading-spin-icon' import { LoadingSpinIcon } from '@/components/loading-spin-icon'
import { StyledInputs } from '@/components/mui/styled-inputs' import { StyledInputs } from '@/components/mui/styled-inputs'
...@@ -25,12 +26,14 @@ import { ...@@ -25,12 +26,14 @@ import {
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { zodResolver } from '@hookform/resolvers/zod'
import { Box, Chip, MenuItem, TextField } from '@mui/material' import { Box, Chip, MenuItem, TextField } from '@mui/material'
import { useQuery } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { format } from 'date-fns' import { format } from 'date-fns'
import { CalendarIcon, PlusIcon } from 'lucide-react' import { CalendarIcon, PlusIcon, Trash2 } from 'lucide-react'
import { useState } from 'react' import { useState } from 'react'
import { Controller, useForm } from 'react-hook-form' import { Controller, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { z } from 'zod' import { z } from 'zod'
// type CoursesProps = { // type CoursesProps = {
...@@ -50,8 +53,8 @@ import { z } from 'zod' ...@@ -50,8 +53,8 @@ import { z } from 'zod'
// } // }
const FormSchema = z.object({ const FormSchema = z.object({
name: z.string(), name: z.string().trim().min(3, { message: 'Título obrigatório' }),
description: z.string(), description: z.string().trim().min(3, { message: 'Descrição obrigatória' }),
desktopBanner: z.string(), desktopBanner: z.string(),
mobileBanner: z.string(), mobileBanner: z.string(),
duration: z.number(), duration: z.number(),
...@@ -70,15 +73,18 @@ export default function Curso() { ...@@ -70,15 +73,18 @@ export default function Curso() {
const [checkedEnd, setCheckedEnd] = useState<boolean>(false) const [checkedEnd, setCheckedEnd] = useState<boolean>(false)
const [dateStart, setDateStart] = useState<Date>() const [dateStart, setDateStart] = useState<Date>()
const [dateEnd, setDateEnd] = useState<Date>() const [dateEnd, setDateEnd] = useState<Date>()
const [inputValue, setInputValue] = useState<string>('') const [inputValue, setInputValue] = useState<string>('')
const queryClient = useQueryClient()
// const { form.register, watch, handleSubmit, control } = useForm<CoursesProps>() // const { form.register, watch, handleSubmit, control } = useForm<CoursesProps>()
const { register, handleSubmit, reset } = useForm<ModulesProps>()
const form = useForm<z.infer<typeof FormSchema>>({ const form = useForm<z.infer<typeof FormSchema>>({
// resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),
defaultValues: { defaultValues: {
areaId: '', areaId: '',
professors: [], professors: [],
moduleIds: [],
}, },
}) })
const values = form.watch() const values = form.watch()
...@@ -90,6 +96,11 @@ export default function Curso() { ...@@ -90,6 +96,11 @@ export default function Curso() {
queryFn: getAreas, queryFn: getAreas,
}) })
const { data: modules } = useQuery({
queryKey: ['modules'],
queryFn: getModules,
})
const { data: audiences, isLoading: audiencesLoading } = useQuery({ const { data: audiences, isLoading: audiencesLoading } = useQuery({
queryKey: ['audiences'], queryKey: ['audiences'],
queryFn: getAudiences, queryFn: getAudiences,
...@@ -100,15 +111,6 @@ export default function Curso() { ...@@ -100,15 +111,6 @@ export default function Curso() {
queryFn: getCategories, queryFn: getCategories,
}) })
function onSubmit(data: z.infer<typeof FormSchema>) {
const body = {
...data,
startDate: dateStart?.toLocaleDateString() || '',
endDate: dateEnd?.toLocaleDateString() || '',
}
console.log(body)
}
function onCheckedStart(checkedStart: boolean) { function onCheckedStart(checkedStart: boolean) {
setCheckedStart(checkedStart) setCheckedStart(checkedStart)
} }
...@@ -132,6 +134,55 @@ export default function Curso() { ...@@ -132,6 +134,55 @@ export default function Curso() {
) )
} }
const mutation = useMutation({
mutationFn: createModules,
onSuccess: () => {
toast.success('Módulo criado com sucesso!')
queryClient.invalidateQueries({ queryKey: ['modules'] })
},
})
async function addNewModuler(data: ModulesProps) {
const response = await mutation.mutateAsync(data)
const responseModuleId = response.data.id
form.setValue('moduleIds', [...selectedIds, responseModuleId])
reset()
}
const selectedIds = form.watch('moduleIds') || []
const selectedModules = modules?.data.filter((module) =>
selectedIds.includes(module.id),
)
const handleSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedId = event.target.value
if (selectedId && !selectedIds.includes(selectedId)) {
form.setValue('moduleIds', [...selectedIds, selectedId])
}
}
const handleDeleteModule = (idToRemove: string) => {
form.setValue(
'moduleIds',
selectedIds.filter((id) => id !== idToRemove),
)
}
function onSubmit(data: z.infer<typeof FormSchema>) {
const body = {
...data,
startDate: dateStart?.toLocaleDateString() || '',
endDate: dateEnd?.toLocaleDateString() || '',
}
console.log(body)
}
console.log('error: ', form.formState.errors)
return ( return (
<section className="container py-10"> <section className="container py-10">
<Form {...form}> <Form {...form}>
...@@ -155,6 +206,9 @@ export default function Curso() { ...@@ -155,6 +206,9 @@ export default function Curso() {
{...form.register('name')} {...form.register('name')}
sx={StyledInputs({ color: '#26AAA7' })} sx={StyledInputs({ color: '#26AAA7' })}
/> />
{form.formState.errors.name && (
<p>{form.formState.errors.name.message}</p>
)}
<TextField <TextField
type="text" type="text"
...@@ -185,14 +239,6 @@ export default function Curso() { ...@@ -185,14 +239,6 @@ export default function Curso() {
{...form.register('description')} {...form.register('description')}
/> />
{/* <TextField
label="Nome dos(as) Professores(as):"
variant="standard"
type="text"
sx={StyledInputs({ color: '#26AAA7' })}
{...form.register('professors')}
/> */}
<Controller <Controller
control={form.control} control={form.control}
name="professors" name="professors"
...@@ -274,6 +320,7 @@ export default function Curso() { ...@@ -274,6 +320,7 @@ export default function Curso() {
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
variant={'ghost'} variant={'ghost'}
type="button"
className={cn( className={cn(
'lg:w-[450px] justify-start text-left font-normal border-b rounded-none pl-0 border-green-400', 'lg:w-[450px] justify-start text-left font-normal border-b rounded-none pl-0 border-green-400',
!dateStart && 'text-muted-foreground', !dateStart && 'text-muted-foreground',
...@@ -293,6 +340,7 @@ export default function Curso() { ...@@ -293,6 +340,7 @@ export default function Curso() {
selected={dateStart} selected={dateStart}
onSelect={setDateStart} onSelect={setDateStart}
initialFocus initialFocus
{...form.register('startDate')}
/> />
</PopoverContent> </PopoverContent>
</Popover> </Popover>
...@@ -316,6 +364,7 @@ export default function Curso() { ...@@ -316,6 +364,7 @@ export default function Curso() {
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
variant={'ghost'} variant={'ghost'}
type="button"
className={cn( className={cn(
'lg:w-[450px] justify-start text-left font-normal border-b rounded-none pl-0 border-green-400', 'lg:w-[450px] justify-start text-left font-normal border-b rounded-none pl-0 border-green-400',
!dateEnd && 'text-muted-foreground', !dateEnd && 'text-muted-foreground',
...@@ -348,12 +397,47 @@ export default function Curso() { ...@@ -348,12 +397,47 @@ export default function Curso() {
<h6 className="text-xl text-purple-100 mb-4">Módulos</h6> <h6 className="text-xl text-purple-100 mb-4">Módulos</h6>
<TextField
type="text"
variant="standard"
label="Selecione os módulos do curso"
select
sx={StyledInputs({ color: '#26AAA7' })}
defaultValue={''}
value=""
onChange={handleSelect}
// {...form.register('moduleIds')}
>
<MenuItem value="" className="hidden">
Selecione os módulos
</MenuItem>
{modules?.data.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</TextField>
{selectedModules?.map((item) => (
<div
key={item.id}
className="w-full flex justify-between bg-green-800/30 py-2 px-4 rounded-sm"
>
<p>{item.name}</p>
<Trash2
className="cursor-pointer text-red-400 hover:text-red-100"
onClick={() => handleDeleteModule(item.id)}
/>
</div>
))}
<div className="flex flex-col p-4 gap-6 border border-gray-100"> <div className="flex flex-col p-4 gap-6 border border-gray-100">
<TextField <TextField
label="Título" label="Título"
variant="standard" variant="standard"
type="text" type="text"
sx={StyledInputs({ color: '#26AAA7' })} sx={StyledInputs({ color: '#26AAA7' })}
{...register('name')}
/> />
<TextField <TextField
...@@ -363,16 +447,20 @@ export default function Curso() { ...@@ -363,16 +447,20 @@ export default function Curso() {
multiline multiline
rows={4} rows={4}
sx={StyledInputs({ color: '#26AAA7' })} sx={StyledInputs({ color: '#26AAA7' })}
{...register('description')}
/> />
</div> </div>
<Button <Button
variant="third" variant="third"
type="button"
className="uppercase w-52 flex items-center gap-2 !mt-8" className="uppercase w-52 flex items-center gap-2 !mt-8"
onClick={handleSubmit(addNewModuler)}
> >
<PlusIcon size={20} /> <span>Adicionar Módulo</span> <PlusIcon size={20} /> <span>Criar novo Módulo</span>
</Button> </Button>
</div> </div>
<div className="w-[380px] pl-6 flex flex-col gap-4"> <div className="w-[380px] pl-6 flex flex-col gap-4">
<h6 className="text-xl text-purple-100 mb-4"> <h6 className="text-xl text-purple-100 mb-4">
Qual o público alvo? Qual o público alvo?
......
...@@ -4,10 +4,13 @@ import { getAreas } from '@/api/areas' ...@@ -4,10 +4,13 @@ import { getAreas } from '@/api/areas'
import { Card } from '@/components/card' import { Card } from '@/components/card'
import { InputMui } from '@/components/mui/inputs' import { InputMui } from '@/components/mui/inputs'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { AuthContext } from '@/contexts/auth-context'
import { CoursesCard } from '@/utils/courses-array' import { CoursesCard } from '@/utils/courses-array'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { Pencil, Search } from 'lucide-react' import { Pencil, Search } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
import { useContext } from 'react'
import { useForm } from 'react-hook-form'
// const areas = [ // const areas = [
// { // {
...@@ -46,6 +49,10 @@ const banners = [ ...@@ -46,6 +49,10 @@ const banners = [
] ]
export default function Admin() { export default function Admin() {
const { register } = useForm()
const { user } = useContext(AuthContext)
console.log('🚀 ~ Admin ~ user:', user)
const { const {
data: areas, data: areas,
error, error,
...@@ -63,7 +70,7 @@ export default function Admin() { ...@@ -63,7 +70,7 @@ export default function Admin() {
return ( return (
<section className="container"> <section className="container">
<h1 className="text-green-400 text-2xl">Bem vindo, João da Silva,</h1> <h1 className="text-green-400 text-2xl">Bem vindo, {user?.name}!</h1>
<div className="flex h-auto mb-20"> <div className="flex h-auto mb-20">
<aside className="mt-10 w-[348px]"> <aside className="mt-10 w-[348px]">
<h2 className="text-purple-50 text-2xl">Áreas</h2> <h2 className="text-purple-50 text-2xl">Áreas</h2>
...@@ -116,6 +123,8 @@ export default function Admin() { ...@@ -116,6 +123,8 @@ export default function Admin() {
variant="standard" variant="standard"
className="w-full" className="w-full"
themeColor="#fafafa" themeColor="#fafafa"
name="search"
register={register}
/> />
<Search size={24} className="-ml-6" /> <Search size={24} className="-ml-6" />
</div> </div>
......
...@@ -2,6 +2,7 @@ import { InputMui } from '@/components/mui/inputs' ...@@ -2,6 +2,7 @@ import { InputMui } from '@/components/mui/inputs'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { MenuItem } from '@mui/material' import { MenuItem } from '@mui/material'
import { Clock4, Mail, Smartphone } from 'lucide-react' import { Clock4, Mail, Smartphone } from 'lucide-react'
import { useForm } from 'react-hook-form'
const matters = [ const matters = [
{ {
...@@ -23,6 +24,8 @@ const matters = [ ...@@ -23,6 +24,8 @@ const matters = [
] ]
export default function Contact() { export default function Contact() {
const { register } = useForm()
return ( return (
<main className="container flex flex-col justify-center items-center py-20"> <main className="container flex flex-col justify-center items-center py-20">
<h1 className="text-center font-normal md:font-extrabold text-green-800 text-2xl md:text-4xl"> <h1 className="text-center font-normal md:font-extrabold text-green-800 text-2xl md:text-4xl">
...@@ -53,16 +56,31 @@ export default function Contact() { ...@@ -53,16 +56,31 @@ export default function Contact() {
Envie uma mensagem Envie uma mensagem
</h2> </h2>
<form action="" className="mt-6 flex flex-col gap-6"> <form action="" className="mt-6 flex flex-col gap-6">
<InputMui type="text" variant="outlined" label="Nome completo" /> <InputMui
type="text"
variant="outlined"
label="Nome completo"
name="full-name"
register={register}
/>
<InputMui <InputMui
type="text" type="text"
variant="outlined" variant="outlined"
label="Mensagem" label="Mensagem"
multiline multiline
rows={4} rows={4}
name="message"
register={register}
/> />
<InputMui type="text" variant="outlined" label="Assunto" select> <InputMui
type="text"
variant="outlined"
label="Assunto"
select
name="subject"
register={register}
>
{matters.map((option) => ( {matters.map((option) => (
<MenuItem key={option.value} value={option.value}> <MenuItem key={option.value} value={option.value}>
{option.label} {option.label}
......
...@@ -7,6 +7,7 @@ import type { Metadata } from 'next' ...@@ -7,6 +7,7 @@ import type { Metadata } from 'next'
import { Poppins } from 'next/font/google' import { Poppins } from 'next/font/google'
import { Toaster } from 'sonner' import { Toaster } from 'sonner'
import { AuthProvider } from '@/contexts/auth-context'
import ReactQueryProvider from '@/providers/react-query' import ReactQueryProvider from '@/providers/react-query'
const poppins = Poppins({ subsets: ['latin'], weight: ['300', '400', '800'] }) const poppins = Poppins({ subsets: ['latin'], weight: ['300', '400', '800'] })
...@@ -24,21 +25,23 @@ export default function RootLayout({ ...@@ -24,21 +25,23 @@ export default function RootLayout({
return ( return (
<html lang="pt-BR"> <html lang="pt-BR">
<body className={`antialiased ${poppins.className}`}> <body className={`antialiased ${poppins.className}`}>
<ThemeProvider <AuthProvider>
attribute="class" <ThemeProvider
defaultTheme="dark" attribute="class"
enableSystem defaultTheme="dark"
disableTransitionOnChange enableSystem
> disableTransitionOnChange
<StyledEngineProvider injectFirst> >
<ReactQueryProvider> <StyledEngineProvider injectFirst>
<Toaster richColors /> <ReactQueryProvider>
<Header /> <Toaster richColors />
{children} <Header />
<Footer /> {children}
</ReactQueryProvider> <Footer />
</StyledEngineProvider> </ReactQueryProvider>
</ThemeProvider> </StyledEngineProvider>
</ThemeProvider>
</AuthProvider>
</body> </body>
</html> </html>
) )
......
'use client'
import { InputMui } from '@/components/mui/inputs' import { InputMui } from '@/components/mui/inputs'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox' import { AuthContext } from '@/contexts/auth-context'
import Image from 'next/image' import Image from 'next/image'
import Link from 'next/link' import Link from 'next/link'
// import { useRouter } from 'next/router'
import { AxiosError } from 'axios'
import { useContext } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import globo from '../../../public/images/globo.svg' import globo from '../../../public/images/globo.svg'
type FormLogin = {
email: string
password: string
}
export default function Login() { export default function Login() {
const { handleSubmit, register } = useForm<FormLogin>()
const { signIn } = useContext(AuthContext)
// const router = useRouter()
const onSubmit = async (data: FormLogin) => {
try {
await signIn(data)
// router.push('/admin')
} catch (error) {
if (error instanceof AxiosError) {
const { message } = error.response?.data
toast.error(`${message}`)
}
console.log('🚀 ~ onSubmit ~ error aqui:', error)
}
}
return ( return (
<main> <main>
<section className=" container flex flex-col justify-center items-center space-y-10 h-[723px]"> <section className=" container flex flex-col justify-center items-center space-y-10 h-[723px]">
...@@ -20,15 +50,27 @@ export default function Login() { ...@@ -20,15 +50,27 @@ export default function Login() {
Acesso exclusivo para usuários cadastrados. Acesso exclusivo para usuários cadastrados.
</h2> </h2>
<form <form
// onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
className="w-full md:w-[600px] flex flex-col gap-8 order-4" className="w-full md:w-[600px] flex flex-col gap-8 order-4"
> >
<div className="flex flex-col space-y-6"> <div className="flex flex-col space-y-6">
<div className="flex flex-col space-y-4"> <div className="flex flex-col space-y-4">
<InputMui label="E-mail" variant="outlined" type="email" /> <InputMui
<InputMui label="Senha" variant="outlined" type="password" /> label="E-mail"
variant="outlined"
type="email"
name="email"
register={register}
/>
<InputMui
label="Senha"
variant="outlined"
type="password"
name="password"
register={register}
/>
</div> </div>
<div className="flex items-center space-x-2"> {/* <div className="flex items-center space-x-2">
<Checkbox id="remember" /> <Checkbox id="remember" />
<label <label
htmlFor="remember" htmlFor="remember"
...@@ -36,9 +78,10 @@ export default function Login() { ...@@ -36,9 +78,10 @@ export default function Login() {
> >
Lembrar-me Lembrar-me
</label> </label>
</div> </div> */}
<Button className="rounded-sm" asChild> <Button className="rounded-sm" type="submit">
<Link href="/admin">Entrar</Link> {/* <Link href="/admin">Entrar</Link> */}
Entrar
</Button> </Button>
<Button <Button
variant={'link'} variant={'link'}
......
...@@ -17,6 +17,7 @@ const InputFile = forwardRef<HTMLInputElement, InputFileProps>( ...@@ -17,6 +17,7 @@ const InputFile = forwardRef<HTMLInputElement, InputFileProps>(
<input <input
id={id} id={id}
type="file" type="file"
accept="image/png, image/jpeg, image/jpg"
className="hidden" className="hidden"
ref={ref} ref={ref}
{...inputProps} {...inputProps}
......
...@@ -3,21 +3,26 @@ ...@@ -3,21 +3,26 @@
import { useThemeClient } from '@/hooks/useThemeClient' import { useThemeClient } from '@/hooks/useThemeClient'
import { TextField, TextFieldProps } from '@mui/material' import { TextField, TextFieldProps } from '@mui/material'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { FieldValues, Path, UseFormRegister } from 'react-hook-form'
type InputMuiProps = TextFieldProps & { type InputMuiProps<T extends FieldValues> = TextFieldProps & {
label: string label: string
variant: 'standard' | 'filled' | 'outlined' variant: 'standard' | 'filled' | 'outlined'
themeColor?: string themeColor?: string
children?: ReactNode children?: ReactNode
name: Path<T>
register: UseFormRegister<T>
} }
export function InputMui({ export const InputMui = <T extends FieldValues>({
label, label,
variant, variant,
children, children,
themeColor = '#fafafa', themeColor = '#fafafa',
...props register,
}: InputMuiProps) { name,
...rest
}: InputMuiProps<T>) => {
const themeConfig = useThemeClient() const themeConfig = useThemeClient()
const color = themeConfig === 'dark' ? themeColor : '#3C3C3C' const color = themeConfig === 'dark' ? themeColor : '#3C3C3C'
...@@ -26,7 +31,8 @@ export function InputMui({ ...@@ -26,7 +31,8 @@ export function InputMui({
<TextField <TextField
label={label} label={label}
variant={variant} variant={variant}
{...props} {...register(name)}
{...rest}
sx={{ sx={{
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
'& fieldset': { '& fieldset': {
......
import { Search } from 'lucide-react' import { Search } from 'lucide-react'
import { useForm } from 'react-hook-form'
import { InputMui } from './mui/inputs' import { InputMui } from './mui/inputs'
import { Label } from './ui/label' import { Label } from './ui/label'
import { import {
...@@ -11,6 +12,8 @@ import { ...@@ -11,6 +12,8 @@ import {
} from './ui/select' } from './ui/select'
export default function SearchFilter() { export default function SearchFilter() {
const { register } = useForm()
return ( return (
<div className="container grid md:grid-cols-2 gap-8 items-end mb-8"> <div className="container grid md:grid-cols-2 gap-8 items-end mb-8">
<div className="flex items-center max-w-[712px] flex-1"> <div className="flex items-center max-w-[712px] flex-1">
...@@ -19,6 +22,8 @@ export default function SearchFilter() { ...@@ -19,6 +22,8 @@ export default function SearchFilter() {
variant="standard" variant="standard"
type="text" type="text"
className="w-full #fafafa" className="w-full #fafafa"
name="learned-today"
register={register}
/> />
<Search size={24} className="-ml-6" /> <Search size={24} className="-ml-6" />
</div> </div>
......
'use client'
// import { useRouter } from 'next/router'
import { parseCookies, setCookie } from 'nookies'
import { createContext, useEffect, useState } from 'react'
import { getAccountsId } from '@/api/accounts'
import { login } from '@/api/login'
import { api } from '@/lib/axios'
import { useRouter } from 'next/navigation'
type User = {
id: string
name: string
email: string
} | null
type SignInData = {
email: string
password: string
}
type AuthContextType = {
isAuthenticated: boolean
user: User
signIn: (data: SignInData) => Promise<void>
}
export const AuthContext = createContext({} as AuthContextType)
export function AuthProvider({ children }) {
const [user, setUser] = useState<User | null>(null)
const router = useRouter()
const isAuthenticated = !!user
useEffect(() => {
const { 'sevenpro-token': token } = parseCookies()
const { 'sevenpro-user': userCookie } = parseCookies()
if (token && userCookie) {
const { id } = JSON.parse(userCookie)
getAccountsId(id).then((response) => {
setUser(response.data)
})
}
}, [])
async function signIn({ email, password }: SignInData) {
const { data } = await login({
email,
password,
})
const token = data.access_token
const user = data.user
setCookie(undefined, 'sevenpro-token', token, {
maxAge: 60 * 60 * 1, // 1 hour
})
setCookie(undefined, 'sevenpro-user', JSON.stringify(user), {
maxAge: 60 * 60 * 1, // 1 hour
})
api.defaults.headers.Authorization = `Bearer ${token}`
setUser(user)
router.push('/admin')
}
return (
<AuthContext.Provider value={{ user, isAuthenticated, signIn }}>
{children}
</AuthContext.Provider>
)
}
import axios from 'axios' import axios from 'axios'
// import Router from 'next/router'
import { destroyCookie, parseCookies } from 'nookies'
const { 'sevenpro-token': token } = parseCookies()
console.log('🚀 ~ token:', token)
export const api = axios.create({ export const api = axios.create({
baseURL: 'http://localhost:3000', baseURL: process.env.NEXT_PUBLIC_URL_API,
}) })
if (token) {
api.defaults.headers.Authorization = `Bearer ${token}`
}
// api.interceptors.request.use(
// (config) => {
// return config
// },
// (error) => {
// return Promise.reject(error)
// },
// )
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response && error.response.status === 401) {
// Remove o token inválido
destroyCookie(null, 'sevenpro-token')
// Redireciona para a página de login
// Router.push('/login')
}
return Promise.reject(error)
},
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment