├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── Capa.png ├── demo.gif └── logo-full.svg ├── .gitignore ├── @types └── index.d.ts ├── README.md ├── challenges.json ├── next-env.d.ts ├── next.config.js ├── package.json ├── prettier.config.js ├── public ├── background-logo.png ├── favicon.png ├── icons │ ├── body.svg │ ├── close.svg │ ├── eye.svg │ ├── level-up.svg │ ├── level.svg │ ├── levelup.svg │ └── twitter.svg ├── logo-full.svg ├── sounds │ ├── notification.mp3 │ ├── turn-off.mp3 │ └── turn-on.mp3 └── white-logo-full.svg ├── src ├── components │ ├── ChallengeBox.tsx │ ├── CompletedChallenges.tsx │ ├── CountDown.tsx │ ├── ExperienceBar.tsx │ ├── Input.tsx │ ├── LevelUpModal.tsx │ ├── Loading.tsx │ ├── Profile.tsx │ └── SideBar.tsx ├── contexts │ ├── ChallengesContext.tsx │ ├── CountDownContext.tsx │ ├── SessionContext.tsx │ └── theme.tsx ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ └── users │ │ │ ├── all.ts │ │ │ ├── create.ts │ │ │ ├── search.ts │ │ │ └── update.ts │ ├── home.tsx │ ├── index.tsx │ └── leaderboard.tsx └── styles │ ├── components │ ├── ChallengeBox │ │ └── index.ts │ ├── CompletedChallenges │ │ └── index.ts │ ├── CountDown │ │ └── index.ts │ ├── ExperienceBar │ │ └── index.ts │ ├── Input │ │ └── index.ts │ ├── LevelUpModal │ │ └── index.ts │ ├── Loading │ │ └── index.ts │ ├── Profile │ │ └── index.ts │ └── SideBar │ │ └── index.ts │ ├── global.ts │ ├── pages │ ├── home.ts │ ├── index.ts │ └── leaderboard.ts │ └── theme.ts ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [["styled-components", { "ssr": true }]] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.js 2 | /*.js 3 | node_modules 4 | build 5 | /src/react-app-env.d.ts -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "plugin:@typescript-eslint/recommended", 8 | "plugin:react/recommended", 9 | "prettier/@typescript-eslint", 10 | "plugin:prettier/recommended", 11 | "airbnb" 12 | ], 13 | "globals": { 14 | "Atomics": "readonly", 15 | "SharedArrayBuffer": "readonly" 16 | }, 17 | "parser": "@typescript-eslint/parser", 18 | "parserOptions": { 19 | "ecmaFeatures": { 20 | "jsx": true 21 | }, 22 | "ecmaVersion": 2018, 23 | "sourceType": "module" 24 | }, 25 | "plugins": [ 26 | "prettier", 27 | "react", 28 | "react-hooks", 29 | "@typescript-eslint" 30 | ], 31 | "rules": { 32 | "prettier/prettier": "error", 33 | "react-hooks/rules-of-hooks": "error", 34 | "react-hooks/exhaustive-deps": "warn", 35 | "react/jsx-filename-extension": [1, { "extensions": [".tsx"] }], 36 | "import/prefer-default-export": "off", 37 | "import/extensions": [ 38 | "error", 39 | "ignorePackages", 40 | { 41 | "ts": "never", 42 | "tsx": "never" 43 | } 44 | ] 45 | }, 46 | "settings": { 47 | "import/resolver": { 48 | "typescript": {} 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /.github/Capa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabrielpdev/Move-it/61a9ac9ce10ceb23211608c60005c4ba136753a4/.github/Capa.png -------------------------------------------------------------------------------- /.github/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabrielpdev/Move-it/61a9ac9ce10ceb23211608c60005c4ba136753a4/.github/demo.gif -------------------------------------------------------------------------------- /.github/logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | -------------------------------------------------------------------------------- /@types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.mp3' 2 | declare module '*.svg' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Move-it 3 |

4 | 5 |

6 | GitHub language count 7 | 8 | Repository size 9 | 10 | 11 | Made by Gabrielpdev 12 | 13 | 14 | 15 | GitHub last commit 16 | 17 | 18 | License 19 | 20 | 21 | Stargazers 22 | 23 |

24 | 25 |

26 | Move-it 27 |

28 | 29 | 30 | ## 📝 Conteúdo 31 |

32 | Sobre   |    33 | Iniciando   |    34 | Tecnologias Utilizadas   |    35 | Como contribuir   |    36 |

37 | 38 | 39 | ## 🧐 Sobre 40 | 41 | Move.it é uma Aplicação feita durante a **NWL 4.0** oferecido pela [Rocketseat] :rocket:.
42 | A aplicação visa ajudar pessoas que ficam muito tempo na frente do computador e se esquecem de tempo em tempo 43 | se alongar. Usando do método de Pomodoro, lembrando o usuário a cada período de tempo realizar uma atividade 44 | que ao ser realizada, o usuário ganha experiência, subindo seu level
45 | 46 | 47 | 48 |

Demo

49 | Move-it-demo 50 | Link 51 |
52 | 53 | ## 🏁 Iniciando 54 | 55 | Instruções de como instalar a aplicação na sua máquina. 56 | 57 | ### ⚒ Instalando 58 | 59 | ``` 60 | # 💻 Iniciando 61 | 62 | $ cd web 63 | $ yarn install 64 | $ yarn start 65 | 66 | ``` 67 | ## ⛏️ Tecnologias Utilizadas 68 | 69 | As seguintes ferramentas foram usadas na construção do projeto: 70 | - 🌱 [MongoDB][mongodb] 71 | - 🔵 [TypeScript][typescript] 72 | - ⚛️ [React][reactjs] 73 | - 🔼 [NextJs][next] 74 | - ⏺ [Framer Motion][framermotion] 75 | - 🍪 [JavaScript Cookie][jscookie] 76 | - 💅 [Styled-components][styled-components] 77 | 78 | ## 🤔 Como contribuir 79 | 80 | - Faça um fork desse repositório; 81 | - Cria uma branch com a sua feature: `git checkout -b minha-feature`; 82 | - Faça commit das suas alterações: `git commit -m 'feat: Minha nova feature'`; 83 | - Faça push para a sua branch: `git push origin minha-feature`. 84 | 85 | Feito com ❤️ por Gabriel Pereira 👋🏽 [Entre em contato!](https://www.linkedin.com/in/gabriel-pereira-oliveira-78b1801ab/) 86 | 87 | [jscookie]: https://github.com/js-cookie/js-cookie 88 | [mongodb]: https://www.mongodb.com 89 | [framermotion]: https://www.framer.com/motion/ 90 | [next]: https://nextjs.org/ 91 | [typescript]: https://www.typescriptlang.org/ 92 | [reactjs]: https://reactjs.org 93 | [Rocketseat]:https://github.com/Rocketseat 94 | [styled-components]:https://styled-components.com/ 95 | 96 | -------------------------------------------------------------------------------- /challenges.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "body", 4 | "description": "Estique um de seus braços com a palma da mão virada para frente e puxe os dedos para cima por 10 segundos por mão.", 5 | "amount": 80 6 | }, 7 | { 8 | "type": "body", 9 | "description": "Estique seu braço contra o peito e puxe-o utilizando o outro braço por 10 segundos por braço.", 10 | "amount": 60 11 | }, 12 | { 13 | "type": "body", 14 | "description": "Puxe seu pescoço com a ajuda da mão para a direita e para a esquerda, permanecendo na posição por alguns segundos.", 15 | "amount": 70 16 | }, 17 | { 18 | "type": "body", 19 | "description": "Com as duas mãos na parte de trás da cabeça, leve-a para baixo, alongando a parte de trás da região.", 20 | "amount": 60 21 | }, 22 | { 23 | "type": "body", 24 | "description": "Cruze as pernas e desça com as mãos esticadas em direção ao chão. Repita o movimento com a outra perna na frente.", 25 | "amount": 100 26 | }, 27 | { 28 | "type": "body", 29 | "description": "Sentado, abra as pernas e tente encostar as palmas das mãos no chão, repita 3 vezes por 5 segundos.", 30 | "amount": 80 31 | }, 32 | { 33 | "type": "body", 34 | "description": "Puxe o joelho de encontro ao peito e segure, troque de perna após 10 segundos.", 35 | "amount": 50 36 | }, 37 | { 38 | "type": "body", 39 | "description": "Sentado, cruze uma perna e incline seu tronco à frente, troque de perna após 10 segundos.", 40 | "amount": 80 41 | }, 42 | { 43 | "type": "eye", 44 | "description": "Sentado, feche os olhos e cubra-os com as palmas da mão durante 2 minutos, depois abra normalmente.", 45 | "amount": 90 46 | }, 47 | { 48 | "type": "eye", 49 | "description": "Em algum ambiente aberto, olhe o mais longe que puder em quatro direções por 3s, mexa apenas os olhos. Repita 3 vezes.", 50 | "amount": 140 51 | }, 52 | { 53 | "type": "eye", 54 | "description": "Usando os polegares, massage a área abaixo das sobrancelhas em movimentos circulares por 15 segundos.", 55 | "amount": 70 56 | }, 57 | { 58 | "type": "body", 59 | "description": "Em pé, gire a cintura o máximo que puder para a esquerda, segure por cinco segundos. Repita para a direita.", 60 | "amount": 90 61 | } 62 | ] -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: (config) => { 3 | config.module.rules.push({ 4 | test: /\.(mp3)$/, 5 | use: { 6 | loader: 'file-loader', 7 | options: { 8 | publicPath: '/_next/static/sounds/', 9 | outputPath: 'static/sounds/', 10 | name: '[name].[ext]', 11 | esModule: false 12 | } 13 | } 14 | }) 15 | return config 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-styled-components", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "next", 6 | "build": "next build", 7 | "start": "next start" 8 | }, 9 | "dependencies": { 10 | "@vercel/node": "^1.9.0", 11 | "axios": "^0.21.1", 12 | "framer-motion": "^3.7.0", 13 | "js-cookie": "^2.2.1", 14 | "mongodb": "^3.6.4", 15 | "next": "latest", 16 | "react": "^16.8.0", 17 | "react-dom": "^16.8.0", 18 | "react-icons": "^4.2.0", 19 | "react-is": "^16.8.0", 20 | "styled-components": "^5.0.0", 21 | "use-sound": "^2.0.1" 22 | }, 23 | "devDependencies": { 24 | "@types/js-cookie": "^2.2.6", 25 | "@types/mongodb": "^3.6.8", 26 | "@types/node": "^14.14.31", 27 | "@types/react": "^17.0.2", 28 | "@typescript-eslint/eslint-plugin": "^4.15.2", 29 | "@typescript-eslint/parser": "^4.15.2", 30 | "babel-plugin-styled-components": "^1.8.0", 31 | "eslint": "^5.16.0", 32 | "eslint-config-airbnb": "^18.2.1", 33 | "eslint-config-prettier": "^8.0.0", 34 | "eslint-import-resolver-typescript": "^2.4.0", 35 | "eslint-plugin-import": "^2.22.1", 36 | "eslint-plugin-jsx-a11y": "^6.4.1", 37 | "eslint-plugin-prettier": "^3.3.1", 38 | "eslint-plugin-react": "^7.21.5", 39 | "eslint-plugin-react-hooks": "^4", 40 | "file-loader": "^6.2.0", 41 | "prettier": "^2.2.1", 42 | "typescript": "^4.1.5" 43 | }, 44 | "license": "MIT" 45 | } 46 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'all', 4 | arrowParens: 'avoid', 5 | } -------------------------------------------------------------------------------- /public/background-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabrielpdev/Move-it/61a9ac9ce10ceb23211608c60005c4ba136753a4/public/background-logo.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabrielpdev/Move-it/61a9ac9ce10ceb23211608c60005c4ba136753a4/public/favicon.png -------------------------------------------------------------------------------- /public/icons/body.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /public/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/icons/level-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/icons/level.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/icons/levelup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/sounds/notification.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabrielpdev/Move-it/61a9ac9ce10ceb23211608c60005c4ba136753a4/public/sounds/notification.mp3 -------------------------------------------------------------------------------- /public/sounds/turn-off.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabrielpdev/Move-it/61a9ac9ce10ceb23211608c60005c4ba136753a4/public/sounds/turn-off.mp3 -------------------------------------------------------------------------------- /public/sounds/turn-on.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabrielpdev/Move-it/61a9ac9ce10ceb23211608c60005c4ba136753a4/public/sounds/turn-on.mp3 -------------------------------------------------------------------------------- /public/white-logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/ChallengeBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useProvider } from '../contexts/ChallengesContext'; 3 | import { useCountDown } from '../contexts/CountDownContext'; 4 | 5 | import { Container, ChallengeActive, ChallengeNotActive,FailedButton, SucceededButton } from '../styles/components/ChallengeBox' 6 | 7 | export const ChallengeBox: React.FC = () => { 8 | const { activeChallenge, resetChallenge, completeChallenge } = useProvider(); 9 | const { resetCountDown } = useCountDown(); 10 | 11 | function handleChallengeSucceeded(){ 12 | completeChallenge() 13 | resetCountDown() 14 | } 15 | 16 | function handleChallengeFailed(){ 17 | resetChallenge() 18 | resetCountDown() 19 | } 20 | 21 | return ( 22 | 23 | {activeChallenge ? ( 24 | 25 |
Ganhe {activeChallenge.amount} xp
26 | 27 |
28 | 29 | Novo desafio 30 |

{activeChallenge.description}

31 |
32 | 33 |
34 | Falhei 35 | Completei 36 |
37 |
38 | ) : ( 39 | 40 | Finalize um ciclo para receber um desafio 41 | 42 |

43 | 44 | Avance de level completando desafios. 45 |

46 |
47 | )} 48 |
49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/components/CompletedChallenges.tsx: -------------------------------------------------------------------------------- 1 | import { useProvider } from '../contexts/ChallengesContext' 2 | import { Container } from '../styles/components/CompletedChallenges' 3 | 4 | export const CompletedChallenges: React.FC = () => { 5 | 6 | const { challengesCompleted } = useProvider(); 7 | 8 | return ( 9 | 10 | Desafios Completos 11 | {challengesCompleted} 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/components/CountDown.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useProvider } from '../contexts/ChallengesContext'; 3 | import { useCountDown } from '../contexts/CountDownContext'; 4 | 5 | import { CountDownContainer, CountDownButton, StopCountDownButton, FinishedCountDownButton } from '../styles/components/CountDown' 6 | 7 | 8 | export const CountDown: React.FC = () => { 9 | const { 10 | hasFinished, 11 | isActive, 12 | minutes, 13 | resetCountDown, 14 | seconds, 15 | startCountDown 16 | } = useCountDown(); 17 | 18 | const [minuteLeft, minuteRight] = String(minutes).padStart(2, '0').split(''); 19 | const [secondLeft, secondRight] = String(seconds).padStart(2, '0').split(''); 20 | 21 | 22 | 23 | return ( 24 |
25 | 26 |
27 | {minuteLeft} 28 | {minuteRight} 29 |
30 | 31 | : 32 | 33 |
34 | {secondLeft} 35 | {secondRight} 36 |
37 |
38 | 39 | {hasFinished ? ( 40 | 41 | Ciclo encerrado 42 | 43 | ) : ( 44 | <> 45 | {!isActive ? ( 46 | 47 | Iniciar um ciclo 48 | 49 | ) : ( 50 | 51 | Abandonar o ciclo 52 | 53 | )} 54 | 55 | )} 56 |
57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /src/components/ExperienceBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { motion } from "framer-motion" 3 | 4 | import { useProvider } from '../contexts/ChallengesContext' 5 | 6 | import { Container } from '../styles/components/ExperienceBar' 7 | 8 | export const ExperienceBar: React.FC = () => { 9 | const { currentExperience, experienceToNextLevel } = useProvider(); 10 | 11 | const percentToNextLevel = Math.round(currentExperience * 100) / experienceToNextLevel; 12 | 13 | return ( 14 | 15 | 0 xp 16 |
17 | 20 | 21 | {currentExperience} xp 22 |
23 | {experienceToNextLevel} xp 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Input.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | InputHTMLAttributes, 3 | useRef, 4 | useState, 5 | useCallback, 6 | } from 'react'; 7 | 8 | import { FiArrowRight, FiLoader } from 'react-icons/fi'; 9 | import { Container } from '../styles/components/Input'; 10 | 11 | const Input: React.FC> = ({ 12 | ...rest 13 | }) => { 14 | const inputRef = useRef(null); 15 | 16 | const [isLoading, setIsLoading] = useState(false); 17 | const [isFocused, setIsFocused] = useState(false); 18 | const [isFilled, setIsFilled] = useState(false); 19 | 20 | const handleInputFocus = useCallback(() => { 21 | setIsFocused(true); 22 | }, []); 23 | 24 | const handleInputBlur = useCallback(() => { 25 | setIsFocused(false); 26 | 27 | setIsFilled(!!inputRef.current?.value); 28 | }, []); 29 | 30 | return ( 31 | 36 | 42 | 45 | 46 | ); 47 | }; 48 | 49 | export default Input; -------------------------------------------------------------------------------- /src/components/LevelUpModal.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion" 2 | 3 | import { useProvider } from '../contexts/ChallengesContext'; 4 | import { Overlay, Container } from '../styles/components/LevelUpModal' 5 | 6 | export default function LevelUpModal() { 7 | const { level, closeLevelUpModal } = useProvider(); 8 | 9 | return ( 10 | 11 | 12 | 13 |
{level}
14 | Parabéns 15 |

Você alcançou um novo level.

16 | 17 | 20 |
21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '../styles/components/Loading' 2 | 3 | export function Loading() { 4 | const moch = [1,2,3,4,5,6] 5 | 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {moch.map((item, index) => ( 20 | 21 | 22 | 29 | 32 | 35 | 36 | ))} 37 | 38 |
POSIÇÃOUSUÁRIODESAFIOSEXPERIÊNCIA ATUAl
23 |
24 |
25 | 26 | 27 |
28 |
30 | 31 | 33 | 34 |
39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Profile.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import { Container } from '../styles/components/Profile' 3 | import { FiLogOut } from "react-icons/fi"; 4 | import { useSession } from '../contexts/SessionContext'; 5 | import { useProvider } from '../contexts/ChallengesContext'; 6 | 7 | interface IUserGithub { 8 | name: string 9 | level: number 10 | avatar_url: string 11 | } 12 | 13 | export default function Profile(props: IUserGithub) { 14 | const { singOut } = useSession(); 15 | const { level } = useProvider(); 16 | 17 | async function handleSignout(){ 18 | await singOut() 19 | } 20 | 21 | return ( 22 | 23 | {props?.name}/ 24 | 25 |
26 | {props?.name} 27 |

28 | Level 29 | Level {level} 30 |

31 |
32 | 33 | 36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /src/components/SideBar.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import React, { useEffect } from 'react' 3 | import useSound from 'use-sound' 4 | import Link from 'next/link'; 5 | 6 | import { FiSun, FiMoon, FiHome, FiAward } from "react-icons/fi"; 7 | 8 | import { useTheme } from '../contexts/theme'; 9 | import { Container } from '../styles/components/SideBar' 10 | 11 | import turnOnSound from '../../public/sounds/turn-on.mp3'; 12 | import turnOffSound from '../../public/sounds/turn-off.mp3'; 13 | 14 | export const SideBar: React.FC = () => { 15 | const { route } = useRouter(); 16 | const { ToggleTheme, theme } = useTheme(); 17 | const [play] = useSound(theme.title === 'dark' ? turnOffSound : turnOnSound) 18 | 19 | function handleClick(){ 20 | ToggleTheme() 21 | play() 22 | } 23 | 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 45 | 46 | 49 | 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /src/contexts/ChallengesContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, ReactNode, useContext, useEffect, useState } from 'react'; 2 | import useSound from 'use-sound'; 3 | import challenges from '../../challenges.json'; 4 | import Cookies from 'js-cookie'; 5 | 6 | import notifications from '../../public/sounds/notification.mp3'; 7 | import LevelUpModal from '../components/LevelUpModal'; 8 | import { useSession } from './SessionContext'; 9 | interface Challenge { 10 | type: 'body' | 'eye'; 11 | description: string; 12 | amount: number; 13 | } 14 | 15 | interface ProviderContextData { 16 | level: number; 17 | currentExperience: number; 18 | challengesCompleted: number; 19 | experienceToNextLevel: number; 20 | activeChallenge: Challenge; 21 | levelUp(): void; 22 | startNewChallenge(): void; 23 | resetChallenge(): void; 24 | completeChallenge(): void; 25 | closeLevelUpModal(): void; 26 | } 27 | interface ChallengesProviderProps { 28 | children: ReactNode 29 | username: string, 30 | level: number, 31 | currentExperience: number, 32 | challengesCompleted: number, 33 | name: string 34 | avatar_url: string 35 | } 36 | 37 | export const ChallengesContext = createContext({} as ProviderContextData); 38 | 39 | export const ChallengesProvider: React.FC = ({ children, ...rest }: ChallengesProviderProps) => { 40 | const { user, updateUser } = useSession(); 41 | const [level, setLevel] = useState(rest.level ?? 1); 42 | 43 | const [play] = useSound(notifications) 44 | const [currentExperience, setCurrentExperience] = useState(rest.currentExperience ?? 0); 45 | 46 | const [challengesCompleted, setChallengesCompleted] = useState(rest.challengesCompleted ?? 0); 47 | const [activeChallenge, setActiveChallenge] = useState(null) 48 | const [isLevelModalOpen, setIsLevelModalOpen] = useState(false) 49 | 50 | const experienceToNextLevel = Math.pow((level + 1) * 4, 2) 51 | 52 | useEffect(() => { 53 | Notification.requestPermission(); 54 | },[]) 55 | 56 | useEffect(() => { 57 | const formattedUser = { 58 | level, 59 | currentExperience, 60 | challengesCompleted, 61 | username: rest?.username || '', 62 | name: rest?.name || '', 63 | avatar_url: rest?.avatar_url || '', 64 | } 65 | 66 | if(rest.username){ 67 | updateUser( rest.username ,formattedUser ).then(({ value }) => { 68 | Cookies.set("user", JSON.stringify(value)) 69 | }) 70 | } 71 | 72 | },[level, currentExperience, challengesCompleted]) 73 | 74 | function levelUp() { 75 | setLevel(level + 1); 76 | setIsLevelModalOpen(true) 77 | } 78 | 79 | function closeLevelUpModal() { 80 | setIsLevelModalOpen(false) 81 | } 82 | 83 | function startNewChallenge() { 84 | const randomChallengeIndex = Math.floor(Math.random() * challenges.length); 85 | const challenge = challenges[randomChallengeIndex] 86 | 87 | setActiveChallenge(challenge); 88 | 89 | play(); 90 | 91 | if(Notification.permission === 'granted'){ 92 | new Notification('Novo desafio 🎉', { 93 | body: `Valendo ${challenge.amount}xp!` 94 | }) 95 | } 96 | } 97 | 98 | function resetChallenge() { 99 | setActiveChallenge(null); 100 | } 101 | 102 | function completeChallenge() { 103 | if(!activeChallenge){ 104 | return; 105 | } 106 | const { amount } = activeChallenge; 107 | let finalExperience = currentExperience + amount; 108 | 109 | if(finalExperience >= experienceToNextLevel){ 110 | finalExperience = finalExperience - experienceToNextLevel; 111 | levelUp(); 112 | } 113 | 114 | setCurrentExperience(finalExperience); 115 | setChallengesCompleted(challengesCompleted + 1); 116 | setActiveChallenge(null); 117 | } 118 | 119 | return ( 120 | 132 | {children} 133 | 134 | {isLevelModalOpen && } 135 | 136 | ); 137 | }; 138 | 139 | export function useProvider(): ProviderContextData { 140 | const context = useContext(ChallengesContext); 141 | 142 | return context; 143 | } -------------------------------------------------------------------------------- /src/contexts/CountDownContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useEffect, useState } from 'react'; 2 | import { useProvider } from './ChallengesContext'; 3 | 4 | interface CountDownContextData { 5 | minutes:number, 6 | seconds: number, 7 | hasFinished: boolean, 8 | isActive: boolean, 9 | startCountDown(): void, 10 | resetCountDown(): void, 11 | } 12 | 13 | export const CountDownContext = createContext({} as CountDownContextData); 14 | 15 | let countdownTimeout: NodeJS.Timeout; 16 | 17 | export const CountDownProvider: React.FC = ({ children }) => { 18 | const { startNewChallenge } = useProvider(); 19 | 20 | const [ time, setTime ] = useState(.05 * 60); 21 | const [ isActive, setIsActive ] = useState(false); 22 | const [ hasFinished, setHasFinished ] = useState(false); 23 | 24 | const minutes = Math.floor(time / 60); 25 | const seconds = time % 60; 26 | 27 | useEffect(() => { 28 | if(isActive && time > 0){ 29 | countdownTimeout = setTimeout(() => { 30 | setTime(time - 1) 31 | }, 1000); 32 | } else if (isActive && time === 0 ) { 33 | setHasFinished(true) 34 | setIsActive(false) 35 | startNewChallenge() 36 | } 37 | },[isActive, time]) 38 | 39 | function startCountDown() { 40 | setIsActive(true); 41 | } 42 | 43 | function resetCountDown() { 44 | clearTimeout(countdownTimeout); 45 | setIsActive(false); 46 | setHasFinished(false) 47 | setTime(.05 * 60); 48 | } 49 | 50 | return ( 51 | 59 | {children} 60 | 61 | ); 62 | }; 63 | 64 | export function useCountDown(): CountDownContextData { 65 | const context = useContext(CountDownContext); 66 | 67 | return context; 68 | } -------------------------------------------------------------------------------- /src/contexts/SessionContext.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { createContext, useCallback, useContext, useState } from 'react'; 3 | import axios from 'axios'; 4 | import Cookies from 'js-cookie'; 5 | 6 | interface IUser{ 7 | // _id: string, 8 | username: string, 9 | level: number, 10 | currentExperience: number, 11 | challengesCompleted: number, 12 | name: string 13 | avatar_url: string 14 | } 15 | interface IUpdateResponse{ 16 | value: IUser 17 | } 18 | interface SessionContextData { 19 | checkIfUserExists(username: string): Promise; 20 | createUser(username: string): Promise; 21 | updateUser( username: string, user: IUser): Promise; 22 | singIn(username: string): Promise, 23 | singOut(): void, 24 | user: IUser 25 | } 26 | 27 | export const SessionContext = createContext({} as SessionContextData); 28 | 29 | export const SessionProvider: React.FC = ({ children }) => { 30 | const route = useRouter(); 31 | const [ user, setUser ] = useState(); 32 | 33 | const checkIfUserExists = useCallback( async (username) => { 34 | try{ 35 | const { data } = await axios.get(`/api/users/search?username=${username}`); 36 | 37 | if(data){ 38 | return data; 39 | }else{ 40 | return false; 41 | } 42 | }catch(err){ 43 | console.log(err) 44 | } 45 | }, []) 46 | 47 | const createUser = useCallback( async (username) => { 48 | try{ 49 | const response = await fetch(`https://api.github.com/users/${username}`); 50 | const userData = await response.json(); 51 | 52 | if(userData.name){ 53 | const { data } = await axios.post('/api/users/create', { 54 | data: { 55 | username, 56 | name: userData.name, 57 | avatar_url: userData.avatar_url, 58 | level: 1, 59 | currentExperience: 0, 60 | challengesCompleted: 0, 61 | active: true 62 | } 63 | }) 64 | 65 | return data; 66 | }else{ 67 | throw new Error("Github username não existe") 68 | } 69 | }catch(err){ 70 | console.log(err) 71 | } 72 | }, []) 73 | 74 | const updateUser = useCallback( async (username, user) => { 75 | try{ 76 | const { data } = await axios.put('/api/users/update', { 77 | username, 78 | data: user 79 | }) 80 | 81 | return data; 82 | }catch(err){ 83 | console.log(err) 84 | } 85 | }, []) 86 | 87 | const singIn = useCallback( async (username) => { 88 | const userExists = await checkIfUserExists(username) 89 | 90 | if(!userExists){ 91 | const data = await createUser(username) 92 | 93 | setUser(data) 94 | Cookies.set("user", JSON.stringify(data)) 95 | route.push(`/home`); 96 | }else { 97 | setUser(userExists) 98 | Cookies.set("user", JSON.stringify(userExists)) 99 | route.push(`/home`); 100 | } 101 | }, []) 102 | 103 | const singOut = useCallback( async () => { 104 | setUser(null) 105 | Cookies.set("user", '') 106 | route.push(`/`); 107 | }, []) 108 | 109 | return ( 110 | 118 | {children} 119 | 120 | ); 121 | }; 122 | 123 | export function useSession(): SessionContextData { 124 | const context = useContext(SessionContext); 125 | 126 | return context; 127 | } -------------------------------------------------------------------------------- /src/contexts/theme.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useContext, createContext, useState, useEffect, 3 | } from 'react'; 4 | import { DefaultTheme, ThemeProvider } from 'styled-components'; 5 | import { dark, light} from '../styles/theme'; 6 | 7 | interface ThemeContextData { 8 | theme: DefaultTheme; 9 | ToggleTheme(): void; 10 | } 11 | 12 | const ThemeContext = createContext({} as ThemeContextData); 13 | 14 | export const ThemesProvider: React.FC = ({ children }) => { 15 | const [theme, setTheme] = useState(light); 16 | 17 | useEffect(() => { 18 | const themeLocal = localStorage.getItem('@MoveYourself:theme'); 19 | 20 | setTheme(themeLocal === 'light' ? light : dark); 21 | }, []); 22 | 23 | const ToggleTheme = () => { 24 | if (theme.title === 'light') { 25 | localStorage.setItem('@MoveYourself:theme', dark.title); 26 | setTheme(dark); 27 | } else { 28 | localStorage.setItem('@MoveYourself:theme', light.title); 29 | setTheme(light); 30 | } 31 | }; 32 | 33 | return ( 34 | 35 | 36 | {children} 37 | 38 | 39 | ); 40 | }; 41 | 42 | export function useTheme(): ThemeContextData { 43 | const context = useContext(ThemeContext); 44 | 45 | return context; 46 | } -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from 'next/app' 2 | import { GlobalStyle } from '../styles/global'; 3 | import { ThemesProvider } from '../contexts/theme'; 4 | import { SessionProvider } from '../contexts/SessionContext'; 5 | 6 | export default function App({ Component, pageProps }: AppProps) { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { 2 | DocumentInitialProps, 3 | DocumentContext, 4 | Html, 5 | Head, 6 | Main, 7 | NextScript } from 'next/document' 8 | import { ServerStyleSheet } from 'styled-components' 9 | 10 | export default class MyDocument extends Document { 11 | static async getInitialProps(ctx: DocumentContext): Promise { 12 | const sheet = new ServerStyleSheet() 13 | const originalRenderPage = ctx.renderPage 14 | 15 | try { 16 | ctx.renderPage = () => 17 | originalRenderPage({ 18 | enhanceApp: (App) => (props) => 19 | sheet.collectStyles(), 20 | }) 21 | 22 | const initialProps = await Document.getInitialProps(ctx) 23 | return { 24 | ...initialProps, 25 | styles: ( 26 | <> 27 | {initialProps.styles} 28 | {sheet.getStyleElement()} 29 | 30 | ), 31 | } 32 | } finally { 33 | sheet.seal() 34 | } 35 | } 36 | 37 | render(): JSX.Element { 38 | return ( 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | 51 | 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/pages/api/users/all.ts: -------------------------------------------------------------------------------- 1 | import { NowRequest, NowResponse } from '@vercel/node'; 2 | import { MongoClient } from 'mongodb'; 3 | import url from 'url'; 4 | 5 | let cachedDb = null; 6 | 7 | async function connectToDatabase(uri: string){ 8 | if(cachedDb){ 9 | return cachedDb; 10 | } 11 | 12 | const client = await MongoClient.connect(uri, { 13 | useNewUrlParser: true, 14 | useUnifiedTopology: true, 15 | }) 16 | 17 | const dbName = url.parse(uri).pathname.substring(1); 18 | 19 | const db = client.db(dbName); 20 | 21 | cachedDb = db; 22 | 23 | return db; 24 | } 25 | 26 | export default async (req: NowRequest, res: NowResponse) => { 27 | try{ 28 | const db = await connectToDatabase(process.env.MONOGODB_URL); 29 | 30 | const collection = db.collection('data') 31 | 32 | const myCursor = collection.find().sort( { level: -1 } ); 33 | 34 | let users = [] 35 | await myCursor.forEach((item) => { 36 | users = [...users, item] 37 | }); 38 | 39 | if(users){ 40 | return res.status(200).json(users); 41 | } 42 | return res.status(204).json({ message : "user does not exist"}); 43 | 44 | }catch(err){ 45 | return res.status(400).json({ 46 | message: err.message || "Unexpected error." 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/pages/api/users/create.ts: -------------------------------------------------------------------------------- 1 | import { NowRequest, NowResponse } from '@vercel/node'; 2 | import { MongoClient } from 'mongodb'; 3 | import url from 'url'; 4 | 5 | let cachedDb = null; 6 | 7 | async function connectToDatabase(uri: string){ 8 | if(cachedDb){ 9 | return cachedDb; 10 | } 11 | 12 | const client = await MongoClient.connect(uri, { 13 | useNewUrlParser: true, 14 | useUnifiedTopology: true, 15 | }) 16 | 17 | const dbName = url.parse(uri).pathname.substring(1); 18 | 19 | const db = client.db(dbName); 20 | 21 | cachedDb = db; 22 | 23 | return db; 24 | } 25 | 26 | export default async function CreateUser(req: NowRequest, res: NowResponse){ 27 | try{ 28 | const { data } = req.body; 29 | 30 | const db = await connectToDatabase(process.env.MONOGODB_URL); 31 | 32 | const collection = db.collection('data') 33 | 34 | const { ops } = await collection.insertOne( data ) 35 | 36 | return res.status(201).json(ops[0]); 37 | }catch(err){ 38 | return res.status(400).json({ 39 | message: err.message || "Unexpected error." 40 | }) 41 | } 42 | } -------------------------------------------------------------------------------- /src/pages/api/users/search.ts: -------------------------------------------------------------------------------- 1 | import { NowRequest, NowResponse } from '@vercel/node'; 2 | import { MongoClient } from 'mongodb'; 3 | import url from 'url'; 4 | 5 | let cachedDb = null; 6 | 7 | async function connectToDatabase(uri: string){ 8 | if(cachedDb){ 9 | return cachedDb; 10 | } 11 | 12 | const client = await MongoClient.connect(uri, { 13 | useNewUrlParser: true, 14 | useUnifiedTopology: true, 15 | }) 16 | 17 | const dbName = url.parse(uri).pathname.substring(1); 18 | 19 | const db = client.db(dbName); 20 | 21 | cachedDb = db; 22 | 23 | return db; 24 | } 25 | 26 | export default async function SearchUser(req: NowRequest, res: NowResponse){ 27 | const { username } = req.query; 28 | 29 | try{ 30 | const db = await connectToDatabase(process.env.MONOGODB_URL); 31 | 32 | const collection = db.collection('data') 33 | 34 | const userExists = await collection.findOne({ username }) 35 | 36 | if(userExists){ 37 | return res.status(200).json(userExists); 38 | } 39 | 40 | return res.status(204).json({ message : "user does not exist"}); 41 | }catch(err){ 42 | return res.status(400).json({ 43 | message: err.message || "Unexpected error." 44 | }) 45 | } 46 | } -------------------------------------------------------------------------------- /src/pages/api/users/update.ts: -------------------------------------------------------------------------------- 1 | import { NowRequest, NowResponse } from '@vercel/node'; 2 | import { MongoClient } from 'mongodb'; 3 | import url from 'url'; 4 | 5 | let cachedDb = null; 6 | 7 | async function connectToDatabase(uri: string){ 8 | if(cachedDb){ 9 | return cachedDb; 10 | } 11 | 12 | const client = await MongoClient.connect(uri, { 13 | useNewUrlParser: true, 14 | useUnifiedTopology: true, 15 | }) 16 | 17 | const dbName = url.parse(uri).pathname.substring(1); 18 | 19 | const db = client.db(dbName); 20 | 21 | cachedDb = db; 22 | 23 | return db; 24 | } 25 | 26 | export default async function UpdateUser(req: NowRequest, res: NowResponse){ 27 | try{ 28 | const { username, data } = req.body; 29 | 30 | const db = await connectToDatabase(process.env.MONOGODB_URL); 31 | 32 | const collection = db.collection('data') 33 | 34 | const user = await collection.findOneAndUpdate( 35 | { username }, 36 | { $set: data }, 37 | { returnOriginal: false }, 38 | ); 39 | 40 | return res.status(200).json(user); 41 | }catch(err){ 42 | return res.status(400).json({ 43 | message: err.message || "Unexpected error." 44 | }) 45 | } 46 | } -------------------------------------------------------------------------------- /src/pages/home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Router from 'next/router'; 3 | import Head from 'next/head' 4 | import { GetServerSideProps } from 'next'; 5 | 6 | import { ExperienceBar } from '../components/ExperienceBar' 7 | import { SideBar } from '../components/SideBar' 8 | import Profile from '../components/Profile' 9 | import { CompletedChallenges } from '../components/CompletedChallenges' 10 | import { CountDown } from '../components/CountDown' 11 | 12 | import { ChallengeBox } from '../components/ChallengeBox' 13 | import { CountDownProvider } from '../contexts/CountDownContext'; 14 | import { ChallengesProvider } from '../contexts/ChallengesContext'; 15 | 16 | import { Container, LeftSide, RightSide } from '../styles/pages/home' 17 | import { useSession } from '../contexts/SessionContext'; 18 | interface IProps { 19 | username: string 20 | name: string 21 | avatar_url: string 22 | level: number 23 | currentExperience: number 24 | challengesCompleted: number 25 | } 26 | 27 | const Home: React.FC = (props) => { 28 | return ( 29 | 30 | 31 | 32 | Home | Move YourSelf 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 |
51 |
52 | ) 53 | } 54 | 55 | 56 | export const getServerSideProps: GetServerSideProps = async (ctx) => { 57 | const { user } = await ctx.req.cookies; 58 | 59 | if (!user) { 60 | if(typeof window === 'undefined'){ 61 | ctx.res.writeHead(302, { Location: '/' }) 62 | ctx.res.end() 63 | }else{ 64 | Router.push('/') 65 | } 66 | return { 67 | props: {} 68 | } 69 | }else{ 70 | const userFormatted = JSON.parse(user) 71 | 72 | return { 73 | props: { 74 | ...userFormatted 75 | } 76 | } 77 | }; 78 | } 79 | 80 | export default Home; -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { GetServerSideProps } from 'next'; 2 | import Router from 'next/router'; 3 | import Head from 'next/head' 4 | import React, { useState } from 'react' 5 | import useSound from 'use-sound' 6 | import { FiGithub, FiMoon, FiSun } from "react-icons/fi"; 7 | 8 | import Input from '../components/Input'; 9 | 10 | import { useTheme } from '../contexts/theme'; 11 | import { useSession } from '../contexts/SessionContext'; 12 | 13 | import turnOffSound from '../../public/sounds/turn-off.mp3'; 14 | import turnOnSound from '../../public/sounds/turn-on.mp3'; 15 | 16 | import { Container, LeftSide, RightSide, TitleContainer } from '../styles/pages' 17 | 18 | const Index: React.FC = () => { 19 | const { singIn } = useSession(); 20 | const {theme, ToggleTheme} = useTheme(); 21 | 22 | const [username, setUsername]= useState(''); 23 | 24 | const [play] = useSound(theme.title === 'dark' ? turnOffSound : turnOnSound) 25 | 26 | function handleClick(){ 27 | ToggleTheme() 28 | play() 29 | } 30 | 31 | async function handleUsername(e){ 32 | e.preventDefault(); 33 | if(username){ 34 | await singIn(username) 35 | } 36 | } 37 | 38 | return ( 39 | 40 | 41 | Início | Move YourSelf 42 | 43 |
44 | 45 | 48 | 49 | 50 | Logo 51 | 52 |
53 | Bem-vindo 54 | 55 | 56 | 57 | Faça login com seu Github para começar 58 | 59 | 60 |
61 | setUsername(e.target.value)} 65 | /> 66 |
67 |
68 |
69 |
70 |
71 | ) 72 | } 73 | 74 | export const getServerSideProps: GetServerSideProps = async (ctx) => { 75 | const {user} = await ctx.req.cookies; 76 | 77 | if (user) { 78 | if(typeof window === 'undefined'){ 79 | ctx.res.writeHead(302, { Location: '/home' }) 80 | ctx.res.end() 81 | }else{ 82 | Router.push('/home') 83 | } 84 | return { props: {} } 85 | }else{ 86 | return { props: {} } 87 | }; 88 | } 89 | 90 | export default Index; -------------------------------------------------------------------------------- /src/pages/leaderboard.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import axios from 'axios'; 3 | import React, { useEffect, useState } from 'react' 4 | 5 | import { SideBar } from '../components/SideBar' 6 | import { Loading } from '../components/Loading' 7 | import { Container } from '../styles/pages/leaderboard' 8 | 9 | const Leaderboard: React.FC = () => { 10 | 11 | const [ loading , setLoading] = useState(false); 12 | const [ users , setUsers] = useState([]); 13 | 14 | useEffect(() => { 15 | setLoading(true) 16 | axios.get('/api/users/all').then(({ data }) => { 17 | setUsers( data ) 18 | setLoading(false) 19 | }) 20 | },[]) 21 | 22 | return ( 23 | 24 | 25 | Leaderboard | Move YourSelf 26 | 27 | 28 |
29 |

30 | Leaderboard 31 |

32 | 33 | {loading ? : ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {users.map((item, index) => ( 46 | 47 | 48 | 58 | 62 | 66 | 67 | ))} 68 | 69 |
POSIÇÃOUSUÁRIODESAFIOSEXPERIÊNCIA ATUAl
{index + 1} 49 | {item.name}/ 50 |
51 | {item.name} 52 |
53 | Level 54 | Level {item.level} 55 |
56 |
57 |
59 | {item.challengesCompleted} 60 | {' Completados'} 61 | 63 | {item.currentExperience} 64 | {' xp'} 65 |
70 | )} 71 |
72 |
73 | ) 74 | } 75 | 76 | export default Leaderboard; -------------------------------------------------------------------------------- /src/styles/components/ChallengeBox/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | height: 100%; 5 | 6 | background: ${({ theme }) => theme.colors.backgroundLight}; 7 | border-radius: 5px; 8 | box-shadow: 0 0 60px rgba(0, 0, 0, 0.05); 9 | padding: 1.5rem 2rem; 10 | 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | justify-content: center; 15 | 16 | text-align: center; 17 | 18 | `; 19 | 20 | export const ChallengeNotActive = styled.div` 21 | 22 | display: flex; 23 | flex-direction: column; 24 | align-items: center; 25 | 26 | strong { 27 | font-size: 1.5rem; 28 | font-weight: 500; 29 | line-height: 1.4; 30 | } 31 | 32 | p{ 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | line-height: 1.4; 37 | max-width: 70%; 38 | margin-top: 3rem; 39 | 40 | img { 41 | margin-bottom: 1rem; 42 | } 43 | } 44 | `; 45 | 46 | export const ChallengeActive = styled.div` 47 | height: 100%; 48 | 49 | display: flex; 50 | flex-direction: column; 51 | 52 | header { 53 | color: ${({ theme }) => theme.colors.blue}; 54 | 55 | font-weight: 600; 56 | font-size: 1.25rem; 57 | padding: 0 2rem 1.5rem; 58 | border-bottom: 1px solid ${({ theme }) => theme.colors.grayLine} 59 | } 60 | 61 | main { 62 | flex: 1; 63 | display: flex; 64 | flex-direction: column; 65 | align-items: center; 66 | justify-content: center; 67 | 68 | strong { 69 | font-weight: 600; 70 | font-size: 2rem; 71 | color: ${({ theme }) => theme.colors.title}; 72 | 73 | margin: 1.5rem 0 1rem; 74 | } 75 | 76 | p { 77 | line-height: 1.5; 78 | } 79 | } 80 | 81 | footer{ 82 | display: grid; 83 | grid-template-columns: 1fr 1fr; 84 | gap: 1rem; 85 | 86 | button { 87 | height: 3rem; 88 | display: flex; 89 | align-items: center; 90 | justify-content: center; 91 | 92 | border: 0; 93 | border-radius: 5px; 94 | 95 | color: ${({ theme }) => theme.colors.white}; 96 | 97 | font-size: 1rem; 98 | font-weight: 600; 99 | 100 | transition: filter 0.3s ease; 101 | 102 | &:hover{ 103 | filter: brightness(0.9) 104 | } 105 | } 106 | } 107 | `; 108 | 109 | export const FailedButton = styled.button` 110 | background: ${({ theme }) => theme.colors.red}; 111 | `; 112 | 113 | export const SucceededButton = styled.button` 114 | background: ${({ theme }) => theme.colors.green}; 115 | `; -------------------------------------------------------------------------------- /src/styles/components/CompletedChallenges/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | display: flex; 5 | align-items: center; 6 | justify-content: space-between; 7 | 8 | margin: 3.5rem 0; 9 | padding-bottom: 1rem; 10 | 11 | border-bottom: 1px solid #d7e8ea; 12 | 13 | font-weight: 500; 14 | 15 | span:first-child{ 16 | font-size: 1.25rem; 17 | } 18 | 19 | span:last-child{ 20 | font-size: 1.5rem; 21 | } 22 | `; -------------------------------------------------------------------------------- /src/styles/components/CountDown/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const CountDownContainer = styled.div` 4 | display: flex; 5 | align-items: center; 6 | 7 | font-family: 'Rajdhani'; 8 | font-weight: 600; 9 | color: ${({ theme }) => theme.colors.titleTimer}; 10 | 11 | > div { 12 | flex: 1; 13 | 14 | display: flex; 15 | align-items: center; 16 | justify-content: space-evenly; 17 | 18 | background: ${({ theme }) => theme.colors.backgroundLight}; 19 | box-shadow: 0 0 60px rgba(0, 0, 0, 0.05); 20 | border-radius: 5px; 21 | font-size: 8.5rem; 22 | text-align: center; 23 | 24 | span:first-child { 25 | border-right: 1px solid #f0f2f3; 26 | } 27 | span:last-child { 28 | border-left: 1px solid #f0f2f3; 29 | } 30 | 31 | span { 32 | flex: 1; 33 | } 34 | } 35 | 36 | > span { 37 | font-size: 6.25rem; 38 | margin: 0 0.5rem; 39 | } 40 | `; 41 | 42 | export const CountDownButton = styled.button` 43 | width: 100%; 44 | height: 5rem; 45 | margin-top: 2rem; 46 | display: flex; 47 | align-items: center; 48 | justify-content: center; 49 | 50 | border: 0; 51 | border-radius: 5px; 52 | background: ${({ theme }) => theme.colors.blue}; 53 | color: ${({ theme }) => theme.colors.white}; 54 | 55 | font-size: 1.5rem; 56 | font-weight: 600; 57 | 58 | transition: background-color 0.2s ease; 59 | 60 | &:hover{ 61 | background: ${({ theme }) => theme.colors.blueDark} 62 | } 63 | `; 64 | 65 | 66 | export const StopCountDownButton = styled.button` 67 | width: 100%; 68 | height: 5rem; 69 | margin-top: 2rem; 70 | display: flex; 71 | align-items: center; 72 | justify-content: center; 73 | 74 | border: 0; 75 | border-radius: 5px; 76 | background: ${({ theme }) => theme.colors.backgroundLight}; 77 | color: ${({ theme }) => theme.colors.titleButton}; 78 | 79 | font-size: 1.5rem; 80 | font-weight: 600; 81 | 82 | transition: background-color 0.2s ease; 83 | 84 | &:hover{ 85 | background: ${({ theme }) => theme.colors.red}; 86 | color: ${({ theme }) => theme.colors.white}; 87 | } 88 | `; 89 | 90 | export const FinishedCountDownButton = styled.button` 91 | width: 100%; 92 | height: 5rem; 93 | margin-top: 2rem; 94 | display: flex; 95 | align-items: center; 96 | justify-content: center; 97 | 98 | border: 0; 99 | border-radius: 5px; 100 | background: ${({ theme }) => theme.colors.backgroundLight}; 101 | border-bottom: 5px solid ${({ theme }) => theme.colors.green}; 102 | color: ${({ theme }) => theme.colors.titleButton}; 103 | 104 | font-size: 1.5rem; 105 | font-weight: 600; 106 | 107 | cursor: default; 108 | `; -------------------------------------------------------------------------------- /src/styles/components/ExperienceBar/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.header` 4 | display: flex; 5 | align-items: center; 6 | 7 | span { 8 | font-size: 1rem; 9 | } 10 | 11 | > div{ 12 | flex: 1; 13 | height: 4px; 14 | border-radius: 4px; 15 | background: ${({ theme }) => theme.colors.grayLine}; 16 | margin: 0 1.5rem; 17 | position: relative; 18 | 19 | div { 20 | height: 4px; 21 | width:0; 22 | border-radius: 4px; 23 | background: ${({ theme }) => theme.colors.green}; 24 | } 25 | 26 | span { 27 | position: absolute; 28 | width: max-content; 29 | top: 12px; 30 | transform: translateX( -50% ); 31 | } 32 | } 33 | 34 | @media (max-width: 800px){ 35 | margin-top: 4rem; 36 | } 37 | `; -------------------------------------------------------------------------------- /src/styles/components/Input/index.ts: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | interface ContainerProps { 4 | isFocused: boolean; 5 | isFilled: boolean; 6 | isLoading: boolean; 7 | } 8 | 9 | export const Container = styled.div` 10 | display: flex; 11 | align-items: center; 12 | 13 | input { 14 | border: 0; 15 | background: linear-gradient( 90deg, ${({theme}) => theme.colors.inputBackground} 0%, ${({theme}) => theme.colors.inputBackgroundFinish} 100%); 16 | font-size: 1.3rem; 17 | padding: 1.65rem; 18 | 19 | border-radius: 5px 0px 0px 5px; 20 | 21 | color: ${({theme}) => theme.colors.white}; 22 | border: 1.5px solid transparent; 23 | 24 | /* outline-width: 0; */ 25 | outline-color: ${({ theme }) => theme.colors.inputBackground}; 26 | 27 | &:hover{ 28 | border: 1.5px solid ${({ theme }) => theme.colors.inputBackground}; 29 | } 30 | 31 | &::placeholder{ 32 | color: ${({theme}) => theme.colors.white}; 33 | opacity: 0.5; 34 | } 35 | } 36 | 37 | button { 38 | display: flex; 39 | align-items:center; 40 | justify-content: center; 41 | border: 1.5px solid ${({ theme }) => theme.colors.inputBackground}; 42 | outline-color: ${({ theme }) => theme.colors.inputBackground}; 43 | 44 | padding: 1.66rem; 45 | font-size: 2rem; 46 | background: ${({ theme }) => theme.colors.inputBackground}; 47 | color: ${({theme}) => theme.colors.white }; 48 | border-radius: 0px 5px 5px 0px; 49 | 50 | svg { 51 | width: 1.5rem; 52 | height: 1.5rem; 53 | ${(props) => 54 | props.isLoading && 55 | 56 | css` 57 | @keyframes Rote { 58 | 0% { 59 | transform: rotate(0deg); 60 | } 61 | 100% { 62 | transform: rotate(360deg); 63 | } 64 | } 65 | animation: Rote 1s infinite; 66 | `} 67 | } 68 | 69 | ${(props) => 70 | props.isFilled && 71 | css` 72 | border: 1.5px solid ${({ theme }) => theme.colors.green}; 73 | background: ${({ theme }) => theme.colors.green}; 74 | `} 75 | } 76 | 77 | @media (max-width: 520px){ 78 | input { 79 | width: 78%; 80 | } 81 | } 82 | `; -------------------------------------------------------------------------------- /src/styles/components/LevelUpModal/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Overlay = styled.header` 4 | background: ${({ theme }) => theme.colors.backgroundModal}; 5 | /* background: rgba(242, 243, 245, 0.8); */ 6 | position: fixed; 7 | top: 0; 8 | bottom: 0; 9 | right: 0; 10 | left: 0; 11 | 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | `; 16 | 17 | export const Container = styled.header` 18 | background: ${({ theme }) => theme.colors.backgroundModalContent}; 19 | width: 100%; 20 | max-width:400px; 21 | padding: 2rem 3rem; 22 | border-radius: 5px; 23 | box-shadow: 0 0 60px rgba(0, 0, 0, 0.05); 24 | text-align: center; 25 | position: relative; 26 | 27 | header { 28 | font-size: 8.75rem; 29 | font-weight: 600; 30 | 31 | color: ${({ theme }) => theme.colors.blue}; 32 | background: url('/icons/levelup.svg') no-repeat center; 33 | background-size: contain; 34 | 35 | text-shadow: 0px 10px 16px rgba(89, 101, 224, 0.3); 36 | } 37 | 38 | strong{ 39 | font-size: 2.25rem; 40 | color: ${({ theme }) => theme.colors.title}; 41 | } 42 | 43 | p { 44 | font-size: 1.25rem; 45 | color: ${({ theme }) => theme.colors.text}; 46 | margin-top: 0.25rem; 47 | } 48 | 49 | button { 50 | position: absolute; 51 | right: 0.5rem; 52 | top: 0.5rem; 53 | background: transparent; 54 | border:0; 55 | font-size: 0; 56 | } 57 | `; -------------------------------------------------------------------------------- /src/styles/components/Loading/index.ts: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | @keyframes teste{ 5 | 0% { 6 | background-position: 0% 0%; 7 | } 8 | 100% { 9 | background-position: 195px 0%; 10 | } 11 | }; 12 | 13 | /* section >div{ 14 | display: flex; 15 | } */ 16 | 17 | table{ 18 | border-spacing: 0 8px; 19 | border-radius: 5px; 20 | min-width: 650px; 21 | width: 100%; 22 | 23 | div, span, img, strong{ 24 | background: linear-gradient( 25 | -90deg, 26 | ${({theme}) => theme.colors.background }, 27 | ${({theme}) => theme.colors.backgroundLight }, 28 | ${({theme}) => theme.colors.background } 29 | ); 30 | animation: teste 1.2s ease-in-out infinite; 31 | } 32 | 33 | .separator{ 34 | height: 24px; 35 | } 36 | 37 | thead{ 38 | font-style: normal; 39 | font-weight: bold; 40 | font-size: 14px; 41 | line-height: 17px; 42 | text-transform: uppercase; 43 | color: ${({theme}) => theme.colors.text }; 44 | opacity: 0.5; 45 | } 46 | 47 | tbody{ 48 | margin-top: 24px; 49 | background: ${({theme}) => theme.colors.backgroundLight }; 50 | border-radius: 5px; 51 | 52 | tr{ 53 | border-radius: 5px; 54 | } 55 | 56 | th { 57 | padding: 16px 24px; 58 | 59 | &:first-child{ 60 | padding: 10px; 61 | position: relative; 62 | &::before{ 63 | top: 40%; 64 | left: 20%; 65 | margin: auto; 66 | animation: teste 1.2s ease-in-out infinite; 67 | content:""; 68 | height: 20px; 69 | width: 50px; 70 | display: flex; 71 | position: absolute; 72 | background: linear-gradient( 73 | -90deg, 74 | ${({theme}) => theme.colors.background }, 75 | ${({theme}) => theme.colors.backgroundLight }, 76 | ${({theme}) => theme.colors.background } 77 | ); 78 | } 79 | } 80 | 81 | > span{ 82 | position:relative; 83 | &::before{ 84 | animation: teste 1.2s ease-in-out infinite; 85 | content:""; 86 | height: 20px; 87 | top: 50%; 88 | left: 50%; 89 | width: 100px; 90 | display: flex; 91 | position: absolute; 92 | background: linear-gradient( 93 | -90deg, 94 | ${({theme}) => theme.colors.background }, 95 | ${({theme}) => theme.colors.backgroundLight }, 96 | ${({theme}) => theme.colors.background } 97 | ); 98 | } 99 | } 100 | 101 | &:first-child { 102 | border-radius: 5px 0px 0px 5px; 103 | border-right: 2px solid ${({theme}) => theme.colors.background }; 104 | } 105 | 106 | &:last-child { 107 | border-radius: 0px 5px 5px 0px; 108 | } 109 | 110 | &:nth-child(2) { 111 | display: flex; 112 | align-items: center; 113 | justify-content:center; 114 | 115 | > div:first-child { 116 | width: 64px; 117 | height: 64px; 118 | border-radius: 50%; 119 | margin-right: 16px; 120 | } 121 | 122 | > div{ 123 | display: flex; 124 | flex-direction: column; 125 | margin-right: auto; 126 | 127 | strong { 128 | position: relative; 129 | &::before{ 130 | animation: teste 1.2s ease-in-out infinite; 131 | content:""; 132 | height: 20px; 133 | width: 200px; 134 | display: flex; 135 | position: absolute; 136 | background: linear-gradient( 137 | -90deg, 138 | ${({theme}) => theme.colors.background }, 139 | ${({theme}) => theme.colors.backgroundLight }, 140 | ${({theme}) => theme.colors.background } 141 | ); 142 | } 143 | } 144 | } 145 | } 146 | } 147 | } 148 | } 149 | 150 | @media (max-width: 800px){ 151 | 152 | } 153 | `; -------------------------------------------------------------------------------- /src/styles/components/Profile/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | display: flex; 5 | align-items: center; 6 | 7 | > img { 8 | width: 5.5rem; 9 | height: 5.5rem; 10 | border-radius: 50%; 11 | } 12 | 13 | div{ 14 | margin-left: 1.5rem; 15 | 16 | strong { 17 | font-size: 1.5rem; 18 | font-weight: 600; 19 | color: ${({theme}) => theme.colors.title } 20 | } 21 | 22 | p { 23 | font-size: 1rem; 24 | margin-top: 0.5rem; 25 | 26 | > img { 27 | margin-right: 0.5rem; 28 | } 29 | } 30 | } 31 | 32 | button { 33 | background: transparent; 34 | border: 0; 35 | margin-left: auto; 36 | cursor: pointer; 37 | color: ${({ theme }) => theme.colors.titleTimer}; 38 | 39 | &:hover{ 40 | color: ${({ theme }) => theme.colors.red}; 41 | } 42 | } 43 | 44 | @media (max-width: 800px){ 45 | button { 46 | width: 2.2rem; 47 | } 48 | } 49 | `; -------------------------------------------------------------------------------- /src/styles/components/SideBar/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.nav` 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: space-between; 7 | align-items: center; 8 | 9 | position: absolute; 10 | left: 0; 11 | top: 0; 12 | 13 | height: 100vh; 14 | width: 3rem; 15 | padding: 2.3rem 2.2rem; 16 | 17 | background: ${({ theme }) => theme.colors.backgroundLight}; 18 | 19 | > svg { 20 | path{ 21 | fill: ${({ theme }) => theme.colors.logoColor}; 22 | } 23 | } 24 | 25 | > nav a{ 26 | display: flex; 27 | cursor: pointer; 28 | position: relative; 29 | 30 | &:not(:first-child){ 31 | margin-top: 16px; 32 | } 33 | 34 | &.active{ 35 | color: ${({ theme }) => theme.colors.logoColor}; 36 | 37 | &::before{ 38 | content: ""; 39 | background: ${({ theme }) => theme.colors.logoColor}; 40 | border-radius: 0px 5px 5px 0px; 41 | position: absolute; 42 | left: -1.24rem; 43 | width: 4px; 44 | height: 100%; 45 | 46 | @media (max-width: 1366px){ 47 | left: -1rem; 48 | } 49 | } 50 | } 51 | } 52 | 53 | button{ 54 | border: 0; 55 | background: transparent; 56 | outline-color: ${({ theme }) => theme.colors.backgroundLight}; 57 | 58 | svg { 59 | color: ${({ theme }) => theme.colors.text}; 60 | } 61 | } 62 | 63 | @media (max-width: 800px){ 64 | flex-direction: row; 65 | 66 | padding: 2rem; 67 | left: unset; 68 | right: 0; 69 | top: 0; 70 | 71 | width: 100%; 72 | height: 3rem; 73 | 74 | svg { 75 | width: 2.2rem; 76 | } 77 | 78 | > div { 79 | display: flex; 80 | align-items:center; 81 | 82 | a:last-child{ 83 | margin-left: 16px; 84 | margin-top: 0; 85 | } 86 | } 87 | } 88 | 89 | @media (max-width: 800px){ 90 | >nav{ 91 | display: flex; 92 | align-items:center; 93 | 94 | a { 95 | margin-left: 16px; 96 | 97 | &.active::before{ 98 | content: ""; 99 | top: -26px; 100 | left: 13px; 101 | transform: rotate(90deg); 102 | } 103 | 104 | &:not(:first-child){ 105 | margin-top: 0px; 106 | } 107 | } 108 | } 109 | } 110 | `; -------------------------------------------------------------------------------- /src/styles/global.ts: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components' 2 | 3 | export const GlobalStyle = createGlobalStyle` 4 | * { 5 | margin: 0; 6 | padding: 0; 7 | box-sizing: border-box; 8 | } 9 | 10 | @media(max-width: 1366px){ 11 | html{ 12 | font-size: 83.75% 13 | } 14 | } 15 | 16 | @media(max-width: 720px){ 17 | html{ 18 | font-size: 87.5% 19 | } 20 | } 21 | 22 | body { 23 | background: ${({ theme }) => theme.colors.background}; 24 | color: ${({ theme }) => theme.colors.text}; 25 | font-family: 'Inter', sans-serif; 26 | } 27 | 28 | body, input, textarea, button{ 29 | font: 400 1rem "Inter", sans-serif; 30 | } 31 | 32 | button{ 33 | cursor: pointer; 34 | } 35 | 36 | a { 37 | color: inherit; 38 | text-decoration: none; 39 | } 40 | ` -------------------------------------------------------------------------------- /src/styles/pages/home.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | 4 | export const Container = styled.div` 5 | height: 100vh; 6 | max-width: 992px; 7 | margin: 0 auto; 8 | padding: 2.5rem 2rem 2.5rem 6.5rem; 9 | 10 | display: flex; 11 | flex-direction: column; 12 | 13 | section { 14 | flex: 1; 15 | display: grid; 16 | grid-template-columns: 1fr 1fr; 17 | gap: 6.25rem; 18 | align-content: center; 19 | } 20 | 21 | @media (max-width: 992px){ 22 | section { 23 | gap: 1.25rem; 24 | } 25 | } 26 | 27 | @media (max-width: 800px){ 28 | padding: 1rem; 29 | section { 30 | margin-top: 3.5rem; 31 | grid-template-columns: auto; 32 | } 33 | } 34 | 35 | @media (max-width: 542px){ 36 | section { 37 | grid-template-columns: auto; 38 | } 39 | } 40 | ` 41 | 42 | export const LeftSide = styled.div` 43 | 44 | `; 45 | 46 | export const RightSide = styled.div` 47 | @media (max-width: 800px){ 48 | margin-bottom: 1rem; 49 | } 50 | `; 51 | -------------------------------------------------------------------------------- /src/styles/pages/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | 4 | export const Container = styled.div` 5 | height: 100vh; 6 | width: 100vw; 7 | /* margin: 0 auto; */ 8 | padding: 2.5rem 2rem; 9 | 10 | display: flex; 11 | flex-direction: column; 12 | 13 | background: url('/background-logo.png') no-repeat; 14 | background-size: contain; 15 | background-position: center left; 16 | background-color: ${({theme}) => theme.colors.backgroundIndex }; 17 | 18 | section { 19 | max-width: 992px; 20 | margin: 0 auto; 21 | flex: 1; 22 | display: grid; 23 | grid-template-columns: 1fr 1fr; 24 | gap: 6.25rem; 25 | align-content: center; 26 | position: relative; 27 | 28 | 29 | 30 | @media (max-width: 520px){ 31 | 32 | grid-template-columns: 1fr; 33 | gap: 0; 34 | } 35 | } 36 | ` 37 | 38 | export const LeftSide = styled.div` 39 | /* position: relative; */ 40 | 41 | button{ 42 | position: absolute; 43 | right: 0; 44 | top: 0; 45 | border: 0; 46 | background: transparent; 47 | color: ${({ theme }) => theme.colors.white}; 48 | 49 | } 50 | `; 51 | 52 | export const RightSide = styled.div` 53 | display :flex; 54 | justify-content: space-between; 55 | flex-direction: column; 56 | 57 | >div{ 58 | display: flex; 59 | flex-direction: column; 60 | 61 | strong { 62 | margin-top: 8rem; 63 | font-size: 3rem; 64 | font-weight: 500; 65 | color: ${({theme}) => theme.colors.white }; 66 | } 67 | } 68 | 69 | @media (max-width: 520px){ 70 | width: 85vw; 71 | >div{ 72 | strong { 73 | font-size: 2rem; 74 | } 75 | } 76 | } 77 | `; 78 | 79 | export const TitleContainer = styled.div` 80 | display: flex; 81 | 82 | margin: 3rem 0; 83 | 84 | svg{ 85 | color: ${({theme}) => theme.colors.white }; 86 | } 87 | 88 | span { 89 | max-width: 300px; 90 | margin-left: 1rem; 91 | font-size: 1.4rem; 92 | font-weight: 500; 93 | color: ${({theme}) => theme.colors.white }; 94 | } 95 | 96 | @media (max-width: 520px){ 97 | span { 98 | font-size: 1.3rem; 99 | } 100 | } 101 | `; -------------------------------------------------------------------------------- /src/styles/pages/leaderboard.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | height: 100vh; 5 | /* width: 100vw; */ 6 | /* margin: 0 auto; */ 7 | overflow-x: hidden; 8 | padding: 2.5rem 2rem; 9 | 10 | display: flex; 11 | flex-direction: column; 12 | 13 | background-color: ${({theme}) => theme.colors.background }; 14 | 15 | section { 16 | margin: 0 25px; 17 | flex: 1; 18 | display: flex; 19 | align-content: center; 20 | justify-content:center; 21 | flex-direction: column; 22 | padding-left: 40px; 23 | 24 | h2 { 25 | font-style: normal; 26 | font-weight: 600; 27 | font-size: 36px; 28 | line-height: 46px; 29 | color: ${({theme}) => theme.colors.titleButton }; 30 | margin-bottom: 40px; 31 | } 32 | 33 | table{ 34 | border-spacing: 0 8px; 35 | border-radius: 5px; 36 | min-width: 650px; 37 | 38 | .separator{ 39 | height: 24px; 40 | } 41 | 42 | thead{ 43 | font-style: normal; 44 | font-weight: bold; 45 | font-size: 14px; 46 | line-height: 17px; 47 | text-transform: uppercase; 48 | color: ${({theme}) => theme.colors.text }; 49 | opacity: 0.5; 50 | } 51 | 52 | tbody{ 53 | margin-top: 24px; 54 | background: ${({theme}) => theme.colors.backgroundLight }; 55 | border-radius: 5px; 56 | 57 | tr{ 58 | border-radius: 5px; 59 | 60 | th:first-child{ 61 | font-weight: 500; 62 | font-size: 18px; 63 | } 64 | } 65 | 66 | th { 67 | padding: 16px; 68 | 69 | >span{ 70 | font-style: normal; 71 | font-weight: 500; 72 | font-size: 16px; 73 | line-height: 19px; 74 | color: ${({theme}) => theme.colors.hightLightTable }; 75 | } 76 | 77 | &:first-child { 78 | border-radius: 5px 0px 0px 5px; 79 | border-right: 2px solid ${({theme}) => theme.colors.background }; 80 | } 81 | 82 | &:last-child { 83 | border-radius: 0px 5px 5px 0px; 84 | } 85 | 86 | &:nth-child(2) { 87 | display: flex; 88 | align-items: center; 89 | justify-content:center; 90 | 91 | > img { 92 | width: 64px; 93 | height: 64px; 94 | border-radius: 50%; 95 | margin-right: 16px; 96 | } 97 | 98 | > div{ 99 | display: flex; 100 | flex-direction: column; 101 | margin-right: auto; 102 | 103 | strong { 104 | font-style: normal; 105 | font-weight: 600; 106 | font-size: 20px; 107 | line-height: 24px; 108 | color: ${({theme}) => theme.colors.titleTimer }; 109 | } 110 | 111 | div{ 112 | display: flex; 113 | align-items: flex-start; 114 | justify-content: flex-start; 115 | margin-top: 8px; 116 | 117 | img{ 118 | margin-right: 10px 119 | } 120 | 121 | span{ 122 | font-style: normal; 123 | font-weight: normal; 124 | font-size: 1rem; 125 | line-height: 19px; 126 | } 127 | } 128 | 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | @media (max-width: 800px){ 136 | margin: 0 10px; 137 | padding-left: 0px; 138 | overflow-x: scroll; 139 | } 140 | } 141 | `; -------------------------------------------------------------------------------- /src/styles/theme.ts: -------------------------------------------------------------------------------- 1 | export const light = { 2 | title: 'light', 3 | colors: { 4 | white: '#fff', 5 | background: '#f2f3f5', 6 | grayLine: '#dcdde0', 7 | text: '#666666', 8 | textHighlight: '#b3b9ff', 9 | title: '#2e384d', 10 | red: '#e83f5b', 11 | green: '#4cd62b', 12 | blue: '#5965e0', 13 | blueDark: '#4953b8', 14 | blueTwitter: '#2aa9e0', 15 | 16 | backgroundModal: 'rgba(242, 243, 245, 0.8)', 17 | backgroundModalContent: '#fff', 18 | backgroundIndex: '#5964e0', 19 | backgroundLight: '#fff', 20 | titleTimer: '#2e384d', 21 | textDark: '#666666', 22 | titleButton: '#2e384d', 23 | titleLight: '#848FFF', 24 | titleLighten: '#B2B9FF', 25 | logoColor: '#5965e0', 26 | inputBackground: '#4953b8', 27 | inputBackgroundFinish: 'rgba(73, 83, 184, 0.2)', 28 | hightLightTable: '#5965E0', 29 | } 30 | } 31 | 32 | export const dark = { 33 | title: 'dark', 34 | colors: { 35 | white: '#fff', 36 | background: '#0B1529', 37 | grayLine: '#dcdde0', 38 | text: '#FFFFFF', 39 | textHighlight: '#b3b9ff', 40 | title: '#5965E0', 41 | red: '#e83f5b', 42 | green: '#4cd62b', 43 | blue: '#5965e0', 44 | blueDark: '#4953b8', 45 | blueTwitter: '#2aa9e0', 46 | 47 | backgroundModal: 'rgba(11, 21, 41, 0.7)', 48 | backgroundModalContent: '#0B1529', 49 | backgroundIndex: '#0B1529', 50 | backgroundLight: '#273248', 51 | titleTimer: '#dcdde0', 52 | textDark: '#666666', 53 | titleButton: '#dcdde0', 54 | titleLight: '#848FFF', 55 | titleLighten: '#B2B9FF', 56 | logoColor: '#fff', 57 | inputBackground: 'rgba( 255, 255, 255, 0.45)', 58 | inputBackgroundFinish: 'transparent', 59 | hightLightTable: '#929eaa', 60 | }, 61 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "include": [ 22 | "next-env.d.ts", 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | --------------------------------------------------------------------------------