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 @@
"lint": "eslint . --fix"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.758.0",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@hookform/resolvers": "^3.10.0",
......@@ -28,6 +29,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.2.0",
"embla-carousel-autoplay": "^8.1.8",
"embla-carousel-react": "^8.1.8",
"lucide-react": "^0.383.0",
......
import { api } from '@/lib/axios'
// interface FileBanner {
// name: string
// }
export type AreasProps = {
id?: string
name: string
desktopBanner: string
mobileBanner: string
// desktopBanner?: { [key: string]: FileBanner }
// mobileBanner?: { [key: string]: FileBanner }
desktopBanner: string | null
mobileBanner: string | null
}
export async function createAreas({
......@@ -31,7 +26,7 @@ export async function editArea({
desktopBanner,
mobileBanner,
}: AreasProps) {
const area = await api.patch<AreasProps[]>(`/areas/${id}`, {
const area = await api.put<AreasProps[]>(`/areas/${id}`, {
name,
desktopBanner,
mobileBanner,
......
import { api } from '@/lib/axios'
export type AudiencesNameProps = {
name: string
}
export type AudiencesProps = {
id: string
name: string
}
export async function createAudiences({
name,
}: AudiencesNameProps) {
const audiences = await api.post<AudiencesNameProps>('/audiences', {
name,
})
return audiences
}
export async function getAudiences() {
const audiences = await api.get<AudiencesProps[]>('/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'
import { Search } from 'lucide-react'
import Link from 'next/link'
import { About } from '@/components/about'
......@@ -8,16 +7,15 @@ import { Banner } from '@/components/banner'
import { CarouselComponent } from '@/components/corousel-component'
import { CourseCategory } from '@/components/course-category'
import { Differences } from '@/components/differences'
import { InputMui } from '@/components/mui/inputs'
import { NavLinkCategory } from '@/components/nav-link-category'
import { SignUp } from '@/components/sign-up'
import { SkeletonSerachParams } from '@/components/skeleton-serach-params'
import { Button } from '@/components/ui/button'
import { Suspense } from 'react'
import { useForm } from 'react-hook-form'
// import { useForm } from 'react-hook-form'
export default function Home() {
const { register } = useForm()
// const { register } = useForm()
return (
<>
......@@ -29,7 +27,7 @@ export default function Home() {
<Suspense fallback={<SkeletonSerachParams />}>
<NavLinkCategory />
</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
label="O que você quer aprender hoje?"
variant="standard"
......@@ -40,14 +38,14 @@ export default function Home() {
register={register}
/>
<Search size={24} />
</div>
</div> */}
<CarouselComponent />
<Button
variant="secondary"
className="uppercase mx-auto my-8"
asChild
>
<Link href="#">Ver todos os cursos</Link>
<Link href="/estudantes">Ver todos os cursos</Link>
</Button>
</div>
<CourseCategory />
......
......@@ -5,28 +5,37 @@ import BreadcrumbComponent from '@/components/breadcrumb-component'
import InputFile from '@/components/input-file'
import { LoadingSpinIcon } from '@/components/loading-spin-icon'
import ModalDialog from '@/components/modal-dialog'
import { StyledInputs } from '@/components/mui/styled-inputs'
import { AlertDialog, AlertDialogTrigger } from '@/components/ui/alert-dialog'
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 { Trash } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
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 { register, handleSubmit, watch } = useForm<AreasProps>()
const {
register,
handleSubmit,
formState: { errors },
} = useForm<AreasProps>()
const router = useRouter()
const data = watch()
const valueDesktopBanner = data?.desktopBanner?.['0']?.name
const valueMobileBanner = data?.mobileBanner?.['0']?.name
console.log('🚀 ~ AreaId ~ errors:', errors)
const queryClient = useQueryClient()
const { data: area, isLoading } = useQuery({
queryKey: ['area', params.id],
queryKey: ['areas', params.id],
queryFn: () => getAreasId(params.id),
enabled: !!params.id,
})
......@@ -49,12 +58,34 @@ export default function AreaId() {
},
})
function onSubmit(data: AreasProps) {
const body = {
async function onSubmit(data: AreasProps) {
const urlDesktop = await handleUpload(desktopBanner)
const urlMobile = await handleUpload(mobileBanner)
console.log(data)
const dataArea = {
...data,
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() {
......@@ -71,8 +102,8 @@ export default function AreaId() {
}
return (
<section className="container py-20 space-y-10">
<BreadcrumbComponent />
<section className="container py-10">
<BreadcrumbComponent page="Área" />
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex items-center gap-6">
<h1 className="text-3xl font-bold">Editar Área</h1>
......@@ -103,36 +134,52 @@ export default function AreaId() {
</AlertDialog>
</div>
<div className="w-full mt-16">
<Input
<TextField
label="Nome da área"
className="border-green-400"
type="text"
defaultValue={area?.data.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 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>Formatos aceitos: .png, .jpg</span>
<span>Formatos aceitos: .png, .jpg, .jpeg</span>
<InputFile
id="desktopBanner"
values={valueDesktopBanner}
label="Adicionar banner"
{...register('desktopBanner')}
label={!desktopBanner ? 'Atualizar banner' : desktopBanner.name}
onChange={(e) => setDesktopBanner(e.target.files?.[0] || null)}
/>
</div>
<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>Formatos aceitos: .png, .jpg</span>
<span>Formatos aceitos: .png, .jpg, .jpeg</span>
<InputFile
id="mobileBanner"
values={valueMobileBanner}
label="Adicionar banner"
{...register('mobileBanner')}
label={!mobileBanner ? 'Atualizar banner' : mobileBanner.name}
onChange={(e) => setMobileBanner(e.target.files?.[0] || null)}
/>
</div>
</div>
......
'use client'
import { AreasProps, createAreas } from '@/api/areas'
import BreadcrumbComponent from '@/components/breadcrumb-component'
import InputFile from '@/components/input-file'
import { LoadingSpinIcon } from '@/components/loading-spin-icon'
import { StyledInputs } from '@/components/mui/styled-inputs'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { zodResolver } from '@hookform/resolvers/zod'
import { TextField } from '@mui/material'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
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() {
const { register, handleSubmit, watch } = useForm<AreasProps>()
const router = useRouter()
const [desktopBanner, setDesktopBanner] = useState<File | null>(null)
const [mobileBanner, setMobileBanner] = useState<File | null>(null)
console.log('🚀 ~ Area ~ mobileBanner:', mobileBanner)
const data = watch()
const valueMobileBanner = data?.mobileBanner?.['0']?.name
const valueDesktopBanner = data?.desktopBanner?.['0']?.name
const {
register,
handleSubmit,
setError,
formState: { errors },
} = useForm<AreasProps>({
resolver: zodResolver(FormSchema),
})
console.log('🚀 ~ Area ~ error:', errors)
const router = useRouter()
const queryClient = useQueryClient()
......@@ -30,29 +47,51 @@ export default function Area() {
},
})
function onSubmit(data: AreasProps) {
// console.log('🚀 ~ onSubmit ~ data:', data)
// const fileMobile = data.mobileBanner?.[0]
// const fileDesktop = data.desktopBanner?.[0]
async function onSubmit(data: AreasProps) {
if (!desktopBanner) {
setError('desktopBanner', {
type: 'manual',
message: 'Foto desktop obrigatória!',
})
return
}
if (!mobileBanner) {
setError('mobileBanner', {
type: 'manual',
message: 'Foto mobile obrigatória!',
})
return
}
// const formData = new FormData()
// formData.append('mobileBanner', fileMobile, fileMobile?.name)
// formData.append('desktopBanner', fileDesktop, fileDesktop?.name)
const urlDesktop = await handleUpload(desktopBanner)
const urlMobile = await handleUpload(mobileBanner)
// 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',
desktopBanner: urlDesktop,
mobileBanner: urlMobile,
}
console.log('🚀 ~ onSubmit ~ data:', dataArea)
mutation.mutate(dataArea)
mutation.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
}
return (
<section className="container py-20 space-y-10">
<section className="container py-10">
<BreadcrumbComponent page="Área" />
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex items-center gap-6">
<h1 className="text-3xl font-bold">Criar Área</h1>
......@@ -68,31 +107,45 @@ export default function Area() {
type="text"
className="flex"
{...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 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>Formatos aceitos: .png, .jpg, .jpeg</span>
<InputFile
id="desktopBanner"
values={valueDesktopBanner}
label="Adicionar banner"
{...register('desktopBanner')}
label={!desktopBanner ? 'Adicionar banner' : desktopBanner.name}
onChange={(e) => setDesktopBanner(e.target.files?.[0] || null)}
/>
</div>
<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>Formatos aceitos: .png, .jpg, .jpeg</span>
<InputFile
id="mobileBanner"
values={valueMobileBanner}
label="Adicionar banner"
{...register('mobileBanner')}
label={!mobileBanner ? 'Adicionar banner' : mobileBanner.name}
onChange={(e) => setMobileBanner(e.target.files?.[0] || null)}
/>
</div>
</div>
......
This diff is collapsed.
This diff is collapsed.
'use client'
import { getAreas } from '@/api/areas'
import { createAudiences, deleteAudience, editAudienceId, getAudiences } from '@/api/audiences'
import { getCourses } from '@/api/courses'
import { Card } from '@/components/card'
import ModalInputs from '@/components/modal-inputs'
import { InputMui } from '@/components/mui/inputs'
import { AlertDialog, AlertDialogTrigger } from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import { AuthContext } from '@/contexts/auth-context'
import { CoursesCard } from '@/utils/courses-array'
import { useQuery } from '@tanstack/react-query'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Pencil, Search } from 'lucide-react'
import Link from 'next/link'
import { useContext } from 'react'
import { useContext, useState } from 'react'
import { useForm } from 'react-hook-form'
// 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',
},
]
import { toast } from 'sonner'
export default function Admin() {
const [open, setOpen] = useState<boolean>(false);
const [nameAudience, setNameAudience] = useState('')
const { register } = useForm()
const { user } = useContext(AuthContext)
console.log('🚀 ~ Admin ~ user:', user)
const queryClient = useQueryClient()
const {
data: areas,
......@@ -60,6 +30,58 @@ export default function Admin() {
isError,
} = 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) {
return <span>Loading...</span>
}
......@@ -95,7 +117,62 @@ export default function Admin() {
</ul>
</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">
<ul className="space-y-4">
{banners.map((banner) => (
......@@ -108,7 +185,7 @@ export default function Admin() {
</li>
))}
</ul>
</nav>
</nav> */}
</aside>
<div className="flex-1 ml-4 pl-6 border-l border-gray-50">
......@@ -128,28 +205,36 @@ export default function Admin() {
/>
<Search size={24} className="-ml-6" />
</div>
<div className="flex flex-wrap justify-around gap-4">
{CoursesCard.map((course) => (
<Link
key={course.id}
className="lowercase scale-100 hover:scale-105 duration-300"
href={`admin/curso/${course.id}`}
>
<div className="border border-gray-100 pb-4 rounded-lg overflow-hidden">
<Card.Image image={course.image.src} width={240} height={320}>
<Card.Title title={course.title} />
</Card.Image>
<Button
variant="secondary"
className="flex gap-4 mt-4 mx-auto"
>
<Pencil />
<span className="uppercase">Editar</span>
</Button>
</div>
</Link>
))}
</div>
{courses?.data.length === 0 ? (
<p className="text-yellow-100">Não há curso cadastrado!</p>
) : (
<div className="flex flex-wrap justify-around gap-4">
{courses?.data.map((course) => (
<Link
key={course.id}
className="lowercase scale-100 hover:scale-105 duration-300"
href={`admin/curso/${course.id}`}
>
<div className="border border-gray-100 pb-4 rounded-lg overflow-hidden">
<Card.Image
image={course.desktopBanner || ''}
width={240}
height={320}
>
<Card.Title title={course.name} />
</Card.Image>
<Button
variant="secondary"
className="flex gap-4 mt-4 mx-auto"
>
<Pencil />
<span className="uppercase">Editar</span>
</Button>
</div>
</Link>
))}
</div>
)}
</div>
</div>
</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() {
<ul className="flex flex-col gap-4 mt-6">
<li className="flex items-center gap-4">
<Clock4 className="text-green-800" />
<span>De XXh à XXh</span>
<span>De segunda à sexta das 08h às 18h</span>
</li>
<li className="flex items-center gap-4">
<Smartphone className="text-green-800" />
<span>Telefones: (XX) XXXX-XXXX</span>
<span>Telefones: (44) 9725-0427</span>
</li>
<li className="flex items-center gap-4">
<Mail className="text-green-800" />
<span>E-mail: sevenpro@sevenpro.com.br</span>
<span>E-mail: id.sevenpro@gmail.com</span>
</li>
</ul>
</div>
......
......@@ -2,76 +2,37 @@
import Image from 'next/image'
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 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 bannerEducacaoMobile from '../../public/images/banners/educacao-mobile.jpg'
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'
import { getAreas } from '@/api/areas'
import { useQuery } from '@tanstack/react-query'
export function BannerCategory() {
const searchParams = useSearchParams()
const category = searchParams.get('categoria')
const category = searchParams.get('area')
const { data: areas } = useQuery({ queryKey: ['areas'], queryFn: getAreas })
function desktopBannerCategory() {
switch (category) {
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
}
}
const area = areas?.data.find(
(item) => item.name.toLowerCase() === category?.toLowerCase(),
)
return (
<div className="h-[300px] flex items-center justify-end">
<Image
alt="banner"
className="hidden md:flex w-full h-full object-cover"
src={desktopBannerCategory()}
width={100}
height={100}
src={area?.desktopBanner || bannerStudants}
unoptimized
/>
<Image
alt="banner"
className="flex md:hidden w-full h-full object-cover"
src={mobileBannerCategory()}
width={100}
height={100}
src={area?.mobileBanner || bannerStudants}
unoptimized
/>
</div>
......
......@@ -5,24 +5,25 @@ import {
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import Link from "next/link"
} from '@/components/ui/breadcrumb'
import Link from 'next/link'
type BreadcrumbProps = {
page: string
}
export default function BreadcrumbComponent() {
export default function BreadcrumbComponent({ page }: BreadcrumbProps) {
return (
<Breadcrumb>
<Breadcrumb className="my-4">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href="/admin">
Admin
</Link>
<Link href="/admin">Admin</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Área</BreadcrumbPage>
<BreadcrumbPage>{page}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
......
'use client'
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 {
Carousel,
......@@ -11,32 +14,48 @@ import {
} from './ui/carousel'
export function CarouselComponent() {
const { data: courses } = useQuery({
queryKey: ['courses'],
queryFn: getCourses,
})
console.log('🚀 ~ Admin ~ courses:', courses)
return (
<section>
<Carousel>
<CarouselContent>
{CoursesCard.map((course) => (
<CarouselItem
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.Title title={course.title} />
</Card.Image>
<Card.Content description={course.hours}>
<Card.Icon icon={Clock4} />
</Card.Content>
<Card.Content description={course.category}>
<Card.Icon icon={User} />
</Card.Content>
<Card.Content description={course.calender}>
<Card.Icon icon={Calendar} />
</Card.Content>
</Card.Root>
</CarouselItem>
))}
</CarouselContent>
{courses ? (
<CarouselContent>
{courses.data.slice(0, 10).map((course) => (
<CarouselItem
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.mobileBanner || ''}
width={240}
height={320}
>
<Card.Title title={course.name} />
</Card.Image>
<Card.Content
description={`${String(course.workload)} horas`}
>
<Card.Icon icon={Clock4} />
</Card.Content>
<Card.Content description={course?.audience?.name || ''}>
<Card.Icon icon={User} />
</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 />
<CarouselNext />
</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'
export function NavLinkCategory() {
const { data: areas } = useQuery({ queryKey: ['areas'], queryFn: getAreas })
return (
<nav className="container my-10">
<ul className="flex flex-wrap justify-center items-center gap-8">
<li>
<NavLinkSearchParams
variant="default"
href="tecnologia"
className="uppercase"
>
Tecnologia
</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>
{areas?.data.map((area) => (
<li key={area.id}>
<NavLinkSearchParams
variant="default"
href={area.name.toLowerCase()}
className="uppercase"
>
{area.name}
</NavLinkSearchParams>
</li>
))}
</ul>
</nav>
)
......
......@@ -23,7 +23,7 @@ interface NavLinkProps extends LinkProps {
export function NavLinkSearchParams(props: NavLinkProps) {
const searchParams = useSearchParams()
const category = searchParams.get('categoria')
const category = searchParams.get('area')
return (
<Button
......@@ -32,7 +32,7 @@ export function NavLinkSearchParams(props: NavLinkProps) {
variant={category === props.href ? 'third' : props.variant}
asChild
>
<Link href={{ query: { categoria: props.href as string } }}>
<Link href={{ query: { area: props.href as string } }}>
{props.children}
</Link>
</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