Commit 1a435420 authored by Wellton Quirino's avatar Wellton Quirino

final adjustments

parent 472aa36e
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: `${process.env.NEXT_PUBLIC_CLOUDFLARE_URL_PUBLIC_BUCKET}`,
port: '',
pathname: '**',
},
],
},
}
module.exports = nextConfig
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"lint": "eslint . --fix" "lint": "eslint . --fix"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.758.0",
"@emotion/react": "^11.13.0", "@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0", "@emotion/styled": "^11.13.0",
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
...@@ -28,6 +29,7 @@ ...@@ -28,6 +29,7 @@
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"date-fns-tz": "^3.2.0",
"embla-carousel-autoplay": "^8.1.8", "embla-carousel-autoplay": "^8.1.8",
"embla-carousel-react": "^8.1.8", "embla-carousel-react": "^8.1.8",
"lucide-react": "^0.383.0", "lucide-react": "^0.383.0",
......
import { api } from '@/lib/axios' import { api } from '@/lib/axios'
// interface FileBanner {
// name: string
// }
export type AreasProps = { export type AreasProps = {
id?: string id?: string
name: string name: string
desktopBanner: string desktopBanner: string | null
mobileBanner: string mobileBanner: string | null
// desktopBanner?: { [key: string]: FileBanner }
// mobileBanner?: { [key: string]: FileBanner }
} }
export async function createAreas({ export async function createAreas({
...@@ -31,7 +26,7 @@ export async function editArea({ ...@@ -31,7 +26,7 @@ export async function editArea({
desktopBanner, desktopBanner,
mobileBanner, mobileBanner,
}: AreasProps) { }: AreasProps) {
const area = await api.patch<AreasProps[]>(`/areas/${id}`, { const area = await api.put<AreasProps[]>(`/areas/${id}`, {
name, name,
desktopBanner, desktopBanner,
mobileBanner, mobileBanner,
......
import { api } from '@/lib/axios' import { api } from '@/lib/axios'
export type AudiencesNameProps = {
name: string
}
export type AudiencesProps = { export type AudiencesProps = {
id: string id: string
name: string name: string
} }
export async function createAudiences({
name,
}: AudiencesNameProps) {
const audiences = await api.post<AudiencesNameProps>('/audiences', {
name,
})
return audiences
}
export async function getAudiences() { export async function getAudiences() {
const audiences = await api.get<AudiencesProps[]>('/audiences') const audiences = await api.get<AudiencesProps[]>('/audiences')
return audiences return audiences
} }
export async function getAudienceId(id: string) {
const audiences = await api.get<AudiencesProps>(`/audiences/${id}`)
return audiences
}
export async function editAudienceId(id: string) {
const audiences = await api.put<AudiencesProps>(`/audiences/${id}`)
return audiences
}
export async function deleteAudience(id: string) {
const audiences = await api.delete(`/audiences/${id}`)
return audiences
}
\ No newline at end of file
import { api } from '@/lib/axios'
import { AreasProps } from './areas'
import { AudiencesProps } from './audiences'
import { CategoriesProps } from './categories'
import { ModulesProps } from './modules'
export type CourseIdProps = {
id?: string
name: string
area: AreasProps
audience: AudiencesProps
category: CategoriesProps
description: string
desktopBanner: string
mobileBanner: string
startDate?: string
endDate?: string
professors: string[]
workload: number
modules: {
courseId: string
module: ModulesProps
moduleId: string
}[]
}
export type CoursesProps = {
id?: string
name: string
description: string
desktopBanner: string | null
mobileBanner: string | null
startDate?: string
endDate?: string
professors: string[]
workload: number
areaId: string
audienceId: string
categoryId: string
audience?: {
id: string
name: string
}
category?: {
id: string
name: string
}
moduleIds: string[]
}
export async function createCourses(data: CoursesProps) {
const course = await api.post<CoursesProps[]>('/courses', data)
return course
}
export async function getCourses() {
const courses = await api.get<CoursesProps[]>('/courses')
return courses
}
export async function getCourseId(id: string) {
const course = await api.get<CourseIdProps>(`/courses/${id}`)
return course
}
export async function editCourseId(data: CoursesProps) {
const course = await api.put<CoursesProps>(`/courses/${data.id}`, data)
return course
}
export async function deleteCourse(id: string) {
const course = await api.delete(`/courses/${id}`)
return course
}
'use client' 'use client'
import { Search } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
import { About } from '@/components/about' import { About } from '@/components/about'
...@@ -8,16 +7,15 @@ import { Banner } from '@/components/banner' ...@@ -8,16 +7,15 @@ import { Banner } from '@/components/banner'
import { CarouselComponent } from '@/components/corousel-component' import { CarouselComponent } from '@/components/corousel-component'
import { CourseCategory } from '@/components/course-category' import { CourseCategory } from '@/components/course-category'
import { Differences } from '@/components/differences' import { Differences } from '@/components/differences'
import { InputMui } from '@/components/mui/inputs'
import { NavLinkCategory } from '@/components/nav-link-category' import { NavLinkCategory } from '@/components/nav-link-category'
import { SignUp } from '@/components/sign-up' 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' // import { useForm } from 'react-hook-form'
export default function Home() { export default function Home() {
const { register } = useForm() // const { register } = useForm()
return ( return (
<> <>
...@@ -29,7 +27,7 @@ export default function Home() { ...@@ -29,7 +27,7 @@ export default function Home() {
<Suspense fallback={<SkeletonSerachParams />}> <Suspense fallback={<SkeletonSerachParams />}>
<NavLinkCategory /> <NavLinkCategory />
</Suspense> </Suspense>
<div className="flex justify-center items-center w-full md:w-1/2 mx-auto mb-8"> {/* <div className="flex justify-center items-center w-full md:w-1/2 mx-auto mb-8">
<InputMui <InputMui
label="O que você quer aprender hoje?" label="O que você quer aprender hoje?"
variant="standard" variant="standard"
...@@ -40,14 +38,14 @@ export default function Home() { ...@@ -40,14 +38,14 @@ export default function Home() {
register={register} register={register}
/> />
<Search size={24} /> <Search size={24} />
</div> </div> */}
<CarouselComponent /> <CarouselComponent />
<Button <Button
variant="secondary" variant="secondary"
className="uppercase mx-auto my-8" className="uppercase mx-auto my-8"
asChild asChild
> >
<Link href="#">Ver todos os cursos</Link> <Link href="/estudantes">Ver todos os cursos</Link>
</Button> </Button>
</div> </div>
<CourseCategory /> <CourseCategory />
......
...@@ -5,28 +5,37 @@ import BreadcrumbComponent from '@/components/breadcrumb-component' ...@@ -5,28 +5,37 @@ import BreadcrumbComponent from '@/components/breadcrumb-component'
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 ModalDialog from '@/components/modal-dialog' import ModalDialog from '@/components/modal-dialog'
import { StyledInputs } from '@/components/mui/styled-inputs'
import { AlertDialog, AlertDialogTrigger } from '@/components/ui/alert-dialog' import { AlertDialog, AlertDialogTrigger } from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { cn } from '@/lib/utils'
import { TextField } from '@mui/material'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Trash } from 'lucide-react' import { Trash } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation' import { useParams, useRouter } from 'next/navigation'
import { useState } from 'react'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import { toast } from 'sonner' import { toast } from 'sonner'
export default function AreaId() { export default function AreaId() {
const [desktopBanner, setDesktopBanner] = useState<File | null>(null)
console.log('🚀 ~ AreaId ~ desktopBanner:', desktopBanner)
const [mobileBanner, setMobileBanner] = useState<File | null>(null)
const params = useParams<{ id: string }>() const params = useParams<{ id: string }>()
const { register, handleSubmit, watch } = useForm<AreasProps>() const {
register,
handleSubmit,
formState: { errors },
} = useForm<AreasProps>()
const router = useRouter() const router = useRouter()
const data = watch() console.log('🚀 ~ AreaId ~ errors:', errors)
const valueDesktopBanner = data?.desktopBanner?.['0']?.name
const valueMobileBanner = data?.mobileBanner?.['0']?.name
const queryClient = useQueryClient() const queryClient = useQueryClient()
const { data: area, isLoading } = useQuery({ const { data: area, isLoading } = useQuery({
queryKey: ['area', params.id], queryKey: ['areas', params.id],
queryFn: () => getAreasId(params.id), queryFn: () => getAreasId(params.id),
enabled: !!params.id, enabled: !!params.id,
}) })
...@@ -49,12 +58,34 @@ export default function AreaId() { ...@@ -49,12 +58,34 @@ export default function AreaId() {
}, },
}) })
function onSubmit(data: AreasProps) { async function onSubmit(data: AreasProps) {
const body = { const urlDesktop = await handleUpload(desktopBanner)
const urlMobile = await handleUpload(mobileBanner)
console.log(data)
const dataArea = {
...data, ...data,
id: params.id, id: params.id,
desktopBanner: urlDesktop,
mobileBanner: urlMobile,
} }
mutationEdit.mutate(body) mutationEdit.mutateAsync(dataArea)
}
const handleUpload = async (file: File | null) => {
if (!file) return
const formData = new FormData()
formData.append('file', file)
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const data = await res.json()
return data.url
} }
function handleDeleteArea() { function handleDeleteArea() {
...@@ -71,8 +102,8 @@ export default function AreaId() { ...@@ -71,8 +102,8 @@ export default function AreaId() {
} }
return ( return (
<section className="container py-20 space-y-10"> <section className="container py-10">
<BreadcrumbComponent /> <BreadcrumbComponent page="Área" />
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex items-center gap-6"> <div className="flex items-center gap-6">
<h1 className="text-3xl font-bold">Editar Área</h1> <h1 className="text-3xl font-bold">Editar Área</h1>
...@@ -103,36 +134,52 @@ export default function AreaId() { ...@@ -103,36 +134,52 @@ export default function AreaId() {
</AlertDialog> </AlertDialog>
</div> </div>
<div className="w-full mt-16"> <div className="w-full mt-16">
<Input <TextField
label="Nome da área" label="Nome da área"
className="border-green-400" type="text"
defaultValue={area?.data.name} defaultValue={area?.data.name}
{...register('name')} {...register('name')}
type="text" variant="standard"
className="flex"
sx={StyledInputs(
errors.name ? { color: '#dc2626' } : { color: '#26AAA7' },
)}
/> />
<div className="flex justify-between flex-wrap gap-6 py-6"> <div className="flex justify-between flex-wrap gap-6 py-6">
<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={cn(
'text-xl text-purple-100',
errors.desktopBanner && 'text-red-600',
)}
>
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"
values={valueDesktopBanner} label={!desktopBanner ? 'Atualizar banner' : desktopBanner.name}
label="Adicionar banner" onChange={(e) => setDesktopBanner(e.target.files?.[0] || null)}
{...register('desktopBanner')}
/> />
</div> </div>
<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={cn(
'text-xl text-purple-100',
errors.mobileBanner && 'text-red-600',
)}
>
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} label={!mobileBanner ? 'Atualizar banner' : mobileBanner.name}
label="Adicionar banner" onChange={(e) => setMobileBanner(e.target.files?.[0] || null)}
{...register('mobileBanner')}
/> />
</div> </div>
</div> </div>
......
'use client' 'use client'
import { AreasProps, createAreas } from '@/api/areas' import { AreasProps, createAreas } from '@/api/areas'
import BreadcrumbComponent from '@/components/breadcrumb-component'
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'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { zodResolver } from '@hookform/resolvers/zod'
import { TextField } from '@mui/material' import { TextField } from '@mui/material'
import { useMutation, useQueryClient } from '@tanstack/react-query' import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import { toast } from 'sonner' import { toast } from 'sonner'
import { z } from 'zod'
const FormSchema = z.object({
name: z.string().trim().min(3, { message: 'Nome obrigatório' }),
})
export default function Area() { export default function Area() {
const { register, handleSubmit, watch } = useForm<AreasProps>() const [desktopBanner, setDesktopBanner] = useState<File | null>(null)
const router = useRouter() const [mobileBanner, setMobileBanner] = useState<File | null>(null)
console.log('🚀 ~ Area ~ mobileBanner:', mobileBanner)
const data = watch() const {
const valueMobileBanner = data?.mobileBanner?.['0']?.name register,
const valueDesktopBanner = data?.desktopBanner?.['0']?.name handleSubmit,
setError,
formState: { errors },
} = useForm<AreasProps>({
resolver: zodResolver(FormSchema),
})
console.log('🚀 ~ Area ~ error:', errors)
const router = useRouter()
const queryClient = useQueryClient() const queryClient = useQueryClient()
...@@ -30,29 +47,51 @@ export default function Area() { ...@@ -30,29 +47,51 @@ export default function Area() {
}, },
}) })
function onSubmit(data: AreasProps) { async function onSubmit(data: AreasProps) {
// console.log('🚀 ~ onSubmit ~ data:', data) if (!desktopBanner) {
// const fileMobile = data.mobileBanner?.[0] setError('desktopBanner', {
// const fileDesktop = data.desktopBanner?.[0] type: 'manual',
message: 'Foto desktop obrigatória!',
})
return
}
if (!mobileBanner) {
setError('mobileBanner', {
type: 'manual',
message: 'Foto mobile obrigatória!',
})
return
}
// const formData = new FormData() const urlDesktop = await handleUpload(desktopBanner)
// formData.append('mobileBanner', fileMobile, fileMobile?.name) const urlMobile = await handleUpload(mobileBanner)
// formData.append('desktopBanner', fileDesktop, fileDesktop?.name)
// console.log('🚀 ~ onSubmit ~ formData:', formData)
const dataArea = { const dataArea = {
...data, ...data,
mobileBanner: desktopBanner: urlDesktop,
'/Users/wellton/Documents/www/sevenpro-frontend/public/images/banners/negocios-mobile.jpg', mobileBanner: urlMobile,
desktopBanner:
'/Users/wellton/Documents/www/sevenpro-frontend/public/images/banners/negocios.jpg',
} }
console.log('🚀 ~ onSubmit ~ data:', dataArea) mutation.mutateAsync(dataArea)
mutation.mutate(dataArea) }
const handleUpload = async (file: File | null) => {
if (!file) return
const formData = new FormData()
formData.append('file', file)
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const data = await res.json()
return data.url
} }
return ( return (
<section className="container py-20 space-y-10"> <section className="container py-10">
<BreadcrumbComponent page="Área" />
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex items-center gap-6"> <div className="flex items-center gap-6">
<h1 className="text-3xl font-bold">Criar Área</h1> <h1 className="text-3xl font-bold">Criar Área</h1>
...@@ -68,31 +107,45 @@ export default function Area() { ...@@ -68,31 +107,45 @@ export default function Area() {
type="text" type="text"
className="flex" className="flex"
{...register('name')} {...register('name')}
sx={StyledInputs({ color: '#26AAA7' })} sx={StyledInputs(
errors.name ? { color: '#dc2626' } : { color: '#26AAA7' },
)}
/> />
<div className="flex justify-between flex-wrap gap-6 py-6"> <div className="flex justify-between flex-wrap gap-6 py-6">
<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={cn(
'text-xl text-purple-100',
errors.desktopBanner && 'text-red-600',
)}
>
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, .jpeg</span> <span>Formatos aceitos: .png, .jpg, .jpeg</span>
<InputFile <InputFile
id="desktopBanner" id="desktopBanner"
values={valueDesktopBanner} label={!desktopBanner ? 'Adicionar banner' : desktopBanner.name}
label="Adicionar banner" onChange={(e) => setDesktopBanner(e.target.files?.[0] || null)}
{...register('desktopBanner')}
/> />
</div> </div>
<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={cn(
'text-xl text-purple-100',
errors.mobileBanner && 'text-red-600',
)}
>
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, .jpeg</span> <span>Formatos aceitos: .png, .jpg, .jpeg</span>
<InputFile <InputFile
id="mobileBanner" id="mobileBanner"
values={valueMobileBanner} label={!mobileBanner ? 'Adicionar banner' : mobileBanner.name}
label="Adicionar banner" onChange={(e) => setMobileBanner(e.target.files?.[0] || null)}
{...register('mobileBanner')}
/> />
</div> </div>
</div> </div>
......
This diff is collapsed.
This diff is collapsed.
'use client' 'use client'
import { getAreas } from '@/api/areas' import { getAreas } from '@/api/areas'
import { createAudiences, deleteAudience, editAudienceId, getAudiences } from '@/api/audiences'
import { getCourses } from '@/api/courses'
import { Card } from '@/components/card' import { Card } from '@/components/card'
import ModalInputs from '@/components/modal-inputs'
import { InputMui } from '@/components/mui/inputs' import { InputMui } from '@/components/mui/inputs'
import { AlertDialog, AlertDialogTrigger } from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { AuthContext } from '@/contexts/auth-context' import { AuthContext } from '@/contexts/auth-context'
import { CoursesCard } from '@/utils/courses-array' import { useMutation, useQuery, useQueryClient } 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 { useContext, useState } from 'react'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
// const areas = [
// {
// id: '1',
// title: 'Educação',
// },
// {
// id: '2',
// title: 'Negócios',
// },
// {
// id: '3',
// title: 'Comunicação',
// },
// {
// id: '4',
// title: 'Saúde',
// },
// {
// id: '5',
// title: 'Tecnologia',
// },
// {
// id: '6',
// title: 'Teologia',
// },
// ]
const banners = [
{
title: 'Banner Home',
},
{
title: 'CTAs',
},
]
export default function Admin() { export default function Admin() {
const [open, setOpen] = useState<boolean>(false);
const [nameAudience, setNameAudience] = useState('')
const { register } = useForm() const { register } = useForm()
const { user } = useContext(AuthContext) const { user } = useContext(AuthContext)
console.log('🚀 ~ Admin ~ user:', user) const queryClient = useQueryClient()
const { const {
data: areas, data: areas,
...@@ -60,6 +30,58 @@ export default function Admin() { ...@@ -60,6 +30,58 @@ export default function Admin() {
isError, isError,
} = useQuery({ queryKey: ['areas'], queryFn: getAreas }) } = useQuery({ queryKey: ['areas'], queryFn: getAreas })
const { data: audiences } = useQuery({
queryKey: ['audiences'],
queryFn: getAudiences,
})
const { data: courses } = useQuery({
queryKey: ['courses'],
queryFn: getCourses,
})
const mutationCreateAudience = useMutation({
mutationFn: createAudiences,
onSuccess: () => {
toast.success('Audiência criada com sucesso!')
queryClient.invalidateQueries({ queryKey: ['audiences'] })
},
})
const mutationEditAudience = useMutation({
mutationFn: editAudienceId,
onSuccess: () => {
toast.success('Audiência criada com sucesso!')
queryClient.invalidateQueries({ queryKey: ['audiences'] })
},
})
const mutationDeleteAudience = useMutation({
mutationFn: deleteAudience,
onSuccess: () => {
toast.success('Audiência criada com sucesso!')
queryClient.invalidateQueries({ queryKey: ['audiences'] })
},
})
function handleCreateAudience() {
const body = {
name: nameAudience
}
mutationCreateAudience.mutate(body)
setOpen(false)
}
function handleEditAudience(id: string) {
mutationEditAudience.mutate(id)
setOpen(false)
}
function handleDeleteAudience(id: string) {
mutationDeleteAudience.mutate(id)
setOpen(false)
}
if (isLoading) { if (isLoading) {
return <span>Loading...</span> return <span>Loading...</span>
} }
...@@ -95,7 +117,62 @@ export default function Admin() { ...@@ -95,7 +117,62 @@ export default function Admin() {
</ul> </ul>
</nav> </nav>
<h2 className="text-purple-50 text-2xl mt-10">Banners</h2> <h2 className="text-purple-50 text-2xl mt-10">Audiência</h2>
<AlertDialog open={open} onOpenChange={setOpen}>
<>
<AlertDialogTrigger
className="flex justify-between w-full"
type="button"
>
<Button className="uppercase mt-6">
+ Adicionar novo
</Button>
</AlertDialogTrigger>
<ModalInputs
title="Criar"
description="Criar nova audiência"
handleClick={handleCreateAudience}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setNameAudience(e.target.value)
}
/>
</>
</AlertDialog>
<AlertDialog open={open} onOpenChange={setOpen}>
<nav className="my-6 w-full">
<ul className="space-y-4">
{audiences?.data.map((audience) => (
<li
key={audience.id}
className="border-b border-green-400 py-2 hover:bg-green-400/10"
>
<AlertDialogTrigger
className="flex justify-between w-full"
type="button"
>
{audience.name}
<Pencil className="text-green-400" />
</AlertDialogTrigger>
<ModalInputs
title="Editar"
description="Editar nome da audiência"
handleClick={() => handleEditAudience(audience.id)}
value={nameAudience}
handleClickDelete={() => handleDeleteAudience(audience.id)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setNameAudience(e.target.value)
}
/>
</li>
))}
</ul>
</nav>
</AlertDialog>
{/* <h2 className="text-purple-50 text-2xl mt-10">Banners</h2>
<nav className="my-6"> <nav className="my-6">
<ul className="space-y-4"> <ul className="space-y-4">
{banners.map((banner) => ( {banners.map((banner) => (
...@@ -108,7 +185,7 @@ export default function Admin() { ...@@ -108,7 +185,7 @@ export default function Admin() {
</li> </li>
))} ))}
</ul> </ul>
</nav> </nav> */}
</aside> </aside>
<div className="flex-1 ml-4 pl-6 border-l border-gray-50"> <div className="flex-1 ml-4 pl-6 border-l border-gray-50">
...@@ -128,28 +205,36 @@ export default function Admin() { ...@@ -128,28 +205,36 @@ export default function Admin() {
/> />
<Search size={24} className="-ml-6" /> <Search size={24} className="-ml-6" />
</div> </div>
<div className="flex flex-wrap justify-around gap-4"> {courses?.data.length === 0 ? (
{CoursesCard.map((course) => ( <p className="text-yellow-100">Não há curso cadastrado!</p>
<Link ) : (
key={course.id} <div className="flex flex-wrap justify-around gap-4">
className="lowercase scale-100 hover:scale-105 duration-300" {courses?.data.map((course) => (
href={`admin/curso/${course.id}`} <Link
> key={course.id}
<div className="border border-gray-100 pb-4 rounded-lg overflow-hidden"> className="lowercase scale-100 hover:scale-105 duration-300"
<Card.Image image={course.image.src} width={240} height={320}> href={`admin/curso/${course.id}`}
<Card.Title title={course.title} /> >
</Card.Image> <div className="border border-gray-100 pb-4 rounded-lg overflow-hidden">
<Button <Card.Image
variant="secondary" image={course.desktopBanner || ''}
className="flex gap-4 mt-4 mx-auto" width={240}
> height={320}
<Pencil /> >
<span className="uppercase">Editar</span> <Card.Title title={course.name} />
</Button> </Card.Image>
</div> <Button
</Link> variant="secondary"
))} className="flex gap-4 mt-4 mx-auto"
</div> >
<Pencil />
<span className="uppercase">Editar</span>
</Button>
</div>
</Link>
))}
</div>
)}
</div> </div>
</div> </div>
</section> </section>
......
import { s3 } from '@/lib/cloudflare'
import { PutObjectCommand } from '@aws-sdk/client-s3'
import { NextRequest, NextResponse } from 'next/server'
// const s3Client = new S3Client({
// region: 'auto',
// endpoint: process.env.NEXT_PUBLIC_CLOUDFLARE_ACCESS_ENDPOINT,
// credentials: {
// accessKeyId: process.env.NEXT_PUBLIC_CLOUDFLARE_ACCESS_KEY_ID!,
// secretAccessKey: process.env.NEXT_PUBLIC_CLOUDFLARE_SECRET_ACCESS_KEY!,
// },
// })
export async function POST(req: NextRequest) {
const formData = await req.formData()
const file = formData.get('file') as File
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 })
}
const arrayBuffer = await file.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
const fileName = `${Date.now()}-${file.name}`
try {
await s3.send(
new PutObjectCommand({
Bucket: process.env.NEXT_PUBLIC_CLOUDFLARE_BUCKET!,
Key: fileName,
Body: buffer,
ContentType: file.type,
}),
)
const fileUrl = `https://${process.env.NEXT_PUBLIC_CLOUDFLARE_URL_PUBLIC_BUCKET}/${fileName}`
return NextResponse.json({ url: fileUrl })
} catch (error) {
console.error(error)
return NextResponse.json({ error: 'Upload failed' }, { status: 500 })
}
}
...@@ -41,15 +41,15 @@ export default function Contact() { ...@@ -41,15 +41,15 @@ export default function Contact() {
<ul className="flex flex-col gap-4 mt-6"> <ul className="flex flex-col gap-4 mt-6">
<li className="flex items-center gap-4"> <li className="flex items-center gap-4">
<Clock4 className="text-green-800" /> <Clock4 className="text-green-800" />
<span>De XXh à XXh</span> <span>De segunda à sexta das 08h às 18h</span>
</li> </li>
<li className="flex items-center gap-4"> <li className="flex items-center gap-4">
<Smartphone className="text-green-800" /> <Smartphone className="text-green-800" />
<span>Telefones: (XX) XXXX-XXXX</span> <span>Telefones: (44) 9725-0427</span>
</li> </li>
<li className="flex items-center gap-4"> <li className="flex items-center gap-4">
<Mail className="text-green-800" /> <Mail className="text-green-800" />
<span>E-mail: sevenpro@sevenpro.com.br</span> <span>E-mail: id.sevenpro@gmail.com</span>
</li> </li>
</ul> </ul>
</div> </div>
......
...@@ -2,76 +2,37 @@ ...@@ -2,76 +2,37 @@
import Image from 'next/image' import Image from 'next/image'
import { useSearchParams } from 'next/navigation' import { useSearchParams } from 'next/navigation'
import bannerComunicacao from '../../public/images/banners/comunicacao.jpg'
import bannerEducacao from '../../public/images/banners/educacao.jpg'
import bannerNegocios from '../../public/images/banners/negocios.jpg'
import bannerSaude from '../../public/images/banners/saude.jpg'
import bannerStudants from '../../public/images/banners/students_banner.jpg' import bannerStudants from '../../public/images/banners/students_banner.jpg'
import bannerTecnologia from '../../public/images/banners/tecnologia.jpg'
import bannerTeologia from '../../public/images/banners/teologia.jpg'
import bannerComunicacaoMobile from '../../public/images/banners/comunicacao-mobile.jpg' import { getAreas } from '@/api/areas'
import bannerEducacaoMobile from '../../public/images/banners/educacao-mobile.jpg' import { useQuery } from '@tanstack/react-query'
import bannerNegociosMobile from '../../public/images/banners/negocios-mobile.jpg'
import bannerSaudeMobile from '../../public/images/banners/saude-mobile.jpg'
import bannerTecnologiaMobile from '../../public/images/banners/tecnologia-mobile.jpg'
import bannerTeologiaMobile from '../../public/images/banners/teologia-mobile.jpg'
export function BannerCategory() { export function BannerCategory() {
const searchParams = useSearchParams() const searchParams = useSearchParams()
const category = searchParams.get('categoria') const category = searchParams.get('area')
const { data: areas } = useQuery({ queryKey: ['areas'], queryFn: getAreas })
function desktopBannerCategory() { const area = areas?.data.find(
switch (category) { (item) => item.name.toLowerCase() === category?.toLowerCase(),
case 'tecnologia': )
return bannerTecnologia
case 'negocios':
return bannerNegocios
case 'comunicacao':
return bannerComunicacao
case 'educacao':
return bannerEducacao
case 'saude':
return bannerSaude
case 'teologia':
return bannerTeologia
default:
return bannerStudants
}
}
function mobileBannerCategory() {
switch (category) {
case 'tecnologia':
return bannerTecnologiaMobile
case 'negocios':
return bannerNegociosMobile
case 'comunicacao':
return bannerComunicacaoMobile
case 'educacao':
return bannerEducacaoMobile
case 'saude':
return bannerSaudeMobile
case 'teologia':
return bannerTeologiaMobile
default:
return bannerStudants
}
}
return ( return (
<div className="h-[300px] flex items-center justify-end"> <div className="h-[300px] flex items-center justify-end">
<Image <Image
alt="banner" alt="banner"
className="hidden md:flex w-full h-full object-cover" className="hidden md:flex w-full h-full object-cover"
src={desktopBannerCategory()} width={100}
height={100}
src={area?.desktopBanner || bannerStudants}
unoptimized unoptimized
/> />
<Image <Image
alt="banner" alt="banner"
className="flex md:hidden w-full h-full object-cover" className="flex md:hidden w-full h-full object-cover"
src={mobileBannerCategory()} width={100}
height={100}
src={area?.mobileBanner || bannerStudants}
unoptimized unoptimized
/> />
</div> </div>
......
...@@ -5,24 +5,25 @@ import { ...@@ -5,24 +5,25 @@ import {
BreadcrumbList, BreadcrumbList,
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator, BreadcrumbSeparator,
} from "@/components/ui/breadcrumb" } from '@/components/ui/breadcrumb'
import Link from "next/link" import Link from 'next/link'
type BreadcrumbProps = {
page: string
}
export default function BreadcrumbComponent() { export default function BreadcrumbComponent({ page }: BreadcrumbProps) {
return ( return (
<Breadcrumb> <Breadcrumb className="my-4">
<BreadcrumbList> <BreadcrumbList>
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbLink asChild> <BreadcrumbLink asChild>
<Link href="/admin"> <Link href="/admin">Admin</Link>
Admin
</Link>
</BreadcrumbLink> </BreadcrumbLink>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator /> <BreadcrumbSeparator />
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbPage>Área</BreadcrumbPage> <BreadcrumbPage>{page}</BreadcrumbPage>
</BreadcrumbItem> </BreadcrumbItem>
</BreadcrumbList> </BreadcrumbList>
</Breadcrumb> </Breadcrumb>
......
'use client'
import { Calendar, Clock4, User } from 'lucide-react' import { Calendar, Clock4, User } from 'lucide-react'
import { CoursesCard } from '@/utils/courses-array' import { getCourses } from '@/api/courses'
import { useQuery } from '@tanstack/react-query'
import { Card } from './card' import { Card } from './card'
import { import {
Carousel, Carousel,
...@@ -11,32 +14,48 @@ import { ...@@ -11,32 +14,48 @@ import {
} from './ui/carousel' } from './ui/carousel'
export function CarouselComponent() { export function CarouselComponent() {
const { data: courses } = useQuery({
queryKey: ['courses'],
queryFn: getCourses,
})
console.log('🚀 ~ Admin ~ courses:', courses)
return ( return (
<section> <section>
<Carousel> <Carousel>
<CarouselContent> {courses ? (
{CoursesCard.map((course) => ( <CarouselContent>
<CarouselItem {courses.data.slice(0, 10).map((course) => (
key={course.id} <CarouselItem
className="sm:basis-1/2 md:basis-1/3 lg:basis-1/4 xl:basis-1/5 flex justify-center" key={course.id}
> className="sm:basis-1/2 md:basis-1/3 lg:basis-1/4 xl:basis-1/5 flex justify-center"
<Card.Root link={`curso/${course.id}`}> >
<Card.Image image={course.image.src} width={240} height={320}> <Card.Root link={`curso/${course.id}`}>
<Card.Title title={course.title} /> <Card.Image
</Card.Image> image={course.mobileBanner || ''}
<Card.Content description={course.hours}> width={240}
<Card.Icon icon={Clock4} /> height={320}
</Card.Content> >
<Card.Content description={course.category}> <Card.Title title={course.name} />
<Card.Icon icon={User} /> </Card.Image>
</Card.Content> <Card.Content
<Card.Content description={course.calender}> description={`${String(course.workload)} horas`}
<Card.Icon icon={Calendar} /> >
</Card.Content> <Card.Icon icon={Clock4} />
</Card.Root> </Card.Content>
</CarouselItem> <Card.Content description={course?.audience?.name || ''}>
))} <Card.Icon icon={User} />
</CarouselContent> </Card.Content>
<Card.Content description={course?.category?.name || ''}>
<Card.Icon icon={Calendar} />
</Card.Content>
</Card.Root>
</CarouselItem>
))}
</CarouselContent>
) : (
<p>Não há cursos disponíveis</p>
)}
<CarouselPrevious /> <CarouselPrevious />
<CarouselNext /> <CarouselNext />
</Carousel> </Carousel>
......
'use client'
import { TextField } from '@mui/material'
import { StyledInputs } from './mui/styled-inputs'
import {
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from './ui/alert-dialog'
import { Button } from './ui/button'
type AlertDialogProps = {
title: string
description: string
value?: string
handleClick: () => void
handleClickDelete?: () => void
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
}
export default function ModalInputs({
title,
description,
value,
handleClick,
handleClickDelete,
onChange,
}: AlertDialogProps) {
return (
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<TextField
label="Nome"
type="text"
value={value}
onChange={onChange}
variant="standard"
className="flex"
sx={StyledInputs({ color: '#26AAA7' })}
/>
<AlertDialogFooter className="mt-4 w-full flex !justify-between">
{handleClickDelete && (
<Button
onClick={handleClickDelete}
variant="outline"
className="text-gray-50 bg-red-500 hover:bg-red-500/80"
>
Deletar
</Button>
)}
<div className='flex gap-4'>
<AlertDialogCancel>Cancelar</AlertDialogCancel>
<Button
onClick={handleClick}
variant="outline"
className="text-gray-50 bg-green-500 hover:bg-green-500/80"
>
Salvar
</Button>
</div>
</AlertDialogFooter>
</AlertDialogContent>
)
}
'use client'
import { getAreas } from '@/api/areas'
import { useQuery } from '@tanstack/react-query'
import { NavLinkSearchParams } from './nav-link-search-params' import { NavLinkSearchParams } from './nav-link-search-params'
export function NavLinkCategory() { export function NavLinkCategory() {
const { data: areas } = useQuery({ queryKey: ['areas'], queryFn: getAreas })
return ( return (
<nav className="container my-10"> <nav className="container my-10">
<ul className="flex flex-wrap justify-center items-center gap-8"> <ul className="flex flex-wrap justify-center items-center gap-8">
<li> {areas?.data.map((area) => (
<NavLinkSearchParams <li key={area.id}>
variant="default" <NavLinkSearchParams
href="tecnologia" variant="default"
className="uppercase" href={area.name.toLowerCase()}
> className="uppercase"
Tecnologia >
</NavLinkSearchParams> {area.name}
</li> </NavLinkSearchParams>
<li> </li>
<NavLinkSearchParams ))}
variant="default"
href="negocios"
className="uppercase"
>
Negócios
</NavLinkSearchParams>
</li>
<li>
<NavLinkSearchParams
variant="default"
href="saude"
className="uppercase"
>
Saúde
</NavLinkSearchParams>
</li>
<li>
<NavLinkSearchParams
variant="default"
href="educacao"
className="uppercase"
>
Educação
</NavLinkSearchParams>
</li>
<li>
<NavLinkSearchParams
variant="default"
href="comunicacao"
className="uppercase"
>
Comunicação
</NavLinkSearchParams>
</li>
<li>
<NavLinkSearchParams
variant="default"
href="teologia"
className="uppercase"
>
Teologia
</NavLinkSearchParams>
</li>
</ul> </ul>
</nav> </nav>
) )
......
...@@ -23,7 +23,7 @@ interface NavLinkProps extends LinkProps { ...@@ -23,7 +23,7 @@ interface NavLinkProps extends LinkProps {
export function NavLinkSearchParams(props: NavLinkProps) { export function NavLinkSearchParams(props: NavLinkProps) {
const searchParams = useSearchParams() const searchParams = useSearchParams()
const category = searchParams.get('categoria') const category = searchParams.get('area')
return ( return (
<Button <Button
...@@ -32,7 +32,7 @@ export function NavLinkSearchParams(props: NavLinkProps) { ...@@ -32,7 +32,7 @@ export function NavLinkSearchParams(props: NavLinkProps) {
variant={category === props.href ? 'third' : props.variant} variant={category === props.href ? 'third' : props.variant}
asChild asChild
> >
<Link href={{ query: { categoria: props.href as string } }}> <Link href={{ query: { area: props.href as string } }}>
{props.children} {props.children}
</Link> </Link>
</Button> </Button>
......
// import * as z from 'zod'
// const envSchema = z.object({
// NEXT_PUBLIC_URL_API: z.string().url(),
// NEXT_PUBLIC_CLOUDFLARE_ACCESS_ENDPOINT: z.string().url(),
// NEXT_PUBLIC_CLOUDFLARE_ACCESS_TOKEN_VALUE: z.string(),
// NEXT_PUBLIC_CLOUDFLARE_ACCESS_KEY_ID: z.string(),
// NEXT_PUBLIC_CLOUDFLARE_SECRET_ACCESS_KEY: z.string(),
// NEXT_PUBLIC_CLOUDFLARE_ACCESS_ACCOUNT_ID: z.string(),
// })
// export const env = envSchema.parse(process.env)
import { S3Client } from '@aws-sdk/client-s3'
export const s3 = new S3Client({
region: 'auto',
endpoint: process.env.NEXT_PUBLIC_CLOUDFLARE_ACCESS_ENDPOINT,
credentials: {
accessKeyId: process.env.NEXT_PUBLIC_CLOUDFLARE_ACCESS_KEY_ID!,
secretAccessKey: process.env.NEXT_PUBLIC_CLOUDFLARE_SECRET_ACCESS_KEY!,
},
})
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