diff --git a/.czrc b/.czrc new file mode 100644 index 0000000..d1bcc20 --- /dev/null +++ b/.czrc @@ -0,0 +1,3 @@ +{ + "path": "cz-conventional-changelog" +} diff --git a/.editorconfig b/.editorconfig index f4ac754..c6c8b36 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,9 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true \ No newline at end of file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.env b/.env deleted file mode 100644 index ca97002..0000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -REACT_APP_API_BASEURL=https://v3.football.api-sports.io -REACT_APP_API_IMAGES=https://media.api-sports.io diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..7d357e5 --- /dev/null +++ b/.env.sample @@ -0,0 +1,2 @@ +# REACT_APP_API_BASEURL= +# REACT_APP_API_IMAGES= diff --git a/.eslintrc.json b/.eslintrc.json index 97550ef..ef61fb6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,44 +1,39 @@ -{ - "env": { - "browser": true, - "es2020": true - }, - "settings": { - "react": { - "version": "detect" - }, - "import/resolver": { - "node": { - "paths": ["src"], - "extensions": [".js", ".jsx", ".ts", ".tsx"] - } - } - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:react/recommended", - "plugin:react-hooks/recommended", - "plugin:prettier/recommended", - "prettier" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": [ - "react", - "react-refresh", - "@typescript-eslint", - "prettier" - ], - "rules": { - "prettier/prettier": "error", - "react-refresh/only-export-components": "off", - "prefer-arrow-callback": "error", - "react/prop-types": "off", - "react/react-in-jsx-scope": "off", - "@typescript-eslint/no-explicit-any": "off" - } -} +{ + "env": { + "browser": true, + "es2020": true + }, + "settings": { + "react": { + "version": "detect" + }, + "import/resolver": { + "node": { + "paths": ["src"], + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + } + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:prettier/recommended", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["react", "react-refresh", "@typescript-eslint", "prettier"], + "rules": { + "prettier/prettier": "error", + "react-refresh/only-export-components": "off", + "prefer-arrow-callback": "warn", // require using arrow functions for callbacks + "react/prop-types": "off", // requires that all React components have their props described in a PropTypes declaration + "react/react-in-jsx-scope": "off", + "@typescript-eslint/no-explicit-any": "off" // disallow usage of the any type, change it to warn later + } +} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..21f9a10 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,24 @@ +# https://docs.github.com/en/actions/quickstart +name: Continuous integration +run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 +on: [pull_request] +jobs: + Explore-GitHub-Actions: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build and test + run: npm run build + + - name: Run tests + run: npm test diff --git a/.gitignore b/.gitignore index 8a86af1..a973a5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,24 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +.env + +# Editor directories and files +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..e57dc29 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +yarn commitlint --edit \ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..99dec5d --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn run lint diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 0000000..4a2096e --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# && yarn cy:run-unit + +git diff HEAD --quiet && yarn run build diff --git a/.vscode/settings.json b/.vscode/settings.json index e3e903f..b25aabc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,17 @@ -{ - "editor.formatOnSave": false, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - }, - "material-icon-theme.folders.associations": { - "redux": "redux-store", - "state": "constant", - "initialState": "redux-selector", - "BackTopBtn": "react-components", - "Card": "react-components", - } -} +{ + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "material-icon-theme.folders.associations": { + "redux": "redux-store", + "state": "constant", + "initialState": "redux-selector", + "BackTopBtn": "react-components", + "Card": "react-components" + }, + "material-icon-theme.files.associations": { + ".czrc": "git" + }, + "window.zoomLevel": 0 +} diff --git a/collections/insominia.json b/collections/insominia.json new file mode 100644 index 0000000..dadd79a --- /dev/null +++ b/collections/insominia.json @@ -0,0 +1,386 @@ +{ + "_type": "export", + "__export_format": 4, + "__export_date": "2024-06-14T17:48:26.516Z", + "__export_source": "insomnia.desktop.app:v2023.5.8", + "resources": [ + { + "_id": "req_8c5ba4ef73a54467a9c671e027cc3cc5", + "parentId": "fld_5d206af81c4e4739b9e9b6ddca07db40", + "modified": 1684438896927, + "created": 1684438695754, + "url": "{{ _.baseUrl }}/teams?country=france", + "name": "teams_by_countries", + "description": "Lista todos os times disponíveis em um país.", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "8e5d5f70898ac2742cee6f23af4830b8", + "addTo": "header" + }, + "metaSortKey": -1684438695754, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "fld_5d206af81c4e4739b9e9b6ddca07db40", + "parentId": "wrk_919546507d3c4129851957414dc2b3b4", + "modified": 1718324777807, + "created": 1684436058244, + "name": "teams_requests", + "description": "", + "environment": {}, + "environmentPropertyOrder": null, + "metaSortKey": -1684436058244, + "_type": "request_group" + }, + { + "_id": "wrk_919546507d3c4129851957414dc2b3b4", + "parentId": null, + "modified": 1684326446209, + "created": 1684326446209, + "name": "api-football", + "description": "", + "scope": "design", + "_type": "workspace" + }, + { + "_id": "req_95c8e42b07f748f8916707bdee73b61a", + "parentId": "fld_5d206af81c4e4739b9e9b6ddca07db40", + "modified": 1684438681733, + "created": 1684438365161, + "url": "{{ _.baseUrl }}/teams/countries", + "name": "countries_by_teams", + "description": "Retorna a lista de países disponíveis por times.", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "8e5d5f70898ac2742cee6f23af4830b8", + "addTo": "header" + }, + "metaSortKey": -1684438365161, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "req_90b8b454ee344a5084a19cff865491ec", + "parentId": "fld_5d206af81c4e4739b9e9b6ddca07db40", + "modified": 1684438419803, + "created": 1684438246282, + "url": "{{ _.baseUrl }}/teams?league=19", + "name": "teams_leagues", + "description": "", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "8e5d5f70898ac2742cee6f23af4830b8", + "addTo": "header" + }, + "metaSortKey": -1684438246282, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "req_1739a2bc35a64215bbd4ba57b149b705", + "parentId": "fld_5d206af81c4e4739b9e9b6ddca07db40", + "modified": 1718324965969, + "created": 1684438084465, + "url": "{{ _.baseUrl }}/teams?id=4", + "name": "teams_by_id", + "description": "Você pode seleciona um time diretamente conhecendo o id do time ou para selecionar um time dentro desse pais deve-se utilizar o id que é único para cada time.", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "{{ _['x-rapidapi-key'] }}", + "addTo": "header" + }, + "metaSortKey": -1684438084465, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "req_6726f74a946a4c05b7a6d3f4875552c0", + "parentId": "fld_a2a0c772ee4144358ee306d2a2f859d6", + "modified": 1684437510954, + "created": 1684437244612, + "url": "{{ _.baseUrl }}/leagues?id=13", + "name": "league_by_id", + "description": "Pegando uma liga pelo id quanlquer liga de qualquer lugar do mundo será retornada, para retornar uma liga de uma país específico saiba qual o id da liga.", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "8e5d5f70898ac2742cee6f23af4830b8", + "addTo": "header" + }, + "metaSortKey": -1684437244612, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "fld_a2a0c772ee4144358ee306d2a2f859d6", + "parentId": "wrk_919546507d3c4129851957414dc2b3b4", + "modified": 1684435728763, + "created": 1684435728763, + "name": "leagues_requests", + "description": "", + "environment": {}, + "environmentPropertyOrder": null, + "metaSortKey": -1684435728763, + "_type": "request_group" + }, + { + "_id": "req_4ffaeca6ba5f4baa84ed6cccc294a198", + "parentId": "fld_a2a0c772ee4144358ee306d2a2f859d6", + "modified": 1684438953457, + "created": 1684437028415, + "url": "{{ _.baseUrl }}/leagues?country=france", + "name": "leagues_by_country", + "description": "Voc6e só pode pegar uma liga específica se um país estiver selecionado ou se souber um id.", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "8e5d5f70898ac2742cee6f23af4830b8", + "addTo": "header" + }, + "metaSortKey": -1684437028415, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "req_c055c6c572524ef7aab1ce993ec3a059", + "parentId": "fld_a2a0c772ee4144358ee306d2a2f859d6", + "modified": 1718324897901, + "created": 1684436956586, + "url": "{{ _.baseUrl }}/leagues", + "name": "leagues", + "description": "", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "{{ _['x-rapidapi-key'] }}", + "addTo": "header" + }, + "metaSortKey": -1684436956586, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "req_4660e4746eab4bb1b27ccfe837fd9ee8", + "parentId": "fld_392413a6b75a4c45bb912d5e5a8ba9c2", + "modified": 1718324720375, + "created": 1684436345152, + "url": "{{ _.baseUrl }}/countries?code=br", + "name": "country_by_code", + "description": "", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "8e5d5f70898ac2742cee6f23af4830b8", + "addTo": "header" + }, + "metaSortKey": -1684436345152, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "fld_392413a6b75a4c45bb912d5e5a8ba9c2", + "parentId": "wrk_919546507d3c4129851957414dc2b3b4", + "modified": 1684435716666, + "created": 1684435716666, + "name": "country_requests", + "description": "", + "environment": {}, + "environmentPropertyOrder": null, + "metaSortKey": -1684435716666, + "_type": "request_group" + }, + { + "_id": "req_5c9ef45be9d54d338033672599b2370d", + "parentId": "fld_392413a6b75a4c45bb912d5e5a8ba9c2", + "modified": 1718324735418, + "created": 1684436235727, + "url": "{{ _.baseUrl }}/countries", + "name": "countries", + "description": "", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "8e5d5f70898ac2742cee6f23af4830b8", + "addTo": "header" + }, + "metaSortKey": -1684436235727, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "req_a9f467700db54dc4bf7f75084380b143", + "parentId": "wrk_919546507d3c4129851957414dc2b3b4", + "modified": 1718324743442, + "created": 1684431914516, + "url": "{{ _.baseUrl }}/status", + "name": "user", + "description": "", + "method": "GET", + "body": {}, + "parameters": [], + "headers": [], + "authentication": { + "type": "apikey", + "disabled": false, + "key": "x-rapidapi-key", + "value": "{{ _['x-rapidapi-key'] }}", + "addTo": "header" + }, + "metaSortKey": -1684431914516, + "isPrivate": false, + "settingStoreCookies": true, + "settingSendCookies": true, + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingRebuildPath": true, + "settingFollowRedirects": "global", + "_type": "request" + }, + { + "_id": "env_d433992b4d03f34c1ea94f0f98f9e19811a7b1a1", + "parentId": "wrk_919546507d3c4129851957414dc2b3b4", + "modified": 1718308126616, + "created": 1684326446228, + "name": "Base Environment", + "data": { + "baseUrl": "https://v3.football.api-sports.io", + "baseImgsUrl": "https://media.api-sports.io", + "x-rapidapi-key": "5b1629f3c8607e0ac038d0d2b5761026" + }, + "dataPropertyOrder": { + "&": [ + "baseUrl", + "baseImgsUrl", + "x-rapidapi-key" + ] + }, + "color": null, + "isPrivate": false, + "metaSortKey": 1684326446228, + "_type": "environment" + }, + { + "_id": "jar_d433992b4d03f34c1ea94f0f98f9e19811a7b1a1", + "parentId": "wrk_919546507d3c4129851957414dc2b3b4", + "modified": 1684326446232, + "created": 1684326446232, + "name": "Default Jar", + "cookies": [], + "_type": "cookie_jar" + }, + { + "_id": "spc_485ff6e8f1cd400b907c98cc93177576", + "parentId": "wrk_919546507d3c4129851957414dc2b3b4", + "modified": 1684326446211, + "created": 1684326446211, + "fileName": "api-football", + "contents": "", + "contentType": "yaml", + "_type": "api_spec" + } + ] +} diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..3f5e287 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1 @@ +export default { extends: ['@commitlint/config-conventional'] }; diff --git a/cypress.config.ts b/cypress.config.ts index 0d01fdf..7bfdde4 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,7 +1,10 @@ -import { defineConfig } from 'cypress'; - -export default defineConfig({ - e2e: { - baseUrl: 'http://localhost:5173', - }, -}); +import { defineConfig } from "cypress"; + +export default defineConfig({ + component: { + devServer: { + framework: "react", + bundler: "vite", + }, + }, +}); diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 0000000..698b01a --- /dev/null +++ b/cypress/support/commands.ts @@ -0,0 +1,37 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } \ No newline at end of file diff --git a/cypress/support/component-index.html b/cypress/support/component-index.html new file mode 100644 index 0000000..ac6e79f --- /dev/null +++ b/cypress/support/component-index.html @@ -0,0 +1,12 @@ + + + + + + + Components App + + +
+ + \ No newline at end of file diff --git a/cypress/support/component.ts b/cypress/support/component.ts new file mode 100644 index 0000000..37f59ed --- /dev/null +++ b/cypress/support/component.ts @@ -0,0 +1,39 @@ +// *********************************************************** +// This example support/component.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') + +import { mount } from 'cypress/react18' + +// Augment the Cypress namespace to include type definitions for +// your custom command. +// Alternatively, can be defined in cypress/support/component.d.ts +// with a at the top of your spec. +declare global { + namespace Cypress { + interface Chainable { + mount: typeof mount + } + } +} + +Cypress.Commands.add('mount', mount) + +// Example use: +// cy.mount() \ No newline at end of file diff --git a/index.html b/index.html index 951227e..b0ca89a 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,13 @@ - - - - - - - Vite + React + TS - - -
- - - + + + + + + + Meu time + + +
+ + + diff --git a/package.json b/package.json index e21ec36..468986b 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,50 @@ -{ - "name": "meu_time", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview", - "cz": "git cz", - "cypress:open":"cypres open" - }, - "dependencies": { - "axios": "^1.4.0", - "formik": "^2.2.9", - "fuse.js": "^6.6.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-redux": "^8.0.5", - "react-router-dom": "^6.11.1", - "react-toastify": "^9.1.3", - "redux": "^4.2.1", - "sass": "^1.62.1" - }, - "devDependencies": { - "@types/react": "^18.0.28", - "@types/react-dom": "^18.0.11", - "@typescript-eslint/eslint-plugin": "^5.57.1", - "@typescript-eslint/parser": "^5.57.1", - "@vitejs/plugin-react": "^4.0.0", - "commitizen": "^4.3.0", - "cypress": "^12.12.0", - "eslint": "^8.38.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.3.4", - "prettier": "^2.8.8", - "typescript": "^5.0.2", - "vite": "^4.3.2" - } -} +{ + "name": "meu_time", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview", + "commit": "cz", + "prepare": "husky", + "cypress:open": "cypres open", + "cy:open-unit": "cypress open --component", + "cy:run-unit": "cypress run --component" + }, + "dependencies": { + "axios": "^1.4.0", + "formik": "^2.2.9", + "fuse.js": "^6.6.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-redux": "^8.0.5", + "react-router-dom": "^6.11.1", + "react-toastify": "^9.1.3", + "redux": "^4.2.1", + "sass": "^1.62.1" + }, + "devDependencies": { + "@commitlint/cli": "^19.3.0", + "@commitlint/config-conventional": "^19.2.2", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "@typescript-eslint/eslint-plugin": "^5.57.1", + "@typescript-eslint/parser": "^5.57.1", + "@vitejs/plugin-react": "^4.0.0", + "commitizen": "^4.3.0", + "cypress": "^13.11.0", + "eslint": "^8.38.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.3.4", + "husky": "^9.0.11", + "prettier": "^2.8.8", + "typescript": "^5.0.2", + "vite": "^4.3.2" + } +} diff --git a/src/App.scss b/src/App.scss index e7d7ad8..d880a88 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,21 +1,14 @@ -// Global CSS to compoenents -body { - height: 100vh; - width: 100%; -} - -.u-container { - max-width: 1280px; - margin: 0 auto; -} - -.c-grid { - // Base layout - display: grid; - grid-template-columns: auto; - grid-template-rows: auto auto auto; -} - -*:not(a) { - color: #3C3939; -} +// Global CSS to compoenents +body { + height: 100vh; + width: 100%; +} + +.u-container { + max-width: 1280px; + margin: 0 auto; +} + +*:not(a) { + color: #3c3939; +} diff --git a/src/App.tsx b/src/App.tsx index dfea367..71d2605 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,26 @@ import './App.scss'; import 'react-toastify/dist/ReactToastify.css'; -import { Grid } from './components/layout/Grid'; +import { Grid } from './components/__layout__/Grid'; import { ToastContainer } from 'react-toastify'; -import { MainRoutes } from './router/routes'; +import { MainRoute as Route } from './router/routes'; import { BrowserRouter as Router } from 'react-router-dom'; +import { Login } from './pages/Login'; +import { Dashboard } from './pages/Dashboard'; +import { Home } from './pages/Home'; function App() { return ( - + + + + + + + + + diff --git a/src/components/Hero/index.tsx b/src/components/Hero/index.tsx new file mode 100644 index 0000000..74237ef --- /dev/null +++ b/src/components/Hero/index.tsx @@ -0,0 +1,22 @@ +import './style.scss'; +import logoFullBig from '../../assets/img/logo-full-big.svg'; + +export const Hero = () => { + return ( +
+
+
+ Logomarca meu time +

+ Informações sobre ligas, times, temporadas, estatísticas e jogadores + tudo em um só lugar. +

+
+
+
+ ); +}; diff --git a/src/components/templates/Header/style.scss b/src/components/Hero/style.scss similarity index 91% rename from src/components/templates/Header/style.scss rename to src/components/Hero/style.scss index e8db0f5..216a665 100644 --- a/src/components/templates/Header/style.scss +++ b/src/components/Hero/style.scss @@ -1,5 +1,4 @@ .c-header { - .c-header__nav { display: flex; justify-content: space-between; @@ -36,12 +35,12 @@ } .c-header__banner { - background: url('../../../assets/img/background.png'); + background: url('../../assets/img/background.png'); background-repeat: no-repeat; background-position: top; background-size: cover; - background-color: #8D1B3D; + background-color: #8d1b3d; background-blend-mode: soft-light; opacity: 0.9; text-align: center; diff --git a/src/components/LoginCard/index.tsx b/src/components/LoginCard/index.tsx deleted file mode 100644 index c509b15..0000000 --- a/src/components/LoginCard/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { connect } from 'react-redux'; -import { getApiKey } from '../../redux/actions/apiKey'; - -import './style.scss'; -import logoLogin from '../../assets/img/logo-login.svg'; -import { Button } from '../common/Button'; -import { useApi } from '../../hooks/useApi'; -import { handleLogin } from './utils/handleLogin'; -import { useNavigate } from 'react-router'; - -interface ApiKeyProps { - apiKey: any; -} - -const LoginCard = ({ apiKey }: ApiKeyProps): JSX.Element => { - const buttonStyle = { - fontSize: '1.5em', - padding: '16px 100px', - }; - - const api = useApi(); - const navigate = useNavigate(); - - const handleClick = async () => { - const userData = await handleLogin(api); - if (userData) navigate('./home'); - }; - - return ( -
- Logomarca para login -
-
- - apiKey(e.target.value)} - /> - - - Chave inválida ou inexistente. Tente novamente. - -
- -

ou

- - Crie uma conta em API-FOOTBALL - -
- ); -}; - -const mapStateToProps = (state: any) => { - return { - apiKey: state.value.apiKey, - }; -}; - -const mapDispatchToProps = (dispatch: any) => { - return { - apiKey(value: string) { - const action = getApiKey(value); - dispatch(action); - }, - }; -}; -export default connect(mapStateToProps, mapDispatchToProps)(LoginCard); diff --git a/src/components/LoginCard/style.scss b/src/components/LoginCard/style.scss deleted file mode 100644 index b2e4a9d..0000000 --- a/src/components/LoginCard/style.scss +++ /dev/null @@ -1,57 +0,0 @@ -.c-logincard { - display: flex; - flex-direction: column; - align-items: center; - - background-color: #FFF2F2; - padding: 55px 0; - margin-bottom: 80px; - border-radius: 6px; - - - .c-logincard__img { - padding-bottom: 28px; - } - - .c-logincard__divider { - border-top: 1px solid #BABABA; - opacity: 0.4; - width: 70%; - } - - .c-logincard__form { - display: flex; - flex-direction: column; - align-items: start; - padding: 48px 0 48px 0; - width: 70%; - - .c-logincard__input { - background-color: #fff; - border: 1px solid #BABABA; - border-radius: 6px; - width: 100%; - padding: 16px 38px; - font-size: 22px; - } - - .c-logincard__label { - font-size: 1.125em; - padding-bottom: 4px; - font-weight: 500; - } - - .u-iserror { - display: none; - color: red; - font-size: 18px; - font-weight: 500; - padding-top: 8px; - } - } - .c-logincard__text, .c-logincard__link { - font-size: 1.125em; - font-weight: 600; - padding: 25px 0; - } -} diff --git a/src/components/LoginCard/utils/handleLogin.ts b/src/components/LoginForm/functions/handleLogin.ts similarity index 87% rename from src/components/LoginCard/utils/handleLogin.ts rename to src/components/LoginForm/functions/handleLogin.ts index 2a387af..83f9f5e 100644 --- a/src/components/LoginCard/utils/handleLogin.ts +++ b/src/components/LoginForm/functions/handleLogin.ts @@ -1,4 +1,4 @@ -import { err, success } from '../../../utils/toatsFunctions'; +import { erro, success } from '../../../utils/toatsFunctions'; export const handleLogin = async (api: any) => { try { @@ -9,7 +9,7 @@ export const handleLogin = async (api: any) => { const errorMessage: HTMLElement | HTMLSpanElement | any = document.querySelector('.u-iserror'); errorMessage.style.display = 'block'; - err( + erro( `Desculpe algo saiu errado, verifique sua chave de acesso e tente novamente.`, ); throw new Error('Erro no login'); diff --git a/src/components/LoginForm/index.tsx b/src/components/LoginForm/index.tsx new file mode 100644 index 0000000..8c7dab0 --- /dev/null +++ b/src/components/LoginForm/index.tsx @@ -0,0 +1,85 @@ +import { connect } from 'react-redux'; +import { getApiKey } from '../../redux/actions/apiKey'; + +import './style.scss'; +import logoLogin from '../../assets/img/logo-login.svg'; +import { Button } from '../__common__/Button'; +import { Divider } from '../__common__/Divider'; +import { Error } from '../__common__/Error'; +import { Input } from '../__common__/Input'; +import { Link } from '../__common__/Link'; +import { useApi } from '../../hooks/useApi'; +import { handleLogin } from './functions/handleLogin'; +import { useNavigate } from 'react-router'; +import { Form } from '../__common__/Form'; +import { Image } from '../__common__/Image'; + +interface ApiKeyProps { + apiKey: any; +} + +/** + * Esse é um componente especializado, que é responsável por renderizar o formulário de login. + * + * @param {ApiKeyProps} { apiKey } - Propriedade que contém a chave de autenticação. + * @returns {JSX.Element} - Formulário de login. + */ +const LoginForm = ({ apiKey }: ApiKeyProps): JSX.Element => { + const buttonStyle = { + fontSize: '1.5em', + padding: '16px 100px', + }; + + const api = useApi(); + const navigate = useNavigate(); + + const handleSubmit = async () => { + console.log('Passei aqui ...'); + const userData = await handleLogin(api); + if (userData) navigate('./home'); + }; + + return ( +
+ Logomarca para login + + +
+
+ + Chave + + Chave inválida ou inexistente. Tente novamente. +
+ +
+ +

ou

+ + Crie uma conta em API-FOOTBALL + +
+ ); +}; + +const mapStateToProps = (state: any) => { + return { + apiKey: state.value.apiKey, + }; +}; + +const mapDispatchToProps = (dispatch: any) => { + return { + apiKey(value: string) { + const action = getApiKey(value); + dispatch(action); + }, + }; +}; +export default connect(mapStateToProps, mapDispatchToProps)(LoginForm); diff --git a/src/components/LoginForm/style.scss b/src/components/LoginForm/style.scss new file mode 100644 index 0000000..65a3a3d --- /dev/null +++ b/src/components/LoginForm/style.scss @@ -0,0 +1,25 @@ +.c-logincard { + display: flex; + flex-direction: column; + align-items: center; + + background-color: #FFF2F2; + padding: 55px 0; + margin-bottom: 80px; + border-radius: 6px; + + .c-logincard__form { + display: flex; + flex-direction: column; + align-items: start; + padding: 48px 0 48px 0; + width: 70%; + + } + + .c-logincard__text { + font-size: 1.125em; + font-weight: 600; + padding: 25px 0; + } +} diff --git a/src/components/__common__/Button/index.tsx b/src/components/__common__/Button/index.tsx new file mode 100644 index 0000000..e5f842a --- /dev/null +++ b/src/components/__common__/Button/index.tsx @@ -0,0 +1,16 @@ +import './style.scss'; + +type PropsButton = { + type: 'button' | 'submit' | 'reset'; + onClick?: () => void; + style?: object; + children: React.ReactNode; +}; + +export const Button = ({ onClick, style, children, type }: PropsButton) => { + return ( + + ); +}; diff --git a/src/components/common/Button/style.scss b/src/components/__common__/Button/style.scss similarity index 100% rename from src/components/common/Button/style.scss rename to src/components/__common__/Button/style.scss diff --git a/src/components/__common__/Divider/index.tsx b/src/components/__common__/Divider/index.tsx new file mode 100644 index 0000000..160800d --- /dev/null +++ b/src/components/__common__/Divider/index.tsx @@ -0,0 +1,5 @@ +import './style.scss'; + +export const Divider = () => { + return
; +}; diff --git a/src/components/__common__/Divider/style.scss b/src/components/__common__/Divider/style.scss new file mode 100644 index 0000000..0aff7be --- /dev/null +++ b/src/components/__common__/Divider/style.scss @@ -0,0 +1,6 @@ +.c-logincard__divider { + border-top: 1px solid #BABABA; + opacity: 0.4; + width: 70%; +} + diff --git a/src/components/__common__/Error/index.tsx b/src/components/__common__/Error/index.tsx new file mode 100644 index 0000000..83364fe --- /dev/null +++ b/src/components/__common__/Error/index.tsx @@ -0,0 +1,10 @@ +import { ReactNode } from 'react'; +import './style.scss'; + +type ErrorProps = { + children: ReactNode; +}; + +export const Error = ({ children }: ErrorProps) => { + return {children}; +}; diff --git a/src/components/__common__/Error/style.scss b/src/components/__common__/Error/style.scss new file mode 100644 index 0000000..d155544 --- /dev/null +++ b/src/components/__common__/Error/style.scss @@ -0,0 +1,7 @@ +.u-iserror { + display: none; + color: red; + font-size: 18px; + font-weight: 500; + padding-top: 8px; +} diff --git a/src/components/__common__/Form/index.tsx b/src/components/__common__/Form/index.tsx new file mode 100644 index 0000000..0b52b84 --- /dev/null +++ b/src/components/__common__/Form/index.tsx @@ -0,0 +1,26 @@ +import { ReactNode, FormEvent } from 'react'; +import './style.scss'; + +type FormProps = { + children: ReactNode; + onSubmit: any; +}; + +/** + * Esse é um container componente que renderiza um formulário reutilizável. + * + * @param {FormProps} { children, onSubmit: handleSubmit } - Propriedades do componente. + * @returns {JSX.Element} - Formulário. + */ +export const Form = ({ children, onSubmit: handleSubmit }: FormProps) => { + const submitHandler = (e: FormEvent) => { + handleSubmit(e); + e.preventDefault(); + }; + + return ( +
+ {children} +
+ ); +}; diff --git a/src/components/__common__/Form/style.scss b/src/components/__common__/Form/style.scss new file mode 100644 index 0000000..a804c14 --- /dev/null +++ b/src/components/__common__/Form/style.scss @@ -0,0 +1,7 @@ +.form { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} diff --git a/src/components/__common__/Image/index.tsx b/src/components/__common__/Image/index.tsx new file mode 100644 index 0000000..f7683f9 --- /dev/null +++ b/src/components/__common__/Image/index.tsx @@ -0,0 +1,10 @@ +import './style.scss'; + +type ImageProps = { + src: string; + alt: string; +}; + +export const Image = ({ src, alt }: ImageProps) => { + return {alt}; +}; diff --git a/src/components/__common__/Image/style.scss b/src/components/__common__/Image/style.scss new file mode 100644 index 0000000..76ea830 --- /dev/null +++ b/src/components/__common__/Image/style.scss @@ -0,0 +1,3 @@ +.c-logincard__img { + padding-bottom: 28px; +} diff --git a/src/components/__common__/Input/index.tsx b/src/components/__common__/Input/index.tsx new file mode 100644 index 0000000..a7a6479 --- /dev/null +++ b/src/components/__common__/Input/index.tsx @@ -0,0 +1,40 @@ +import { ReactNode, useEffect, useRef } from 'react'; +import './style.scss'; + +type InputProps = { + children: ReactNode; + onChange: () => void; + type: string; + placeholder: string; +}; + +export const Input = ({ + children, + onChange, + type, + placeholder, +}: InputProps) => { + const inputRef: any = useRef(); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + return ( + <> + + + + ); +}; diff --git a/src/components/__common__/Input/style.scss b/src/components/__common__/Input/style.scss new file mode 100644 index 0000000..f27ee1f --- /dev/null +++ b/src/components/__common__/Input/style.scss @@ -0,0 +1,15 @@ +.c-logincard__input { + background-color: #fff; + border: 1px solid #BABABA; + border-radius: 6px; + width: 100%; + padding: 16px 38px; + font-size: 22px; +} + +.c-logincard__label { + font-size: 1.125em; + padding-bottom: 4px; + font-weight: 500; +} + diff --git a/src/components/__common__/Link/index.tsx b/src/components/__common__/Link/index.tsx new file mode 100644 index 0000000..da1166d --- /dev/null +++ b/src/components/__common__/Link/index.tsx @@ -0,0 +1,17 @@ +import { ReactNode } from 'react'; +import './style.scss'; + +type LinkProps = { + children: ReactNode; + href: string; + rel: string; + target: string; +}; + +export const Link = ({ children, href, rel, target }: LinkProps) => { + return ( + + {children} + + ); +}; diff --git a/src/components/__common__/Link/style.scss b/src/components/__common__/Link/style.scss new file mode 100644 index 0000000..4960c57 --- /dev/null +++ b/src/components/__common__/Link/style.scss @@ -0,0 +1,6 @@ +.c-logincard__link { + font-size: 1.125em; + font-weight: 600; + padding: 25px 0; +} + diff --git a/src/components/layout/Grid/index.tsx b/src/components/__layout__/Grid/index.tsx similarity index 56% rename from src/components/layout/Grid/index.tsx rename to src/components/__layout__/Grid/index.tsx index 75b46e4..0927249 100644 --- a/src/components/layout/Grid/index.tsx +++ b/src/components/__layout__/Grid/index.tsx @@ -1,9 +1,9 @@ -import '../../../App.scss'; - -interface PropsGrid { - children?: React.ReactNode; -} - -export const Grid = ({ children }: PropsGrid) => { - return
{children}
; -}; +import { PropsWithChildren } from 'react'; +import '../../../App.scss'; +import './styles.scss'; + +type PropsGrid = PropsWithChildren; + +export const Grid = ({ children }: PropsGrid) => { + return
{children}
; +}; diff --git a/src/components/__layout__/Grid/styles.scss b/src/components/__layout__/Grid/styles.scss new file mode 100644 index 0000000..f727d22 --- /dev/null +++ b/src/components/__layout__/Grid/styles.scss @@ -0,0 +1,9 @@ +.c-grid { + display: grid; + grid-template-columns: auto; // 1 column + grid-template-rows: auto auto auto; + grid-template-areas: + 'header' + 'main' + 'footer'; +} diff --git a/src/components/templates/Footer/index.tsx b/src/components/__templates__/Footer/index.tsx similarity index 100% rename from src/components/templates/Footer/index.tsx rename to src/components/__templates__/Footer/index.tsx diff --git a/src/components/templates/Footer/style.scss b/src/components/__templates__/Footer/style.scss similarity index 100% rename from src/components/templates/Footer/style.scss rename to src/components/__templates__/Footer/style.scss diff --git a/src/components/__templates__/Main/index.tsx b/src/components/__templates__/Main/index.tsx new file mode 100644 index 0000000..17fc3e1 --- /dev/null +++ b/src/components/__templates__/Main/index.tsx @@ -0,0 +1,9 @@ +import { PropsWithChildren } from 'react'; + +type MainProps = PropsWithChildren; + +const Main = ({ children }: MainProps) => { + return
{children}
; +}; + +export default Main; diff --git a/src/components/templates/Header/index.tsx b/src/components/__templates__/NavBar/index.tsx similarity index 63% rename from src/components/templates/Header/index.tsx rename to src/components/__templates__/NavBar/index.tsx index dda879e..a7a0aa8 100644 --- a/src/components/templates/Header/index.tsx +++ b/src/components/__templates__/NavBar/index.tsx @@ -1,14 +1,13 @@ -import './style.scss'; -import logoName from '../../../assets/img/logo-name-small.svg'; -import logoFullBig from '../../../assets/img/logo-full-big.svg'; import { Link, useNavigate } from 'react-router-dom'; +import logoName from '../../../assets/img/logo-name-small.svg'; +import './style.scss'; -export const Header = () => { +export const NavBar = () => { const navigate = useNavigate(); return (
-
+
-
- Logomarca meu time -

- Informações sobre ligas, times, temporadas, estatísticas e jogadores - tudo em um só lugar. -

-
); diff --git a/src/components/__templates__/NavBar/style.scss b/src/components/__templates__/NavBar/style.scss new file mode 100644 index 0000000..b591803 --- /dev/null +++ b/src/components/__templates__/NavBar/style.scss @@ -0,0 +1,12 @@ +.c-header { + background-color: #1F1E1E; + + .c-header__nav { + padding: 48px 0; + } + + .c-header__item > button { + background-color: #1f1e1e00; + border: none; + } +} diff --git a/src/components/common/Button/index.tsx b/src/components/common/Button/index.tsx deleted file mode 100644 index 4fa811a..0000000 --- a/src/components/common/Button/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import './style.scss'; - -type PropsButton = { - onClick: () => void; - style?: object; - children: React.ReactNode; -}; - -export const Button = ({ onClick, style, children }: PropsButton) => { - return ( - - ); -}; diff --git a/src/pages/Dashboard/index.tsx b/src/pages/Dashboard/index.tsx index 301cf88..95c513b 100644 --- a/src/pages/Dashboard/index.tsx +++ b/src/pages/Dashboard/index.tsx @@ -1,9 +1,10 @@ -import { Footer } from '../../components/templates/Footer'; -import { NavBar } from '../../components/templates/NavBar'; +import { Footer } from '../../components/__templates__/Footer'; +import { NavBar } from '../../components/__templates__/NavBar'; export const Dashboard = () => { return ( <> +

Team