diff --git a/client/src/router/index.js b/client/src/router/index.js index 1fc1546..8fd9172 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -4,11 +4,7 @@ import { adminRoutes } from './routes/admin' import { professorRoutes } from './routes/professor' import { funcionarioRoutes } from './routes/funcionario' import { empresaRoutes } from './routes/empresa' - -import Home from '../views/Home.vue'; -import NotFound from '../views/NotFound.vue'; -import PublicPerfilAluno from '../views/PerfilAluno.vue'; -import PublicPerfilProfessor from '../views/PerfilProfessor.vue'; +import { sharedRoutes } from './routes/shared' const routes = [ ...alunoRoutes, @@ -16,31 +12,7 @@ const routes = [ ...professorRoutes, ...funcionarioRoutes, ...empresaRoutes, - { - path: '/:pathMatch(.*)*', - name: 'NotFound', - component: NotFound - }, - { - path: '/home', - name: 'Home', - component: Home - }, - { - path: '/', - name: 'Home2', - component: Home - }, - { - path: "/aluno/profile/:name", - name: "PublicPerfilAluno", - component: PublicPerfilAluno - }, - { - path: "/professor/profile/:name", - name: "PublicPerfilProfessor", - component: PublicPerfilProfessor - } + ...sharedRoutes ]; const router = createRouter({ diff --git a/client/src/router/routes/shared.js b/client/src/router/routes/shared.js new file mode 100644 index 0000000..c8eddad --- /dev/null +++ b/client/src/router/routes/shared.js @@ -0,0 +1,32 @@ +import Home from '../../views/shared/Home.vue'; +import NotFound from '../../views/shared/NotFound.vue'; +import PublicPerfilAluno from '../../views/shared/PerfilAluno.vue'; +import PublicPerfilProfessor from '../../views/shared/PerfilProfessor.vue'; + +export const sharedRoutes = [ + { + path: '/home', + name: 'Home', + component: Home + }, + { + path: '/', + name: 'Home2', + component: Home + }, + { + path: "/aluno/profile/:name", + name: "PublicPerfilAluno", + component: PublicPerfilAluno + }, + { + path: "/professor/profile/:name", + name: "PublicPerfilProfessor", + component: PublicPerfilProfessor + }, + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: NotFound + } +] \ No newline at end of file diff --git a/client/src/scss/abstracts/_mixins.scss b/client/src/scss/abstracts/_mixins.scss index bd935a1..5ac0b40 100644 --- a/client/src/scss/abstracts/_mixins.scss +++ b/client/src/scss/abstracts/_mixins.scss @@ -35,6 +35,12 @@ } } +@mixin m-screen($width) { + @media screen and (max-width: $width) { + @content; + } +} + @mixin line { &:after { content: ''; diff --git a/client/src/scss/base/_base.scss b/client/src/scss/base/_base.scss index ee062f9..d986c46 100644 --- a/client/src/scss/base/_base.scss +++ b/client/src/scss/base/_base.scss @@ -20,4 +20,5 @@ main { img, button, a{ -webkit-user-select: none; user-select: none; + transition: .2s; } \ No newline at end of file diff --git a/client/src/scss/layouts/_cards.scss b/client/src/scss/layouts/_cards.scss index 9a99c93..2d4f672 100644 --- a/client/src/scss/layouts/_cards.scss +++ b/client/src/scss/layouts/_cards.scss @@ -2,8 +2,10 @@ background-color: $terciary-color-dark; padding: 100px; min-height: 400px; - @include flex(column, flex-start, center); + @include m-screen(1400px){ + min-height: 360px; + } h1, h2 { @@ -12,14 +14,20 @@ h1 { font-size: 2rem; - @include font-inter(700); color: $secondary-color-orange; + @include font-inter(700); + @include m-screen(1400px){ + font-size: 1.8rem; + } } h2 { font-size: 1.3rem; - @include font-inter(300); color: $font-color-dark-2; + @include font-inter(300); + @include m-screen(1400px){ + font-size: 1.1rem; + } } .wrapper { @@ -27,6 +35,10 @@ width: 100%; margin: 80px 0px 20px 0px; position: relative; + @include m-screen(1400px){ + margin: 60px 0px 18px 0px; + max-width: 1000px; + } } .wrapper i { @@ -42,6 +54,9 @@ filter: invert(100%); opacity: 0.7; transition: transform 0.1s linear; + @include m-screen(1400px){ + background-size: 30px; + } } .wrapper i:first-child { @@ -95,6 +110,9 @@ padding-bottom: 15px; flex-direction: column; border-radius: 8px; + @include m-screen(1400px){ + height: 300px; + } .img { background: $primary-color-dark; @@ -115,6 +133,9 @@ font-size: 1.5rem; color: $font-color-dark; padding: 10px; + @include m-screen(1400px){ + font-size: 1.3rem; + } } span { @@ -122,6 +143,9 @@ font-size: 0.9rem; color: $font-color-dark-2; padding: 10px; + @include m-screen(1400px){ + font-size: 0.7rem; + } } } } diff --git a/client/src/scss/layouts/_contactUs.scss b/client/src/scss/layouts/_contactUs.scss index 4b0a020..0b9596e 100644 --- a/client/src/scss/layouts/_contactUs.scss +++ b/client/src/scss/layouts/_contactUs.scss @@ -14,6 +14,21 @@ color: $secondary-color-orange; @include font-inter(500); font-size: 2rem; + @include m-screen(1420px) { + font-size: 1.8rem; + } + @include m-screen(1120px) { + font-size: 1.6rem; + } + } + + p { + @include m-screen(1420px) { + font-size: 0.8rem; + } + @include m-screen(1120px) { + font-size: 0.7rem; + } } p:last-child { @@ -32,6 +47,18 @@ padding: 8px; min-width: 350px; border-radius: 3px; + @include m-screen(1420px) { + padding: 7.2px; + min-width: 315px; + border-radius: 3px; + font-size: 0.8rem; + } + @include m-screen(1120px) { + padding: 6.4px; + min-width: 280px; + border-radius: 3px; + font-size: 0.7rem; + } &:focus { border: solid 1px $secondary-color-orange; @@ -48,6 +75,20 @@ min-width: 350px; border-radius: 3px; resize: none; + @include m-screen(1420px) { + padding: 7.2px; + min-width: 315px; + border-radius: 3px; + font-size: 0.8rem; + height: calc(1.5em * 7); + } + @include m-screen(1120px) { + padding: 6.4px; + min-width: 280px; + border-radius: 3px; + font-size: 0.7rem; + height: calc(1.5em * 6); + } &:focus { border: solid 1px $secondary-color-orange; @@ -62,12 +103,20 @@ padding: 8px 25px; border-radius: 3px; font-size: 1rem; - color: $font-color-dark; + color: $secondary-color-dark; background-color: $primary-color-orange; border: none; border: solid 1px $primary-color-orange; transition: 0.1s linear; cursor: pointer; + @include m-screen(1420px) { + font-size: 0.8rem; + padding: 7px 20px; + } + @include m-screen(1120px) { + font-size: 0.7rem; + padding: 6px 15px; + } &:hover { background-color: transparent; @@ -80,6 +129,12 @@ * { margin-top: 10px; + @include m-screen(1420px) { + margin-top: 8px; + } + @include m-screen(1120px) { + margin-top: 7px; + } } } } diff --git a/client/src/scss/layouts/_footer.scss b/client/src/scss/layouts/_footer.scss index 90bd4ad..edad5d5 100644 --- a/client/src/scss/layouts/_footer.scss +++ b/client/src/scss/layouts/_footer.scss @@ -3,9 +3,20 @@ footer { height: 25vh; @include flex(column, space-evenly, center); @include font-inter(200); - + @include m-screen(1400px) { + font-size: 0.8rem; + } + @include m-screen(1120px) { + font-size: 0.7rem; + } img { height: 40px; + @include m-screen(1400px) { + height: 34px; + } + @include m-screen(1120px) { + height: 30px; + } } nav ul { diff --git a/client/src/scss/layouts/_header.scss b/client/src/scss/layouts/_header.scss index 3500939..f84b0f0 100644 --- a/client/src/scss/layouts/_header.scss +++ b/client/src/scss/layouts/_header.scss @@ -29,6 +29,10 @@ header { @include flex(column, center, flex-start); @include font-inter(400); @include line; + @include m-screen(1120px){ + font-size: 0.7rem; + margin-inline: 8px; + } } li:nth-child(2) { @@ -44,6 +48,9 @@ header { transition: 0.1s linear; @include line; @include flex-center; + @include m-screen(1120px){ + padding: 8px 18px; + } &:after { height: 0; diff --git a/client/src/scss/pages/PerfilProfessor.scss b/client/src/scss/pages/PerfilProfessor.scss deleted file mode 100644 index 6dc3d12..0000000 --- a/client/src/scss/pages/PerfilProfessor.scss +++ /dev/null @@ -1,5 +0,0 @@ -main { - height: calc(100vh - 80px); - background-color: $primary-color-dark; - @include flex-center; -} \ No newline at end of file diff --git a/client/src/scss/pages/_home.scss b/client/src/scss/pages/shared/_home.scss similarity index 62% rename from client/src/scss/pages/_home.scss rename to client/src/scss/pages/shared/_home.scss index b27d7be..9a2d9d1 100644 --- a/client/src/scss/pages/_home.scss +++ b/client/src/scss/pages/shared/_home.scss @@ -8,6 +8,12 @@ main { @include font-inter(700); letter-spacing: .3cm; color: $primary-color-orange; + @include m-screen(1400px){ + padding-top: 18px; + } + @include m-screen(1120px){ + padding-top: 16px; + } } .content { @@ -24,6 +30,12 @@ main { * { margin: 20px 0; + @include m-screen(1400px){ + margin: 18px 0; + } + @include m-screen(1120px){ + margin: 16px 0; + } } .info { @@ -33,10 +45,21 @@ main { color: $secondary-color-orange; font-size: 2rem; @include font-inter(700); + @include m-screen(1400px){ + font-size: 1.8rem; + width: 90%; + } + @include m-screen(1120px){ + font-size: 1.6rem; + } } p { width: 70%; + @include m-screen(1120px){ + font-size: 0.8rem; + width: 60%; + } } a { @@ -52,16 +75,30 @@ main { &:hover { @include color($secondary-color-dark, $primary-color-orange); } + + @include m-screen(1120px){ + font-size: 0.7rem; + padding: 8px 18px; + } } } } #box2 { - background-image: url(../assets/imgs/imageMain.png); + background-image: url(../../assets/imgs/imageMain.png); background-position: center; background-size: 50%; background-repeat: no-repeat; + @include m-screen(1400px){ + width: 40%; + } + } + + #box1{ + @include m-screen(1400px){ + width: 60%; + } } } diff --git a/client/src/scss/pages/_notFound.scss b/client/src/scss/pages/shared/_notFound.scss similarity index 96% rename from client/src/scss/pages/_notFound.scss rename to client/src/scss/pages/shared/_notFound.scss index 7fefa66..59e40e4 100644 --- a/client/src/scss/pages/_notFound.scss +++ b/client/src/scss/pages/shared/_notFound.scss @@ -12,7 +12,7 @@ main { .img { width: 30%; height: 100%; - background-image: url(../assets/imgs/image404.png); + background-image: url(../../assets/imgs/image404.png); background-position: center; background-size: 100%; background-repeat: no-repeat; diff --git a/client/src/scss/pages/_perfilAluno.scss b/client/src/scss/pages/shared/_perfilAluno.scss similarity index 100% rename from client/src/scss/pages/_perfilAluno.scss rename to client/src/scss/pages/shared/_perfilAluno.scss diff --git a/client/src/scss/pages/_perfilProfessor.scss b/client/src/scss/pages/shared/_perfilProfessor.scss similarity index 100% rename from client/src/scss/pages/_perfilProfessor.scss rename to client/src/scss/pages/shared/_perfilProfessor.scss diff --git a/client/src/scss/pages/_rankings.scss b/client/src/scss/pages/shared/_rankings.scss similarity index 100% rename from client/src/scss/pages/_rankings.scss rename to client/src/scss/pages/shared/_rankings.scss diff --git a/client/src/views/Home.vue b/client/src/views/shared/Home.vue similarity index 79% rename from client/src/views/Home.vue rename to client/src/views/shared/Home.vue index 67fbd4e..6fa25df 100644 --- a/client/src/views/Home.vue +++ b/client/src/views/shared/Home.vue @@ -1,6 +1,6 @@ \ No newline at end of file diff --git a/client/src/views/NotFound.vue b/client/src/views/shared/NotFound.vue similarity index 85% rename from client/src/views/NotFound.vue rename to client/src/views/shared/NotFound.vue index de4fba9..bfa260e 100644 --- a/client/src/views/NotFound.vue +++ b/client/src/views/shared/NotFound.vue @@ -21,8 +21,8 @@ \ No newline at end of file diff --git a/client/src/views/PerfilAluno.vue b/client/src/views/shared/PerfilAluno.vue similarity index 63% rename from client/src/views/PerfilAluno.vue rename to client/src/views/shared/PerfilAluno.vue index 5d7c953..4fdb397 100644 --- a/client/src/views/PerfilAluno.vue +++ b/client/src/views/shared/PerfilAluno.vue @@ -10,10 +10,10 @@ \ No newline at end of file diff --git a/client/src/views/PerfilProfessor.vue b/client/src/views/shared/PerfilProfessor.vue similarity index 63% rename from client/src/views/PerfilProfessor.vue rename to client/src/views/shared/PerfilProfessor.vue index 33d816b..5a2d288 100644 --- a/client/src/views/PerfilProfessor.vue +++ b/client/src/views/shared/PerfilProfessor.vue @@ -10,10 +10,10 @@ \ No newline at end of file diff --git a/client/src/views/Rankings.vue b/client/src/views/shared/Rankings.vue similarity index 100% rename from client/src/views/Rankings.vue rename to client/src/views/shared/Rankings.vue diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index b0ef3e2..287a045 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -56,6 +56,7 @@ model Vinculo { professorId String? vinculoComAlunoId String? vinculoComProfessorId String? + accepted Boolean @default(false) aluno Aluno? @relation("AlunoVinculo", fields: [alunoId], references: [id]) professor Professor? @relation("ProfessorVinculo", fields: [professorId], references: [id]) vinculoComAluno Aluno? @relation("VinculoComAluno", fields: [vinculoComAlunoId], references: [id]) diff --git a/server/src/modules/controllers/sharedControllers.ts b/server/src/modules/controllers/sharedControllers.ts new file mode 100644 index 0000000..bc8ab2a --- /dev/null +++ b/server/src/modules/controllers/sharedControllers.ts @@ -0,0 +1,27 @@ +import { Request, Response } from "express"; +import { CreateVinculoUseCase } from "../services/shared/CreateVinculoUseCase"; +import { AcceptVinculoUseCase } from "../services/shared/AcceptVinculoUseCase"; + +export class CreateVinculoController { + async handle(req: Request, res: Response) { + const { email1, email2, identifier1, identifier2 } = req.body; + + const createVinculoUseCase = new CreateVinculoUseCase(); + + const result = await createVinculoUseCase.execute({ email1, email2, identifier1, identifier2 }); + + return res.status(201).json(result); + } +} + +export class AcceptVinculoController { + async handle(req: Request, res: Response) { + const { email1, email2, identifier1, identifier2 } = req.body; + + const acceptVinculoUseCase = new AcceptVinculoUseCase(); + + const result = await acceptVinculoUseCase.execute({ email1, email2, identifier1, identifier2 }); + + return res.status(201).json(result); + } +} \ No newline at end of file diff --git a/server/src/modules/interfaces/sharedDTOs.ts b/server/src/modules/interfaces/sharedDTOs.ts new file mode 100644 index 0000000..bf35276 --- /dev/null +++ b/server/src/modules/interfaces/sharedDTOs.ts @@ -0,0 +1,11 @@ +export interface VinculoDTO { + email1: string, + email2: string, + identifier1: IdentificadorEnum, + identifier2: IdentificadorEnum +} + +export enum IdentificadorEnum { + ALUNO = "ALUNO", + PROFESSOR = "PROFESSOR" +} \ No newline at end of file diff --git a/server/src/modules/services/shared/AcceptVinculoUseCase.ts b/server/src/modules/services/shared/AcceptVinculoUseCase.ts new file mode 100644 index 0000000..0a438ed --- /dev/null +++ b/server/src/modules/services/shared/AcceptVinculoUseCase.ts @@ -0,0 +1,66 @@ +import { prisma } from "../../../prisma/client"; +import { AppError } from "../../../errors/error"; +import { VinculoDTO, IdentificadorEnum } from "../../interfaces/sharedDTOs"; + +export class AcceptVinculoUseCase { + async execute({ email1, email2, identifier1, identifier2 }: VinculoDTO) { + if (!email1 || !email2 || !identifier1 || !identifier2) { + throw new AppError("Parâmetros insuficientes ou inválidos."); + } + + const entidade1 = await this.encontrarEntidadePeloEmail(email1, identifier1); + const entidade2 = await this.encontrarEntidadePeloEmail(email2, identifier2); + + if (!entidade1) { + throw new AppError(`${identifier1.charAt(0).toUpperCase()}${identifier1.slice(1).toLowerCase()} não encontrado.`); + } + + if (!entidade2) { + throw new AppError(`${identifier2.charAt(0).toUpperCase()}${identifier2.slice(1).toLowerCase()} não encontrado.`); + } + + const vinculoExists = await prisma.vinculo.findFirst({ + where: { + alunoId: (identifier1 == "ALUNO") ? entidade1.id : null, + professorId: (identifier1 == "PROFESSOR") ? entidade1.id : null, + vinculoComAlunoId: (identifier2 == "ALUNO") ? entidade2.id : null, + vinculoComProfessorId: (identifier2 == "PROFESSOR") ? entidade2.id : null + }, + }); + + if(vinculoExists){ + throw new AppError("Solicitação inexistente"); + } + + const vinculo = await prisma.vinculo.update({ + where: { + alunoId_professorId_vinculoComAlunoId_vinculoComProfessorId: { + alunoId: identifier1 === IdentificadorEnum.ALUNO ? entidade1.id : "", + professorId: identifier1 === IdentificadorEnum.PROFESSOR ? entidade1.id : "", + vinculoComAlunoId: identifier2 === IdentificadorEnum.ALUNO ? entidade2.id : "", + vinculoComProfessorId: identifier2 === IdentificadorEnum.PROFESSOR ? entidade2.id : "" + } + }, + data:{ + accepted: true + } + }); + + return "Solicitação aceita!"; + } + + private async encontrarEntidadePeloEmail(email: string, identifier: IdentificadorEnum) { + switch (identifier) { + case IdentificadorEnum.ALUNO: + return prisma.aluno.findUnique({ + where: { email } + }); + case IdentificadorEnum.PROFESSOR: + return prisma.professor.findUnique({ + where: { email } + }); + default: + return null; + } + } +} diff --git a/server/src/modules/services/shared/CreateVinculoUseCase.ts b/server/src/modules/services/shared/CreateVinculoUseCase.ts new file mode 100644 index 0000000..a11994c --- /dev/null +++ b/server/src/modules/services/shared/CreateVinculoUseCase.ts @@ -0,0 +1,61 @@ +import { prisma } from "../../../prisma/client"; +import { AppError } from "../../../errors/error"; +import { VinculoDTO, IdentificadorEnum } from "../../interfaces/sharedDTOs"; + +export class CreateVinculoUseCase { + async execute({ email1, email2, identifier1, identifier2 }: VinculoDTO) { + if (!email1 || !email2 || !identifier1 || !identifier2) { + throw new AppError("Parâmetros insuficientes ou inválidos."); + } + + const entidade1 = await this.encontrarEntidadePeloEmail(email1, identifier1); + const entidade2 = await this.encontrarEntidadePeloEmail(email2, identifier2); + + if (!entidade1) { + throw new AppError(`${identifier1.charAt(0).toUpperCase()}${identifier1.slice(1).toLowerCase()} não encontrado.`); + } + + if (!entidade2) { + throw new AppError(`${identifier2.charAt(0).toUpperCase()}${identifier2.slice(1).toLowerCase()} não encontrado.`); + } + + const vinculoExists = await prisma.vinculo.findFirst({ + where: { + alunoId: (identifier1 == "ALUNO") ? entidade1.id : null, + professorId: (identifier1 == "PROFESSOR") ? entidade1.id : null, + vinculoComAlunoId: (identifier2 == "ALUNO") ? entidade2.id : null, + vinculoComProfessorId: (identifier2 == "PROFESSOR") ? entidade2.id : null + }, + }); + + if (!vinculoExists) { + throw new AppError("Solicitação já existente"); + } + + const vinculo = await prisma.vinculo.create({ + data: { + alunoId: (identifier1 == "ALUNO") ? entidade1.id : null, + professorId: (identifier1 == "PROFESSOR") ? entidade1.id : null, + vinculoComAlunoId: (identifier2 == "ALUNO") ? entidade2.id : null, + vinculoComProfessorId: (identifier2 == "PROFESSOR") ? entidade2.id : null + } + }); + + return "Solicitação de vínculo efetuada com sucesso!"; + } + + private async encontrarEntidadePeloEmail(email: string, identifier: IdentificadorEnum) { + switch (identifier) { + case IdentificadorEnum.ALUNO: + return prisma.aluno.findUnique({ + where: { email } + }); + case IdentificadorEnum.PROFESSOR: + return prisma.professor.findUnique({ + where: { email } + }); + default: + return null; + } + } +} diff --git a/server/src/router/routes/aluno.routes.ts b/server/src/router/routes/aluno.routes.ts index e73dc63..d17f81c 100644 --- a/server/src/router/routes/aluno.routes.ts +++ b/server/src/router/routes/aluno.routes.ts @@ -1,5 +1,6 @@ import { Router } from "express"; import { alunoAuthMiddleware } from '../../middleware/autentication'; +import{ CreateVinculoController } from "../../modules/controllers/sharedControllers"; import { CreateAlunoController, CreatePreAlunoController, @@ -17,6 +18,7 @@ const recoveryAlunoController = new RecoveryAlunoController(); const validateRecoveryController = new ValidateRecoveryController(); const completeAlunoController = new CompleteAlunoController(); const updateCurriculoController = new UpdateCurriculoController(); +const createVinculoUseCase = new CreateVinculoController(); const alunoRoutes = Router(); @@ -27,6 +29,7 @@ alunoRoutes.post("/update/curriculo", alunoAuthMiddleware, updateCurriculoContro alunoRoutes.post("/login", loginAlunoController.handle); alunoRoutes.post("/recovery", recoveryAlunoController.handle); alunoRoutes.post("/recovery/validate", validateRecoveryController.handle); +alunoRoutes.post("/link", alunoAuthMiddleware, createVinculoUseCase.handle); alunoRoutes.get("/auth", alunoAuthMiddleware, (req, res) => { res.status(200).send("Aluno autenticado com sucesso."); diff --git a/server/src/router/routes/professor.routes.ts b/server/src/router/routes/professor.routes.ts index 8c9c024..2dee280 100644 --- a/server/src/router/routes/professor.routes.ts +++ b/server/src/router/routes/professor.routes.ts @@ -1,5 +1,6 @@ import { Router } from "express"; import { professorAuthMiddleware } from '../../middleware/autentication'; +import{ CreateVinculoController } from "../../modules/controllers/sharedControllers"; import { ValidateProfessorController, LoginProfessorController, @@ -13,6 +14,7 @@ const loginProfessorController = new LoginProfessorController(); const initProfessorController = new InitProfessorController(); const recoveryProfessorController = new RecoveryProfessorController(); const validateRecoveryController = new ValidateRecoveryController(); +const createVinculoUseCase = new CreateVinculoController(); const professorRoutes = Router(); @@ -20,6 +22,7 @@ professorRoutes.post("/validate", validateProfessorController.handle); professorRoutes.post("/login", loginProfessorController.handle); professorRoutes.post("/recovery", recoveryProfessorController.handle); professorRoutes.post("/recovery/validate", validateRecoveryController.handle); +professorRoutes.post("/link", professorAuthMiddleware, createVinculoUseCase.handle); professorRoutes.post("/auth", professorAuthMiddleware, (req, res) => { res.status(200).send("Professor autenticado com sucesso.");