diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000..bffb357a71 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..88b6f0d981 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo diff --git a/.sample.development.env b/.sample.development.env new file mode 100644 index 0000000000..82ba14aa6b --- /dev/null +++ b/.sample.development.env @@ -0,0 +1,6 @@ +MYSQL_HOST=localhost +MYSQL_PORT=3309 +MYSQL_USER=ateliwareuser +MYSQL_PASSWORD=ateliware_teste +MYSQL_ROOT_PASSWORD=ateliware_root_password +MYSQL_DATABASE=prova_ateliware \ No newline at end of file diff --git a/.sample.production.env b/.sample.production.env new file mode 100644 index 0000000000..3e6c306227 --- /dev/null +++ b/.sample.production.env @@ -0,0 +1,6 @@ +MYSQL_HOST=db +MYSQL_PORT=3309 +MYSQL_USER=ateliwareuser +MYSQL_PASSWORD=ateliware_teste +MYSQL_ROOT_PASSWORD=ateliware_root_password +MYSQL_DATABASE=prova_ateliware \ No newline at end of file diff --git a/.sample.test.env b/.sample.test.env new file mode 100644 index 0000000000..82ba14aa6b --- /dev/null +++ b/.sample.test.env @@ -0,0 +1,6 @@ +MYSQL_HOST=localhost +MYSQL_PORT=3309 +MYSQL_USER=ateliwareuser +MYSQL_PASSWORD=ateliware_teste +MYSQL_ROOT_PASSWORD=ateliware_root_password +MYSQL_DATABASE=prova_ateliware \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..ffbad05fbf --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Next.js: debug server-side", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev" + }, + { + "name": "Next.js: debug client-side", + "type": "pwa-chrome", + "request": "launch", + "url": "http://localhost:3000" + }, + { + "name": "Next.js: debug full stack", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev", + "console": "integratedTerminal", + "serverReadyAction": { + "pattern": "started server on .+, url: (https?://.+)", + "uriFormat": "%s", + "action": "debugWithChrome" + } + } + ] +} diff --git a/README.md b/README.md index 3f1e493650..5250223ff1 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,76 @@ # Desafio técnico para desenvolvedores -Construa uma nova aplicação, utilizando o framework de sua preferência (Ruby on Rails, Elixir Phoenix, Python Django ou Flask, NodeJS Sails, Java Spring, ASP.NET ou outro), a qual deverá conectar na API do GitHub e disponibilizar as seguintes funcionalidades: +## Sobre a solução -- Botão para buscar e armazenar os repositórios destaques de 5 linguagens à sua escolha; -- Listar os repositórios encontrados; -- Visualizar os detalhes de cada repositório. +Para realizar o desafio utilizei: -Alguns requisitos: +- [Next.js](https://nextjs.org/) com [Typescript](https://www.typescriptlang.org/) para o front-end e backend +- [MySQL](https://www.mysql.com/) para armazenar o banco de dados +- [Jest](https://jestjs.io/) e [React Testing Library](https://testing-library.com/) para testes automáticos +- CSS puro sem frameworks -- Deve ser uma aplicação totalmente nova; -- A solução deve estar em um repositório público do GitHub; -- A aplicação deve armazenar as informações encontradas; -- Utilizar PostgreSQL, MySQL ou SQL Server; -- O deploy deve ser realizado, preferencialmente, no Heroku, AWS ou no Azure; -- A aplicação precisa ter testes automatizados; -- Preferenciamente dockerizar a aplicação; -- Por favor atualizar o readme da aplicação com passo a passo com instrução para subir o ambiente. +## Dependencias -Quando terminar, faça um Pull Request neste repo e avise-nos por email. +é preciso ter `docker` e `docker-compose` instalados -**IMPORTANTE:** se você não conseguir finalizar o teste, por favor nos diga o motivo e descreva quais foram as suas dificuldades. Você pode também sugerir uma outra abordagem para avaliarmos seus skills técnicos, vender seu peixe, mostrar-nos do que é capaz. +## Desenvolver + +obter as variaveis de ambiente executando: + +```shell +sh envs.sh +``` + +executar: + +```shell +docker-compose -f docker-compose.yml --env-file ./.env.development.local up +``` + +em outro terminal executar: + +```shell +npm run dev +``` + +```shell +docker-compose -f docker-compose-development.yml --env-file ./.env.development.local up +``` + +## Executar testes + +É preciso instalar as dependencias utilizando na pasta raiz do projeto utilizando: + +```shell +npm install +``` + +obter as variaveis de ambiente executando: + +```shell +sh envs.sh +``` + +com as dependencias instaladas (veja [Dependencias](#dependencias)), executar: + +```shell +docker-compose -f docker-compose.yml --env-file ./.env.test.local up +``` + +e em outro terminal, executar: + +```shell +npx jest +``` + +## Produção + +executar + +```shell +docker-compose -f docker-compose-production.yml --env-file ./.env.production.local up --build +``` + +## versão disponivel online + +[http://34.125.243.67:3000](http://34.125.243.67:3000) diff --git a/components/Alert/Alert.module.css b/components/Alert/Alert.module.css new file mode 100644 index 0000000000..1fa62985ac --- /dev/null +++ b/components/Alert/Alert.module.css @@ -0,0 +1,84 @@ +.main { + display: flex; + flex-direction: column; + align-items: baseline; + white-space: normal; +} + +.hr { + width: 100%; + border: none; + border-bottom: 1px solid var(--text-color); + flex: 1; + margin-top: 1rem; + margin-bottom: 1rem; + order: 2; +} + +.error .hr { + border-bottom-color: var(--color-error); +} + +.success .hr { + border-bottom-color: var(--color-success); +} + +.footer { + width: 100%; + display: flex; + flex-direction: column; + align-items: baseline; +} + +.actions { + display: block; + order: 2; + padding: 0; + list-style: none; + margin: 0; + order: 1; +} + +.actions li { + display: block; +} + +.actions a, +.actions button { + text-transform: uppercase; + display: inline-block; + padding: 0.5rem 0; + cursor: pointer; + border: none; + outline: none; + background: transparent; + color: inherit; + border-bottom: 1px solid transparent; + border-top: 1px solid transparent; +} +.actions a:hover, +.actions button:hover { + border-top-color: 1px solid var(--text-color); + border-bottom-color: 1px solid var(--text-color); +} + +@media screen and (min-width: 501px) { + .hr { + order: 1; + margin-top: 1rem; + margin-bottom: 0; + } + .actions { + margin-left: 1rem; + order: 2; + } + .actions li { + display: inline-block; + } + .footer { + flex-direction: row; + } + .actions li + li { + margin-left: 1rem; + } +} diff --git a/components/Alert/Alert.test.tsx b/components/Alert/Alert.test.tsx new file mode 100644 index 0000000000..e999a79db6 --- /dev/null +++ b/components/Alert/Alert.test.tsx @@ -0,0 +1,56 @@ +import { cleanup, render, screen } from "@testing-library/react"; +import { Alert, AlertPropsVariants } from "./index"; +describe("Alert", () => { + it("renderiza com a variante erro", () => { + const AlertMsg = "Mensagem de alerta de teste de erro"; + render({AlertMsg}); + const successAlert = screen.getByRole("alert"); + expect(successAlert).toHaveClass("main"); + expect(successAlert).toHaveClass("error"); + const successAlertMainElement = screen.getByText(AlertMsg); + expect(successAlertMainElement).toBeInTheDocument(); + }); + it("renderiza um com a variante sucesso", () => { + const AlertMsg = "Mensagem de alerta de teste de sucessso"; + render({AlertMsg}); + const successAlert = screen.getByRole("alert"); + expect(successAlert).toHaveClass("main"); + expect(successAlert).toHaveClass("success"); + const successAlertMainElement = screen.getByText(AlertMsg); + expect(successAlertMainElement).toBeInTheDocument(); + }); + it("renderiza com ações", () => { + const AlertMsg = "Mensagem de alerta de teste de sucessso e ações"; + const alertElement = render( + + cancelar + , + + okay + , + ]} + > + {AlertMsg} + + ); + const successAlert = screen.getByRole("alert"); + expect(successAlert).toHaveClass("main"); + expect(successAlert).toHaveClass("success"); + const successAlertMainElement = screen.getByText(AlertMsg); + expect(successAlertMainElement).toBeInTheDocument(); + + const affirmativeOption = screen.getByText("okay").closest("a"); + expect(affirmativeOption).toBeInTheDocument(); + expect(affirmativeOption).toHaveAttribute("href", "/ok"); + + const NegativeOption = screen.getByText("cancelar").closest("a"); + expect(NegativeOption).toBeInTheDocument(); + expect(NegativeOption).toHaveAttribute("href", "/cancelar"); + + expect(alertElement.container).toMatchSnapshot(); + }); +}); +cleanup(); diff --git a/components/Alert/__snapshots__/Alert.test.tsx.snap b/components/Alert/__snapshots__/Alert.test.tsx.snap new file mode 100644 index 0000000000..dc6e5bc2d4 --- /dev/null +++ b/components/Alert/__snapshots__/Alert.test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Alert renderiza com ações 1`] = ` +
+ +
+`; diff --git a/components/Alert/index.tsx b/components/Alert/index.tsx new file mode 100644 index 0000000000..cfef9815d1 --- /dev/null +++ b/components/Alert/index.tsx @@ -0,0 +1,45 @@ +import React, { PropsWithChildren, useMemo } from "react"; +import styles from "./Alert.module.css"; +export enum AlertPropsVariants { + success = "success", + error = "error", +} +export interface AlertProps { + variant: AlertPropsVariants; + actions?: React.ReactNode[]; +} +export const Alert: React.FC> = ({ + children, + variant, + actions, +}) => { + const classNames = useMemo(() => { + const finalClasses = [styles.main]; + switch (variant) { + case AlertPropsVariants.error: + finalClasses.push(styles.error); + break; + case AlertPropsVariants.success: + finalClasses.push(styles.success); + break; + default: + break; + } + return finalClasses; + }, [variant]); + return ( +
+
{children}
+
+
+ {actions && ( +
    + {actions.map((action, i) => ( +
  • {action}
  • + ))} +
+ )} +
+
+ ); +}; diff --git a/components/Footer/Footer.module.css b/components/Footer/Footer.module.css new file mode 100644 index 0000000000..984f4a0731 --- /dev/null +++ b/components/Footer/Footer.module.css @@ -0,0 +1,35 @@ +.main { + border-top: 1px solid var(--text-color); + padding-top: 12pt; + padding-bottom: 24pt; + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr 1fr; + grid-column-gap: 1rem; + grid-row-gap: 1rem; + align-items: baseline; +} + +.copyleft { + grid-column-start: 1; + grid-column-end: 6; +} + +.themeSwitcherContainer { + grid-column-start: 2; + grid-column-end: 6; +} + +@media screen and (min-width: 501px) { + .copyleft { + grid-column-end: 3; + } + .themeSwitcherContainer { + grid-column-start: 4; + } +} + +@media screen and (min-width: 1200px) { + .themeSwitcherContainer { + grid-column-start: 5; + } +} diff --git a/components/Footer/Footer.test.tsx b/components/Footer/Footer.test.tsx new file mode 100644 index 0000000000..95edddfff9 --- /dev/null +++ b/components/Footer/Footer.test.tsx @@ -0,0 +1,22 @@ +import { render, screen } from "@testing-library/react"; +import { Footer } from "./index"; +import "./matchMedia.mock"; + +describe("Footer", () => { + it("renderiza informação do autor", () => { + render(