├── .devcontainer ├── Dockerfile ├── devcontainer.json ├── docker-compose.yml └── scripts │ ├── node.sh │ └── user.sh ├── .dockerignore ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── labeler.yml ├── pull_request_template.md └── workflows │ ├── build-and-deploy.yml │ ├── greeting.yml │ ├── label-pr.yml │ ├── lint-and-test.yml │ └── status_embed.yml ├── .gitignore ├── .husky └── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .storybook ├── main.js ├── manager.js └── preview.js ├── .yarn ├── plugins │ └── @yarnpkg │ │ ├── plugin-interactive-tools.cjs │ │ ├── plugin-typescript.cjs │ │ └── plugin-workspace-tools.cjs └── releases │ └── yarn-3.0.2.cjs ├── .yarnrc.yml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── lint-staged.config.js ├── next-env.d.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── images │ ├── home │ │ ├── challenges.svg │ │ ├── challengesSmall.svg │ │ ├── home1.svg │ │ ├── home1small.svg │ │ └── home2.svg │ ├── navbar │ │ ├── Drop.svg │ │ └── close.svg │ └── timathon │ │ ├── history.svg │ │ ├── historyCard.svg │ │ ├── prizes.svg │ │ ├── prizesCard.svg │ │ ├── teamsCard.svg │ │ ├── timathon.svg │ │ └── timeline.svg └── robots.txt ├── src ├── components │ ├── Badge.tsx │ ├── Button.tsx │ ├── Card.tsx │ ├── ChallengeHistory.tsx │ ├── Loading │ │ ├── Loading.component.tsx │ │ └── Styles.ts │ ├── Navbar │ │ ├── Navbar.tsx │ │ └── Styles.tsx │ ├── PageContainer.tsx │ ├── Spinner.tsx │ ├── Title.tsx │ └── WithAuth.tsx ├── constants.ts ├── helpers │ ├── discord.ts │ ├── getOrigin.ts │ └── index.ts ├── icons │ ├── Badge1.tsx │ ├── Badge2.tsx │ ├── Badge3.tsx │ ├── Discord.tsx │ ├── Navbar.tsx │ ├── Youtube.tsx │ └── index.tsx ├── modules │ └── profile │ │ ├── ChallengeHistoryController.tsx │ │ └── Profile.tsx ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── auth │ │ └── discord │ │ │ └── callback.tsx │ ├── index.tsx │ ├── login.tsx │ ├── profile.tsx │ └── timathon.tsx ├── stores │ └── useAuthStore.tsx ├── stories │ ├── Button.stories.tsx │ ├── Introduction.stories.mdx │ └── utils │ │ ├── toBool.ts │ │ ├── toEnum.ts │ │ └── toStr.ts ├── styles │ ├── index.scss │ └── tailwind.css └── types │ └── models.types.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.10 2 | 3 | ENV USERNAME=twt 4 | ENV USER_UID=1001 5 | ENV USER_GID=1001 6 | 7 | COPY scripts/*.sh /tmp/scripts/ 8 | 9 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive 10 | RUN bash /tmp/scripts/user.sh "$USERNAME" "$USER_UID" "$USER_GID" 11 | 12 | ENV NVM_DIR=/home/$USERNAME/.nvm 13 | ENV NODE_VERSION="lts/*" 14 | 15 | RUN bash /tmp/scripts/node.sh "$NVM_DIR" "$NODE_VERSION" "$USERNAME" 16 | 17 | RUN apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /root/.gnupg 18 | RUN rm -rf /tmp/scripts 19 | 20 | CMD [ "sleep", "infinity" ] 21 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tech With Tim", 3 | "dockerComposeFile": "docker-compose.yml", 4 | "service": "workspace", 5 | "forwardPorts": [3000], 6 | "workspaceFolder": "/workspace", 7 | "extensions": [ 8 | "dbaeumer.vscode-eslint", 9 | "esbenp.prettier-vscode", 10 | "bradlc.vscode-tailwindcss", 11 | "dsznajder.es7-react-js-snippets" 12 | ], 13 | "remoteUser": "twt" 14 | } 15 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | api: 5 | image: ghcr.io/tech-with-tim/api:latest 6 | ports: 7 | - 5000:5000 8 | env_file: .env 9 | restart: unless-stopped 10 | environment: 11 | SECRET_KEY: key 12 | POSTGRES_URI: postgresql://api:api@db:5432/api 13 | 14 | cdn: 15 | image: techwithtim/cdn:latest 16 | env_file: .env 17 | restart: unless-stopped 18 | ports: 19 | - 8000:5000 20 | environment: 21 | POSTGRES_USER: api 22 | POSTGRES_PASSWORD: api 23 | DB_NAME: api 24 | DB_HOST: db 25 | DB_PORT: "5432" 26 | SECRET_KEY: key 27 | MAX_FILE_SIZE: "30" 28 | 29 | redis: 30 | image: redis:6.2.5-alpine 31 | 32 | websockets: 33 | image: techwithtim/websockets:latest 34 | restart: unless-stopped 35 | ports: 36 | - 5001:5000 37 | environment: 38 | POSTGRES_USER: api 39 | POSTGRES_PASSWORD: api 40 | DB_NAME: api 41 | DB_HOST: db 42 | DB_PORT: "5432" 43 | SECRET_KEY: key 44 | REDIS_HOST: redis 45 | REDIS_USERNAME: "" 46 | REDIS_PASS: "" 47 | REDIS_DB: "0" 48 | 49 | workspace: 50 | build: 51 | context: . 52 | dockerfile: Dockerfile 53 | env_file: .env 54 | volumes: 55 | - ..:/workspace 56 | 57 | db: 58 | image: postgres:13-alpine 59 | restart: unless-stopped 60 | environment: 61 | POSTGRES_USERNAME: api 62 | POSTGRES_PASSWORD: api 63 | POSTGRES_DATABASE: api 64 | -------------------------------------------------------------------------------- /.devcontainer/scripts/node.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | USERNAME=${3:-"twt"} 4 | export NVM_DIR=${1:-"/home/$USERNAME/.nvm"} 5 | export NODE_VERSION=${2:-"lts/*"} 6 | 7 | set -e 8 | 9 | # install all dependencies 10 | apt-get update \ 11 | && apt-get install -y curl ca-certificates tar gnupg2 \ 12 | && apt-get -y autoclean 13 | 14 | # install yarn 15 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | (OUT=$(apt-key add - 2>&1) || echo $OUT) 16 | echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list 17 | apt-get update 18 | apt-get -y install --no-install-recommends yarn 19 | 20 | # install nvm 21 | su ${USERNAME} -c "mkdir $NVM_DIR" 22 | su ${USERNAME} -c "curl --silent -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.36.0/install.sh | bash" 23 | 24 | # install node and npm 25 | su ${USERNAME} -c "source $NVM_DIR/nvm.sh \ 26 | && nvm install $NODE_VERSION \ 27 | && nvm alias default $NODE_VERSION \ 28 | && nvm use default" 29 | -------------------------------------------------------------------------------- /.devcontainer/scripts/user.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | USERNAME=${1:-"twt"} 4 | USER_UID=${2:-1001} 5 | USER_GID=${3:-$USER_UID} 6 | 7 | PACKAGE_LIST="apt-utils \ 8 | git \ 9 | htop \ 10 | curl \ 11 | wget \ 12 | unzip \ 13 | zip \ 14 | vim \ 15 | less \ 16 | sudo \ 17 | man-db \ 18 | make \ 19 | build-essential" 20 | 21 | # install packages 22 | apt-get -y install --no-install-recommends ${PACKAGE_LIST} 23 | 24 | # add non-root user 25 | groupadd --gid $USER_GID $USERNAME 26 | useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME 27 | echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME 28 | chmod 0440 /etc/sudoers.d/$USERNAME 29 | chown ${USERNAME}:${USERNAME} "/home/${USERNAME}/.bashrc" 30 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | .yarn/* 6 | !.yarn/releases 7 | !.yarn/plugins 8 | !.yarn/sdks 9 | !.yarn/versions 10 | .pnp.* 11 | 12 | # testing 13 | /coverage 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # IDE 30 | .idea 31 | .vscode 32 | 33 | .eslintcache 34 | .env 35 | 36 | *Dockerfile* 37 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /.github 2 | /build/** 3 | /coverage/** 4 | /docs/** 5 | /jsdoc/** 6 | /templates/** 7 | /tests/bench/** 8 | /tests/fixtures/** 9 | /tests/performance/** 10 | /tmp/** 11 | /tools/internal-rules/node_modules/** 12 | test.js 13 | !.eslintrc.js 14 | .vscode 15 | .pnp.js 16 | .yarn -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { "ecmaVersion": 2021 }, 8 | "ignorePatterns": ["node_modules/*", ".build/*", "!.prettierrc.js"], 9 | "extends": ["eslint:recommended"], 10 | "overrides": [ 11 | { 12 | "files": ["**/*.ts", "**/*.tsx"], 13 | "parser": "@typescript-eslint/parser", 14 | "settings": { "react": { "version": "detect" } }, 15 | "env": { 16 | "browser": true, 17 | "node": true, 18 | "es6": true 19 | }, 20 | "extends": [ 21 | "prettier", 22 | "eslint:recommended", 23 | "plugin:@typescript-eslint/recommended", 24 | "plugin:react/recommended", 25 | "plugin:react-hooks/recommended", 26 | "plugin:jsx-a11y/recommended", 27 | "plugin:prettier/recommended" 28 | ], 29 | "rules": { 30 | "semi": ["warn", "always"], 31 | "react/prop-types": "off", 32 | "quotes": ["warn", "double"], 33 | "react/display-name": ["off"], 34 | "no-case-declarations": "off", 35 | "jsx-a11y/anchor-is-valid": "off", 36 | "@typescript-eslint/ban-ts-comment": "off", 37 | "jsx-a11y/click-events-have-key-events": "off", 38 | "@typescript-eslint/no-unused-vars": ["error"], 39 | "jsx-a11y/no-static-element-interactions": "off", 40 | "@typescript-eslint/no-non-null-assertion": "off", 41 | "@typescript-eslint/explicit-function-return-type": ["off"], 42 | "prettier/prettier": ["error", {}, { "usePrettierrc": true }] 43 | } 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord Server 4 | url: https://discord.gg/twt 5 | about: Need any help with the repository? 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | DevOps: 2 | - .github/workflows/* 3 | - Dockerfile 4 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/build-and-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build & Deploy 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Lint & Test"] 6 | branches: ["prod"] 7 | types: ["completed"] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' 13 | name: Build & Deploy 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | 20 | - name: Create SHA Container Tag 21 | id: sha_tag 22 | run: | 23 | tag=$(cut -c 1-7 <<< $GITHUB_SHA) 24 | echo "::set-output name=tag::$tag" 25 | 26 | - name: Set up QEMU 27 | uses: docker/setup-qemu-action@v1 28 | 29 | - name: Set up Docker Buildx 30 | id: buildx 31 | uses: docker/setup-buildx-action@v1 32 | 33 | - name: Login to Docker Hub 34 | uses: docker/login-action@v1 35 | with: 36 | username: ${{ secrets.DOCKER_USER }} 37 | password: ${{ secrets.DOCKER_PASS }} 38 | 39 | - name: Cache Docker layers 40 | uses: actions/cache@v2 41 | with: 42 | path: /tmp/.buildx-cache 43 | key: ${{ runner.os }}-buildx-${{ github.sha }} 44 | restore-keys: | 45 | ${{ runner.os }}-buildx- 46 | 47 | - name: Build and Push 48 | id: docker_build 49 | uses: docker/build-push-action@v2 50 | with: 51 | push: true 52 | tags: | 53 | techwithtim/frontend:${{ steps.sha_tag.outputs.tag }} 54 | techwithtim/frontend:latest 55 | builder: ${{ steps.buildx.outputs.name }} 56 | cache-to: type=local,dest=/tmp/.buildx-cache 57 | cache-from: type=local,src=/tmp/.buildx-cache 58 | 59 | deploy: 60 | name: Deploy on Kubernetes cluster 61 | runs-on: ubuntu-20.04 62 | needs: build 63 | 64 | steps: 65 | - name: Checkout Repo 66 | uses: actions/checkout@v2 67 | with: 68 | repository: Tech-With-Tim/k8s 69 | token: ${{ secrets.REPO_TOKEN }} 70 | 71 | - name: Create SHA Container Tag 72 | id: sha_tag 73 | run: | 74 | tag=$(cut -c 1-7 <<< $GITHUB_SHA) 75 | echo "::set-output name=tag::$tag" 76 | 77 | - name: Deploy to Kubernetes 78 | uses: fjogeleit/yaml-update-action@master 79 | with: 80 | repository: Tech-With-Tim/k8s 81 | token: ${{ secrets.REPO_TOKEN }} 82 | branch: "main" 83 | createPR: "false" 84 | updateFile: "true" 85 | message: "Redeploy Frontend: `${{ steps.sha_tag.outputs.tag }}`" 86 | valueFile: "frontend/deployment.yml" 87 | propertyPath: "spec.template.spec.containers.0.image" 88 | value: "techwithtim/frontend:${{ steps.sha_tag.outputs.tag }}" 89 | -------------------------------------------------------------------------------- /.github/workflows/greeting.yml: -------------------------------------------------------------------------------- 1 | name: Greeting 2 | 3 | on: [issues, pull_request] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: | 13 | Hi **${{ github.actor }}**, Thanks for reporting an issue in our Repository. Please follow the issue template while opening an issue. It should have steps to retrace the issue and other information mentioned in the template. 14 | pr-message: | 15 | 16 | Hey **${{ github.actor }}** welcome to the repo for the Tech With Tim website 17 | Please follow the following guidelines while opening a PR: 18 | - Always write clean code 19 | - Please write tests for whatever functionality you add 20 | - Always leave comments so that others can understand what you write 21 | - CSS frameworks are not welcomed here 22 | -------------------------------------------------------------------------------- /.github/workflows/label-pr.yml: -------------------------------------------------------------------------------- 1 | name: Label_pr 2 | 3 | on: 4 | - pull_request_target 5 | 6 | jobs: 7 | triage: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/labeler@main 11 | with: 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 13 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Lint & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | packages: 11 | name: Install Dependencies 12 | runs-on: ubuntu-20.04 13 | steps: 14 | - name: Checkout Repository 15 | uses: actions/checkout@v2 16 | 17 | - name: Cache 18 | uses: actions/cache@v2 19 | id: yarn-cache 20 | with: 21 | path: | 22 | ./node_modules 23 | ./.yarn 24 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 25 | restore-keys: | 26 | ${{ runner.os }}-yarn-2 27 | 28 | - name: Install Packages 29 | run: yarn install 30 | 31 | lint: 32 | name: Lint 33 | runs-on: ubuntu-20.04 34 | needs: packages 35 | steps: 36 | - name: Checkout Repository 37 | uses: actions/checkout@v2 38 | 39 | - name: Restore Cache 40 | uses: actions/cache@v2 41 | id: yarn-cache 42 | with: 43 | path: | 44 | ./node_modules 45 | ./.yarn 46 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 47 | restore-keys: | 48 | ${{ runner.os }}-yarn-2 49 | 50 | - name: Run lint 51 | run: yarn run lint 52 | 53 | # TODO: tesitng job 54 | 55 | build: 56 | name: Build 57 | runs-on: ubuntu-20.04 58 | needs: 59 | - packages 60 | - lint 61 | env: 62 | NEXT_TELEMETRY_DISABLED: 1 63 | steps: 64 | - name: Checkout Repository 65 | uses: actions/checkout@v2 66 | 67 | - name: Restore Cache 68 | uses: actions/cache@v2 69 | id: yarn-cache 70 | with: 71 | path: | 72 | ./node_modules 73 | ./.yarn 74 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 75 | restore-keys: | 76 | ${{ runner.os }}-yarn-2 77 | 78 | - name: Run build 79 | run: yarn run build 80 | -------------------------------------------------------------------------------- /.github/workflows/status_embed.yml: -------------------------------------------------------------------------------- 1 | name: Status Embeds 2 | 3 | on: 4 | workflow_run: 5 | workflows: 6 | - Build & Deploy 7 | - Lint & Test 8 | types: 9 | - completed 10 | 11 | jobs: 12 | embed: 13 | name: Send Status Embed 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Github Actions Embed 17 | uses: SebastiaanZ/github-status-embed-for-discord@v0.2.1 18 | with: 19 | webhook_id: '796006792995143710' 20 | webhook_token: ${{ secrets.WEBHOOK_TOKEN }} 21 | 22 | workflow_name: ${{ github.event.workflow_run.name }} 23 | run_id: ${{ github.event.workflow_run.id }} 24 | run_number: ${{ github.event.workflow_run.run_number }} 25 | status: ${{ github.event.workflow_run.conclusion }} 26 | actor: ${{ github.actor }} 27 | repository: ${{ github.repository }} 28 | ref: ${{ github.ref }} 29 | sha: ${{ github.event.workflow_run.head_sha }} 30 | -------------------------------------------------------------------------------- /.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 | # production 12 | /build 13 | /postgres 14 | /postgres-data 15 | /api/postgres-data 16 | /api/postgres 17 | 18 | # misc 19 | .DS_Store 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # IDE 30 | .idea 31 | .vscode 32 | 33 | # Nextjs 34 | .next 35 | 36 | .eslintcache 37 | 38 | .env 39 | 40 | .yarn/* 41 | !.yarn/releases 42 | !.yarn/plugins 43 | !.yarn/sdks 44 | !.yarn/versions 45 | .pnp.* 46 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.github 2 | /build/** 3 | /coverage/** 4 | /docs/** 5 | /jsdoc/** 6 | /templates/** 7 | /tests/bench/** 8 | /tests/fixtures/** 9 | /tests/performance/** 10 | /tmp/** 11 | /tools/internal-rules/node_modules/** 12 | test.js 13 | !.eslintrc.js 14 | .vscode 15 | .pnp.js 16 | .yarn -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": false, 5 | "printWidth": 100, 6 | "tabWidth": 2, 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], 5 | addons: [ 6 | "@storybook/addon-links", 7 | "@storybook/addon-essentials", 8 | "@storybook/preset-create-react-app", 9 | ], 10 | webpackFinal: async (config) => { 11 | config.module.rules.push({ 12 | test: /\.css$/, 13 | use: [ 14 | { 15 | loader: "postcss-loader", 16 | options: { 17 | ident: "postcss", 18 | plugins: [require("tailwindcss"), require("autoprefixer")], 19 | }, 20 | }, 21 | ], 22 | include: path.resolve(__dirname, "../"), 23 | }); 24 | 25 | return config; 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from "@storybook/addons"; 2 | import { create } from "@storybook/theming"; 3 | 4 | addons.setConfig({ 5 | theme: create({ 6 | base: "dark", 7 | brandTitle: "Tech With Tim", 8 | }), 9 | }); 10 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { themes } from "@storybook/theming"; 2 | 3 | import "../src/index.scss"; 4 | 5 | export const parameters = { 6 | actions: { argTypesRegex: "^on[A-Z].*" }, 7 | controls: { 8 | matchers: { 9 | color: /(background|color)$/i, 10 | date: /Date$/, 11 | }, 12 | }, 13 | docs: { 14 | theme: themes.dark, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | plugins: 4 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 5 | spec: "@yarnpkg/plugin-interactive-tools" 6 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 7 | spec: "@yarnpkg/plugin-workspace-tools" 8 | - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs 9 | spec: "@yarnpkg/plugin-typescript" 10 | 11 | yarnPath: .yarn/releases/yarn-3.0.2.cjs 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Frontend 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## Table of contents 12 | 13 | - [GitHub](#github) 14 | - [Pull Requests](#pull-requests) 15 | - [MIT Software License](#mit-software-license) 16 | - [Report bugs](#report-bugs) 17 | - [Consistent Coding Style](#consistent-coding-style) 18 | 19 | ## GitHub 20 | 21 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 22 | 23 | ## Pull Requests 24 | 25 | [Pull requests](https://github.com/Tech-With-Tim/Frontend/pulls) (PRs) are the best way to propose changes to the codebase, so if you want to contribute, use PRs. 26 | 27 | 1. Fork the repo and create your branch from `master`. 28 | 2. If you've added code that should be tested, **add tests**. 29 | 3. Ensure the test suite passes. Test with `yarn test`. 30 | 4. Make sure your code lints. Lint with `yarn run lint`. 31 | 5. Issue that PR! 32 | 33 | ## MIT Software License 34 | 35 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 36 | 37 | **By contributing, you agree that your contributions will be licensed under its MIT License.** 38 | 39 | ## Report bugs 40 | 41 | We use GitHub [issues](https://github.com/Tech-With-Tim/Frontend/issues) to track public bugs. Report a bug by [opening a new issue](https://github.com/Tech-With-Tim/Frontend/issues/new), it's that easy! 42 | 43 | ### Write good bug reports 44 | 45 | **Great Bug Reports** tend to have: 46 | 47 | - A quick summary and/or background 48 | - Steps to reproduce 49 | - Be specific! 50 | - Give sample code if you can. 51 | - What you expected would happen 52 | - What actually happens 53 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 54 | 55 | People _love_ thorough bug reports. I'm not even kidding. 56 | 57 | ## Consistent Coding Style 58 | 59 | - 2 spaces for indentation rather than tabs 60 | - You can try running `yarn run lint` for style unification 61 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Install dependencies only when needed 2 | FROM node:alpine AS deps 3 | 4 | RUN apk add --no-cache libc6-compat 5 | WORKDIR /app 6 | COPY package.json yarn.lock .yarnrc.yml ./ 7 | COPY .yarn ./.yarn 8 | RUN yarn install 9 | 10 | # Rebuild the source code only when needed 11 | FROM node:alpine AS builder 12 | WORKDIR /app 13 | COPY . . 14 | COPY --from=deps /app/node_modules ./node_modules 15 | RUN yarn build 16 | 17 | # Production image, copy all the files and run next 18 | FROM node:alpine AS runner 19 | WORKDIR /app 20 | 21 | ENV NODE_ENV production 22 | 23 | COPY --from=builder /app/next.config.js ./ 24 | COPY --from=builder /app/public ./public 25 | COPY --from=builder /app/.next ./.next 26 | COPY --from=builder /app/node_modules ./node_modules 27 | 28 | RUN addgroup -g 1001 -S nodejs 29 | RUN adduser -S nextjs -u 1001 30 | RUN chown -R nextjs:nodejs /app/.next 31 | USER nextjs 32 | 33 | EXPOSE 3000 34 | 35 | RUN npx next telemetry disable 36 | 37 | CMD ["node_modules/.bin/next", "start"] 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-present Tech with Tim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Project logo 2 | 3 |

Tech With Tim website frontend

4 | 5 |
6 | 7 | [![GitHub Issues](https://img.shields.io/github/issues/Tech-With-Tim/Frontend.svg)](https://github.com/Tech-With-Tim/Frontend/issues) 8 | [![GitHub Pull Requests](https://img.shields.io/github/issues-pr/Tech-With-Tim/Frontend.svg)](https://github.com/Tech-With-Tim/Frontend/pulls) 9 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](/LICENSE) 10 | [![Discord](https://discord.com/api/guilds/501090983539245061/widget.png?style=shield)](https://discord.gg/twt) 11 | [![Test and deploy](https://github.com/Tech-With-Tim/Frontend/workflows/Test%20and%20deploy/badge.svg)](https://github.com/Tech-With-Tim/Frontend/actions?query=workflow%3A%22Test+and+deploy%22) 12 | [![codecov](https://codecov.io/gh/Tech-With-Tim/Frontend/branch/master/graph/badge.svg?token=2WULJRCY1W)](https://codecov.io/gh/Tech-With-Tim/Frontend) 13 | 14 |
15 |

Frontend for the Tech with Tim website using React.

16 | 17 | ## 📝 Table of Contents 18 | 19 | 20 | 21 | - [Getting Started](#getting_started) 22 | - [Built Using](#built_using) 23 | - [Contributing](/CONTRIBUTING.md) 24 | - [License](/LICENSE.md) 25 | 26 | 29 | 30 | ## 🏁 Getting Started 31 | 32 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See [deployment](#deployment) for notes on how to deploy the project on a live system. 33 | 34 | ### Prerequisites 35 | 36 | Install the required packages with Node Package Manager: 37 | 38 | ```sh 39 | yarn install 40 | ``` 41 | 42 | ### Running 43 | 44 | Run the app in the development mode: 45 | 46 | ```sh 47 | yarn start 48 | ``` 49 | 50 | Open [http://localhost:3000](http://localhost:3000) to view the website in the browser. 51 | 52 | The page will reload if you make edits. And you will also see any lint errors in the console. 53 | 54 | ## 🔧 Running the tests 55 | 56 | Run tests with: 57 | 58 | ```sh 59 | yarn test 60 | ``` 61 | 62 | **⚠ You need to write tests when contributing ⚠** 63 | 64 | ## ⛏️ Built Using 65 | 66 | - [Next.js](https://nextjs.org/) - Frontend Framework 67 | - [Zustand](https://zustand.surge.sh/) - State management 68 | 69 | You can find a list of contributors who participated in this project [here](https://github.com/Tech-With-Tim/Frontend/contributors). 70 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Run type-check on changes to TypeScript files 3 | "**/*.ts?(x)": () => "yarn run type-check", 4 | // Run ESLint on changes to JavaScript/TypeScript files 5 | "**/*.(ts|js)?(x)": () => "yarn run lint", 6 | }; 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | images: { 3 | domains: ["cdn.discordapp.com"] 4 | } 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0a", 4 | "dependencies": { 5 | "@babel/core": "^7.14.5", 6 | "@storybook/addons": "^6.2.7", 7 | "@storybook/theming": "^6.2.7", 8 | "@tailwindcss/jit": "^0.1.18", 9 | "autoprefixer": "^10.2.6", 10 | "axios": "^0.21.1", 11 | "classnames": "^2.2.6", 12 | "eslint": "^7.11.0", 13 | "husky": "^6.0.0", 14 | "lint-staged": "^10.5.4", 15 | "next": "11.1.1", 16 | "postcss": "^8.3.0", 17 | "react": "^17.0.2", 18 | "react-dom": "^17.0.2", 19 | "react-is": "^17.0.2", 20 | "react-query": "^3.13.6", 21 | "sass": "^1.34.1", 22 | "styled-components": "^5.2.1", 23 | "tailwindcss": "^2.1.4", 24 | "typescript": "^4.4.2", 25 | "zustand": "^3.4.1" 26 | }, 27 | "scripts": { 28 | "lint": "eslint -c .eslintrc.json .", 29 | "lint:fix": "eslint -c .eslintrc.json --fix .", 30 | "dev": "next dev", 31 | "build": "next build", 32 | "start": "next start", 33 | "test": "react-scripts test", 34 | "test:cov": "react-scripts test --coverage", 35 | "type-check": "tsc --project tsconfig.json --pretty --noEmit", 36 | "storybook": "start-storybook -p 6006 -s public", 37 | "build-storybook": "build-storybook -s public" 38 | }, 39 | "husky": { 40 | "hooks": { 41 | "pre-commit": "lint-staged" 42 | } 43 | }, 44 | "devDependencies": { 45 | "@babel/eslint-parser": "^7.15.4", 46 | "@storybook/addon-actions": "^6.2.7", 47 | "@storybook/addon-essentials": "^6.2.7", 48 | "@storybook/addon-links": "^6.2.7", 49 | "@storybook/node-logger": "^6.2.7", 50 | "@storybook/preset-create-react-app": "^3.1.7", 51 | "@storybook/react": "^6.2.7", 52 | "@tailwindcss/postcss7-compat": "^2.1.0", 53 | "@types/classnames": "^2.2.11", 54 | "@types/jest": "^26.0.18", 55 | "@types/node": "^15.12.2", 56 | "@types/react": "^17.0.11", 57 | "@types/react-dom": "^17.0.0", 58 | "@types/styled-components": "^5.1.7", 59 | "@typescript-eslint/eslint-plugin": "^4.5.0", 60 | "@typescript-eslint/parser": "^4.5.0", 61 | "eslint-config-prettier": "^8.2.0", 62 | "eslint-config-react-app": "^6.0.0", 63 | "eslint-plugin-flowtype": "^5.7.2", 64 | "eslint-plugin-import": "^2.22.1", 65 | "eslint-plugin-jsx-a11y": "^6.3.1", 66 | "eslint-plugin-prettier": "^3.4.0", 67 | "eslint-plugin-react": "^7.21.5", 68 | "eslint-plugin-react-hooks": "^4.2.0", 69 | "prettier": "^2.3.1" 70 | }, 71 | "jest": { 72 | "collectCoverageFrom": [ 73 | "src/**/*.{js,jsx,ts,tsx}" 74 | ], 75 | "testMatch": [ 76 | "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", 77 | "/src/**/*.{spec,test}.{js,jsx,ts,tsx}" 78 | ] 79 | }, 80 | "browserslist": { 81 | "production": [ 82 | ">0.2%", 83 | "not dead", 84 | "not op_mini all" 85 | ], 86 | "development": [ 87 | "last 1 chrome version", 88 | "last 1 firefox version", 89 | "last 1 safari version" 90 | ] 91 | }, 92 | "eslintConfig": { 93 | "overrides": [ 94 | { 95 | "files": [ 96 | "**/*.stories.*" 97 | ], 98 | "rules": { 99 | "import/no-anonymous-default-export": "off" 100 | } 101 | } 102 | ] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /public/images/home/challenges.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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/images/home/challengesSmall.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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /public/images/home/home1.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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /public/images/home/home1small.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 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/images/home/home2.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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /public/images/navbar/Drop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/images/navbar/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/timathon/history.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 | -------------------------------------------------------------------------------- /public/images/timathon/historyCard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/images/timathon/prizes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/images/timathon/prizesCard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/images/timathon/teamsCard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/images/timathon/timeline.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/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: Google 2 | Disallow: 3 | 4 | User-agent: * 5 | Disallow: / 6 | -------------------------------------------------------------------------------- /src/components/Badge.tsx: -------------------------------------------------------------------------------- 1 | import React, { ImgHTMLAttributes } from "react"; 2 | 3 | const badges = {}; 4 | 5 | type Props = ImgHTMLAttributes & { 6 | badge: keyof typeof badges; 7 | occurence: number; 8 | }; 9 | 10 | const Badge = ({ badge, occurence, ...props }: Props): JSX.Element => { 11 | return ( 12 |
13 | 14 | {occurence} 15 |
16 | ); 17 | }; 18 | 19 | export default Badge; 20 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React, { DetailedHTMLProps, ButtonHTMLAttributes } from "react"; 2 | 3 | import cb from "classnames"; 4 | import Spinner from "./Spinner"; 5 | 6 | const colorSchemas = { 7 | primary: "bg-secondary disabled:bg-primary-lighter hover:bg-primary-hover", 8 | secondary: "bg-white text-secondary", 9 | }; 10 | 11 | export type ButtonProps = DetailedHTMLProps< 12 | ButtonHTMLAttributes, 13 | HTMLButtonElement 14 | > & { 15 | loading?: boolean; 16 | icon?: React.ReactNode; 17 | color?: keyof typeof colorSchemas; 18 | }; 19 | 20 | export const Button: React.FC = ({ 21 | icon, 22 | loading, 23 | children, 24 | disabled, 25 | className, 26 | color = "primary", 27 | ...props 28 | }) => { 29 | return ( 30 | 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/Card.tsx: -------------------------------------------------------------------------------- 1 | import React, { HTMLAttributes } from "react"; 2 | import cb from "classnames"; 3 | 4 | const Card: React.FC> = ({ children, className, ...props }) => { 5 | return ( 6 |
7 | {children} 8 |
9 | ); 10 | }; 11 | 12 | export default Card; 13 | -------------------------------------------------------------------------------- /src/components/ChallengeHistory.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import cb from "classnames"; 3 | 4 | interface Props { 5 | title: string; 6 | passed: boolean; 7 | difficulty: 0 | 1 | 2; 8 | } 9 | 10 | const ChallengeHistory = (props: Props): JSX.Element => { 11 | return ( 12 |
13 | 19 | {props.title} - {props.passed ? "Passed" : "Failed"} 20 | 21 | 22 |
23 |
24 |
0 ? "bg-yellow-500" : "bg-light-gray" 28 | )} 29 | >
30 |
1 ? "bg-red-500" : "bg-light-gray" 34 | )} 35 | >
36 |
37 |
38 | ); 39 | }; 40 | 41 | export default ChallengeHistory; 42 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.component.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Overlay, Spinner } from "./Styles"; 3 | 4 | function Loading(): JSX.Element { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default Loading; 15 | -------------------------------------------------------------------------------- /src/components/Loading/Styles.ts: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from "styled-components"; 2 | 3 | export const Overlay = styled.div` 4 | position: fixed; 5 | display: block; 6 | width: 100%; 7 | height: 100%; 8 | top: 0; 9 | left: 0; 10 | right: 0; 11 | bottom: 0; 12 | background-color: rgba(0, 0, 0, 0.5); 13 | z-index: 998; 14 | `; 15 | 16 | export const spin = keyframes` 17 | from { 18 | transform: rotate(0deg); 19 | } 20 | to { 21 | transform: rotate(360deg); 22 | } 23 | `; 24 | 25 | export const Spinner = styled.div` 26 | border: 5px solid rgb(0, 0, 0, 0); 27 | border-top: 5px solid rgb(255, 255, 255, 0.7); 28 | border-radius: 50%; 29 | width: 60px; 30 | height: 60px; 31 | position: absolute; 32 | top: 0; 33 | bottom: 0; 34 | left: 0; 35 | right: 0; 36 | margin: auto; 37 | z-index: 999; 38 | 39 | animation: ${spin} 500ms linear infinite; 40 | `; 41 | -------------------------------------------------------------------------------- /src/components/Navbar/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | import { 4 | Nav, 5 | Close, 6 | NavDropdown, 7 | NavDropItems, 8 | NavItem, 9 | NavItems, 10 | UserImage, 11 | UserImageContainer, 12 | } from "./Styles"; 13 | import Image from "next/image"; 14 | 15 | import cb from "classnames"; 16 | 17 | import Link from "next/link"; 18 | import { NavbarIcon } from "../../icons"; 19 | import { useAuthStore } from "../../stores/useAuthStore"; 20 | import { getAvatarURI } from "../../helpers"; 21 | 22 | const Navbar = (): JSX.Element => { 23 | const [isOpen, setIsOpen] = useState(false); 24 | const [eventsOpen, setEventsOpen] = useState(false); 25 | 26 | const user = useAuthStore((s) => s.user); 27 | 28 | useEffect(() => { 29 | console.log(isOpen); 30 | }, [isOpen]); 31 | 32 | return ( 33 | 105 | ); 106 | }; 107 | 108 | export default Navbar; 109 | -------------------------------------------------------------------------------- /src/components/Navbar/Styles.tsx: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from "styled-components"; 2 | 3 | export const NavBrand = styled.img` 4 | width: 80px; 5 | height: 80px; 6 | `; 7 | 8 | export const NavAnimate = keyframes` 9 | from{ 10 | width: 0; 11 | right: 0; 12 | } 13 | 14 | to{ 15 | width: 100%; 16 | right: 100%; 17 | } 18 | `; 19 | 20 | export const NavItem = styled.li` 21 | margin-left: 3rem; 22 | font-size: 1.45rem; 23 | color: white; 24 | font-weight: 600; 25 | cursor: pointer; 26 | position: relative; 27 | 28 | ::after { 29 | content: ""; 30 | display: inline-block; 31 | position: absolute; 32 | bottom: -2px; 33 | left: 0; 34 | background-color: #fa9722; 35 | height: 2px; 36 | } 37 | 38 | :hover::after { 39 | animation: ${NavAnimate} 0.2s linear forwards; 40 | } 41 | `; 42 | 43 | export const NavDropdown = styled.div<{ isOpen: boolean }>` 44 | margin-left: 3rem; 45 | font-size: 1.45rem; 46 | color: white; 47 | font-weight: 600; 48 | align-items: center; 49 | display: inline-flex; 50 | cursor: pointer; 51 | 52 | display: flex; 53 | flex-direction: column; 54 | 55 | padding: 0 0 0 15px; 56 | 57 | position: relative; 58 | 59 | img { 60 | margin-left: 15px; 61 | transform: rotateZ(${(props) => (props.isOpen ? "-180deg" : "0")}); 62 | transition: transform 0.25s linear; 63 | } 64 | 65 | span { 66 | width: fit-content; 67 | position: relative; 68 | 69 | ::after { 70 | content: ""; 71 | display: inline-block; 72 | position: absolute; 73 | bottom: -2px; 74 | left: 0; 75 | background-color: #fa9722; 76 | height: 2px; 77 | 78 | animation: ${(props) => props.isOpen && NavAnimate} 0.2s linear forwards; 79 | } 80 | } 81 | 82 | @media screen and (max-width: 900px) { 83 | margin: 0 auto; 84 | padding: 0; 85 | text-align: center; 86 | 87 | img { 88 | display: none; 89 | } 90 | 91 | span::after { 92 | background-color: transparent; 93 | } 94 | } 95 | 96 | :hover { 97 | span::after { 98 | animation: ${NavAnimate} 0.2s linear forwards; 99 | } 100 | } 101 | 102 | > div { 103 | display: ${(props) => !props.isOpen && "none"}; 104 | } 105 | `; 106 | 107 | export const NavItems = styled.ul` 108 | list-style: none; 109 | display: flex; 110 | margin-left: 25px; 111 | 112 | @media screen and (max-width: 900px) { 113 | flex-direction: column; 114 | background-color: #12131c; 115 | 116 | margin-left: 0; 117 | 118 | position: fixed; 119 | left: -100%; 120 | top: 0; 121 | bottom: 0; 122 | 123 | transition: left 0.5s linear; 124 | 125 | min-width: 250px; 126 | width: fit-content; 127 | 128 | > * { 129 | margin-left: auto; 130 | margin-right: auto; 131 | margin-top: 3rem; 132 | width: 100%; 133 | text-align: center; 134 | } 135 | 136 | li::after { 137 | background-color: transparent; 138 | animation: none; 139 | } 140 | } 141 | `; 142 | 143 | export const ExpandHeightAnimation = keyframes` 144 | from{ 145 | max-height: 0; 146 | } 147 | 148 | to{ 149 | max-height: 500px; 150 | } 151 | `; 152 | 153 | export const NavDropItems = styled.div` 154 | position: absolute; 155 | top: 100%; 156 | left: 50%; 157 | text-align: center; 158 | background-color: #313440; 159 | 160 | border-top: 2px solid #fa9722; 161 | 162 | padding: 7px 15px; 163 | 164 | transform: translateX(-50%); 165 | overflow-y: hidden; 166 | 167 | cursor: initial; 168 | 169 | animation: ${ExpandHeightAnimation} 1.5s linear forwards; 170 | 171 | > * { 172 | cursor: pointer; 173 | 174 | p { 175 | border-top: 2px solid #393c49; 176 | } 177 | 178 | &:first-child { 179 | p { 180 | border: none; 181 | } 182 | } 183 | } 184 | 185 | @media screen and (max-width: 810px) { 186 | margin: 0; 187 | margin-top: 5px; 188 | width: 100%; 189 | /* border-top: none; */ 190 | 191 | position: initial; 192 | 193 | transform: translate(0); 194 | 195 | * { 196 | padding-top: 7px; 197 | } 198 | } 199 | 200 | *:hover { 201 | color: #fa9722; 202 | } 203 | `; 204 | 205 | export const UserImageContainer = styled.div` 206 | background: linear-gradient(#ff512f, #dd2476); 207 | border-radius: 50%; 208 | width: 70px; 209 | height: 70px; 210 | display: flex; 211 | align-items: center; 212 | flex-direction: row; 213 | justify-content: center; 214 | padding: 38px; 215 | cursor: pointer; 216 | `; 217 | 218 | export const UserImage = styled.img` 219 | border-radius: 50%; 220 | width: 70px; 221 | height: 70px; 222 | `; 223 | 224 | export const Close = styled.div` 225 | width: 35px; 226 | position: relative; 227 | top: 0; 228 | left: -75px; 229 | 230 | cursor: pointer; 231 | 232 | display: none; 233 | `; 234 | 235 | export const Nav = styled.nav` 236 | margin: 35px 50px; 237 | display: flex; 238 | align-items: center; 239 | justify-content: space-between; 240 | z-index: 998; 241 | 242 | div:first-child { 243 | display: flex; 244 | align-items: center; 245 | } 246 | 247 | @media screen and (max-width: 900px) { 248 | .user { 249 | position: fixed; 250 | bottom: 25px; 251 | left: -100%; 252 | 253 | margin-left: 25px; 254 | 255 | transition: left 0.5s linear; 256 | 257 | width: 200px; 258 | 259 | align-items: center; 260 | 261 | display: flex; 262 | flex-direction: row; 263 | 264 | h2 { 265 | margin-left: 15px; 266 | } 267 | } 268 | } 269 | 270 | .open-btn { 271 | cursor: pointer; 272 | position: absolute; 273 | right: 50px; 274 | } 275 | 276 | .open { 277 | left: 0; 278 | z-index: 10; 279 | } 280 | 281 | @media screen and (min-width: 900px) { 282 | .user { 283 | h2 { 284 | display: none; 285 | } 286 | } 287 | 288 | .open-btn { 289 | display: none; 290 | } 291 | } 292 | `; 293 | -------------------------------------------------------------------------------- /src/components/PageContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const PageContainer: React.FC = ({ children }) => { 4 | return ( 5 |
6 | {children} 7 |
8 | ); 9 | }; 10 | 11 | export default PageContainer; 12 | -------------------------------------------------------------------------------- /src/components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import cb from "classnames"; 3 | 4 | const sizes = { 5 | big: "h-8 w-8", 6 | small: "h-4 w-4", 7 | }; 8 | 9 | type Props = React.SVGAttributes & { 10 | size?: keyof typeof sizes; 11 | }; 12 | 13 | const Spinner = ({ size = "small", ...props }: Props): JSX.Element => { 14 | return ( 15 | 22 | 26 | 27 | ); 28 | }; 29 | 30 | export default Spinner; 31 | -------------------------------------------------------------------------------- /src/components/Title.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Title: React.FC = (props) => { 4 | return ( 5 |
6 | {props.children} 7 |
8 | ); 9 | }; 10 | 11 | export default Title; 12 | -------------------------------------------------------------------------------- /src/components/WithAuth.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useAuthStore } from "stores/useAuthStore"; 3 | 4 | const WithAuth: React.FC = ({ children }) => { 5 | const user = useAuthStore((s) => s.user); 6 | const hasToken = useAuthStore((s) => !!s.token); 7 | 8 | if (!hasToken) { 9 | return

no access

; 10 | } 11 | 12 | if (user === undefined) { 13 | return <>loading...; 14 | } 15 | 16 | if (user == null) { 17 | return

error fetching user

; 18 | } 19 | 20 | return <>{children}; 21 | }; 22 | 23 | export default WithAuth; 24 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { getOrigin } from "./helpers/getOrigin"; 2 | 3 | export const isServer = typeof window == "undefined"; 4 | export const __prod__ = process.env.NODE_ENV == "production"; 5 | export const REDIRECT = `${getOrigin()}/auth/discord/callback`; 6 | export const CLIENT_ID = process.env.NEXT_PUBLIC_CLIENT_ID || "771679369360834601"; 7 | export const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || "https://api.dev.twtcodejam.net"; 8 | -------------------------------------------------------------------------------- /src/helpers/discord.ts: -------------------------------------------------------------------------------- 1 | const discordCDN = "https://cdn.discordapp.com/"; 2 | 3 | interface avatarArgs { 4 | size?: number; 5 | animated?: boolean; 6 | } 7 | 8 | export const getAvatarURI = (id: string, avatar: string, args: avatarArgs = {}): string => { 9 | const formattedArgs: string[] = []; 10 | let ext = "png"; 11 | for (const arg in args) { 12 | if (arg === "animated") { 13 | if (avatar.startsWith("a_")) { 14 | ext = "gif"; 15 | } 16 | continue; 17 | } 18 | formattedArgs.push(`${arg}=${args[arg]}`); 19 | } 20 | return discordCDN + `avatars/${id}/${avatar}.${ext}?${formattedArgs.join("&")}`; 21 | }; 22 | -------------------------------------------------------------------------------- /src/helpers/getOrigin.ts: -------------------------------------------------------------------------------- 1 | export const getOrigin = (): string => { 2 | if (process.env.NODE_ENV == "development") return "http://localhost:3000"; 3 | 4 | return process.env.NEXT_PUBLIC_ORIGIN; 5 | }; 6 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./discord"; 2 | export * from "./getOrigin"; 3 | -------------------------------------------------------------------------------- /src/icons/Badge1.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Badge1 = (props: React.SVGAttributes): JSX.Element => { 4 | return ( 5 | 13 | 14 | 15 | 22 | 26 | 30 | 34 | 38 | 42 | 46 | 47 | 51 | 52 | ); 53 | }; 54 | 55 | export default Badge1; 56 | -------------------------------------------------------------------------------- /src/icons/Badge2.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Badge2 = (props: React.SVGAttributes): JSX.Element => { 4 | return ( 5 | 13 | 21 | 31 | 32 | 36 | 37 | 41 | 45 | 49 | 53 | 57 | 61 | 68 | 75 | 84 | 85 | 94 | 95 | 96 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | ); 110 | }; 111 | 112 | export default Badge2; 113 | -------------------------------------------------------------------------------- /src/icons/Badge3.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Badge3 = (props: React.SVGAttributes): JSX.Element => { 4 | return ( 5 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 26 | 27 | 31 | 35 | 36 | ); 37 | }; 38 | 39 | export default Badge3; 40 | -------------------------------------------------------------------------------- /src/icons/Discord.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Discord = (props: React.SVGAttributes): JSX.Element => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default Discord; 12 | -------------------------------------------------------------------------------- /src/icons/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Navbar = (props: React.SVGAttributes): JSX.Element => { 4 | return ( 5 | 13 | 18 | 23 | 28 | 29 | ); 30 | }; 31 | 32 | export default Navbar; 33 | -------------------------------------------------------------------------------- /src/icons/Youtube.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Youtube = (props: React.SVGAttributes & { playfill?: string }): JSX.Element => { 4 | return ( 5 | 6 | 7 | 11 | 12 | ); 13 | }; 14 | 15 | export default Youtube; 16 | -------------------------------------------------------------------------------- /src/icons/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as DiscordIcon } from "./Discord"; 2 | export { default as YoutubeIcon } from "./Youtube"; 3 | export { default as NavbarIcon } from "./Navbar"; 4 | -------------------------------------------------------------------------------- /src/modules/profile/ChallengeHistoryController.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ChallengeHistory from "../../components/ChallengeHistory"; 3 | 4 | const ChallengeHistoryController = (props: { className: string }): JSX.Element => { 5 | const results: { title: string; passed: boolean; difficulty: 0 | 1 | 2 }[] = [ 6 | { 7 | title: "Challenge 1", 8 | passed: true, 9 | difficulty: 0, 10 | }, 11 | { 12 | title: "Challenge 2", 13 | passed: true, 14 | difficulty: 1, 15 | }, 16 | { 17 | title: "Challenge 3", 18 | passed: true, 19 | difficulty: 2, 20 | }, 21 | { 22 | title: "Challenge 4", 23 | passed: false, 24 | difficulty: 0, 25 | }, 26 | ]; 27 | 28 | return ( 29 |
30 | {results.map((result, key) => ( 31 | 32 | ))} 33 |
34 | ); 35 | }; 36 | 37 | export default ChallengeHistoryController; 38 | -------------------------------------------------------------------------------- /src/modules/profile/Profile.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Title from "../../components/Title"; 3 | import { getAvatarURI } from "../../helpers"; 4 | import { useAuthStore } from "../../stores/useAuthStore"; 5 | import ChallengeHistoryController from "./ChallengeHistoryController"; 6 | 7 | const ProfilePage = (): JSX.Element => { 8 | const user = useAuthStore((s) => s.user!); 9 | 10 | return ( 11 | <> 12 | Profile 13 |
14 |
15 |
16 |
17 |

Challenge History

18 | 19 |
20 |
21 |
22 | 27 |
28 |
29 |

{user.username}

30 |
#{user.discriminator}
31 |
32 |
33 |
34 | Badges. Comming Soon 35 |
36 |
37 |
38 |
39 | 40 | ); 41 | }; 42 | 43 | export default ProfilePage; 44 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { AppProps } from "next/app"; 3 | import { QueryClient, QueryClientProvider } from "react-query"; 4 | import { ReactQueryDevtools } from "react-query/devtools"; 5 | import axios from "axios"; 6 | 7 | import Navbar from "components/Navbar/Navbar"; 8 | import PageContainer from "components/PageContainer"; 9 | import { BACKEND_URL } from "../constants"; 10 | import { useAuthStore } from "stores/useAuthStore"; 11 | 12 | import "styles/tailwind.css"; 13 | import "styles/index.scss"; 14 | 15 | axios.defaults.baseURL = BACKEND_URL; 16 | 17 | const queryClient = new QueryClient({ 18 | defaultOptions: { 19 | queries: { 20 | retry: false, // no need to spam the api... 21 | }, 22 | }, 23 | }); 24 | 25 | const App = ({ pageProps, Component }: AppProps): JSX.Element => { 26 | const user = useAuthStore((s) => s.user); 27 | const hasToken = useAuthStore((s) => !!s.token); 28 | const fetchUser = useAuthStore((s) => s.fetchUser); 29 | 30 | useEffect(() => { 31 | if (hasToken) { 32 | if (!user) { 33 | fetchUser(); 34 | } 35 | } 36 | }, [hasToken, user, fetchUser]); 37 | 38 | return ( 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ServerStyleSheet } from "styled-components"; 3 | import Document, { DocumentContext, DocumentInitialProps } from "next/document"; 4 | 5 | // Styled components have trouble rendering all styles in Nextjs 6 | // Solution: https://dev.to/rsanchezp/next-js-and-styled-components-style-loading-issue-3i68 7 | export default class MyDocument extends Document { 8 | static async getInitialProps(ctx: DocumentContext): Promise { 9 | const sheet = new ServerStyleSheet(); 10 | const originalRenderPage = ctx.renderPage; 11 | 12 | try { 13 | ctx.renderPage = () => 14 | originalRenderPage({ 15 | enhanceApp: (App) => (props) => sheet.collectStyles(), 16 | }); 17 | 18 | const initialProps = await Document.getInitialProps(ctx); 19 | return { 20 | ...initialProps, 21 | styles: ( 22 | <> 23 | {initialProps.styles} 24 | {sheet.getStyleElement()} 25 | 26 | ), 27 | }; 28 | } finally { 29 | sheet.seal(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/pages/auth/discord/callback.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import axios from "axios"; 3 | import Loading from "components/Loading/Loading.component"; 4 | import { REDIRECT } from "../../../constants"; 5 | import { useRouter } from "next/router"; 6 | import { useQuery } from "react-query"; 7 | import { useAuthStore } from "stores/useAuthStore"; 8 | 9 | const DiscordCallbackPage = (): JSX.Element => { 10 | const router = useRouter(); 11 | const setToken = useAuthStore((s) => s.setToken); 12 | 13 | const { code } = router.query; 14 | 15 | useEffect(() => { 16 | if (!code) { 17 | router.replace("/"); 18 | } 19 | }, [code, router]); 20 | 21 | const { isError } = useQuery<{ token: string }>( 22 | "login callback", 23 | async () => { 24 | return await axios 25 | .post<{ token: string }>( 26 | "/auth/discord/callback", 27 | { 28 | callback: REDIRECT, 29 | code: router.query.code, 30 | }, 31 | { 32 | headers: { 33 | "Content-Type": "application/json", 34 | }, 35 | } 36 | ) 37 | .then((res) => res.data); 38 | }, 39 | { 40 | onSuccess: ({ token }) => { 41 | setToken(token); 42 | router.push("/"); 43 | }, 44 | } 45 | ); 46 | 47 | if (isError) { 48 | return

Something unexpected happened

; 49 | } 50 | 51 | return ; 52 | }; 53 | 54 | export default DiscordCallbackPage; 55 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import { Button } from "../components/Button"; 4 | import Card from "../components/Card"; 5 | import { DiscordIcon, YoutubeIcon } from "../icons"; 6 | 7 | export default function HomePage(): JSX.Element { 8 | return ( 9 | <> 10 |
11 |
12 |

Welcome to

13 |

Tech With Tim

14 |
Programming & Tech Tutorials
15 | 29 |
30 |
31 | 32 | {/* */} 33 | 34 | 35 |
36 |
37 |
38 |
39 |
40 |

Timathon

41 |

Code Jam

42 |
43 |

44 | Hosted every 2 months, consisting of a different theme and allowing yourself to put your 45 | skills to the test. 46 |

47 | 48 |
49 |
50 | 51 |

07

52 |
Code Jams
53 |
54 | 55 |

617

56 |
Teams Created
57 |
58 | 59 |
60 |

212

61 |
Submissions
62 |
63 |
64 |
65 |
66 |
67 |
68 |

Weekly

69 |

Challenges

70 |

71 | Submit your solutions and earn badges! 72 |

73 | 74 |
75 |
76 | 77 | {/* */} 82 | 83 | 84 |
85 |
86 |
87 |
88 |

Discord

89 |

Commmunity

90 |

91 | Become a part of one of the most active programming communities. Check out all of our 92 | help channels! 93 |

94 | 95 | 96 | 97 |
98 |
99 | 100 | 101 | 102 |
103 |
104 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /src/pages/login.tsx: -------------------------------------------------------------------------------- 1 | import { REDIRECT, CLIENT_ID } from "../constants"; 2 | import { useRouter } from "next/dist/client/router"; 3 | import React, { useEffect } from "react"; 4 | import { useAuthStore } from "stores/useAuthStore"; 5 | 6 | const SCOPES = ["identify"]; 7 | 8 | function format_scope(scopes) { 9 | return scopes.join(" "); 10 | } 11 | 12 | const discordURL = 13 | "https://discord.com/api/oauth2/authorize?response_type=code" + 14 | `&client_id=${CLIENT_ID}&scope=${format_scope(SCOPES)}` + 15 | `&redirect_uri=${encodeURIComponent(REDIRECT)}` + 16 | "&prompt=consent"; 17 | 18 | const LoginPage = (): JSX.Element => { 19 | const router = useRouter(); 20 | 21 | useEffect(() => { 22 | if (useAuthStore.getState().token) { 23 | router.push("/"); 24 | } 25 | }, [router]); 26 | 27 | return ( 28 |
29 |

Click the button to login using Discord

30 | 31 | Login 32 |
33 | ); 34 | }; 35 | 36 | export default LoginPage; 37 | -------------------------------------------------------------------------------- /src/pages/profile.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { NextPage } from "next"; 3 | import WithAuth from "../components/WithAuth"; 4 | import ProfilePage from "../modules/profile/Profile"; 5 | 6 | const Profile: NextPage = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default Profile; 15 | -------------------------------------------------------------------------------- /src/pages/timathon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | 4 | import { Button } from "components/Button"; 5 | import Card from "components/Card"; 6 | 7 | export default function TimathonPage(): JSX.Element { 8 | return ( 9 | <> 10 |
11 |
12 |
timathon
13 |
code jam
14 |
every 2 months
15 |
16 | 17 | 18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 |
27 | 28 |
29 |
Prizes
30 |
31 | 32 | 33 | 34 |
35 |
36 |
You're in for a chance to win real
37 |
cash prizes
38 |
39 | 40 |
41 |
Teams
42 |
43 | 44 | {/* */} 45 | 46 | 47 |
48 |
49 |
You're in for a chance to win real
50 |
cash prizes
51 |
52 | 53 |
54 |
History
55 |
56 | 57 | {/* */} 58 | 59 | 60 |
61 |
62 |
You're in for a chance to win real
63 |
cash prizes
64 |
65 |
66 |
67 |
68 | 69 | {/* prizes */} 70 | 71 | 72 |
73 |
74 |
Prizes
75 |
Will be split amongst
76 |
the team members
77 | 78 |
*CAD
79 |
80 |
81 |
82 |
83 |
teams and timeline
84 |
85 |
86 |
87 |
88 |
week 1
89 |
week 2 & 3
90 |
week 4
91 |
week 5 & 6
92 |
week 7
93 |
94 | 95 |
96 | 97 |
98 |
create a team and start working on your project
99 |
work on your project
100 |
finish your project and submit it
101 |
vote on your favourite submissions
102 |
winners are announced
103 |
104 |
105 | 106 |
107 | 108 |
109 | 110 | {/* timeline */} 111 | 112 | 113 |
114 |
115 |
116 |
117 |
118 |
119 |
history
120 |
121 | 122 | {/* history */} 123 | 124 | 125 |
126 |
127 |
128 |
129 |
virtual assistant
130 |
current jam
131 |
132 |
for students
133 |
generate
134 |
135 |
136 | 137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /src/stores/useAuthStore.tsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import create from "zustand"; 3 | import { devtools } from "zustand/middleware"; 4 | import { User } from "../types/models.types"; 5 | 6 | export type AuthStore = { 7 | token: string | null; 8 | fetchUser: () => void; 9 | user: User | null | undefined; 10 | setToken: (token: string) => void; 11 | }; 12 | 13 | export const useAuthStore = create( 14 | devtools((set, get) => ({ 15 | user: undefined, 16 | token: typeof window != "undefined" && localStorage.getItem("token"), 17 | setToken: (token: string) => { 18 | localStorage.setItem("token", token); 19 | 20 | set({ token }); 21 | }, 22 | fetchUser: () => { 23 | axios 24 | .get("/users/@me", { 25 | headers: { Authorization: get().token }, 26 | }) 27 | .then((res) => set({ user: res.data })) 28 | .catch(() => { 29 | localStorage.removeItem("token"); 30 | set({ user: null, token: null }); 31 | }); 32 | }, 33 | })) 34 | ); 35 | -------------------------------------------------------------------------------- /src/stories/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { toStr } from "./utils/toStr"; 3 | import { toEnum } from "./utils/toEnum"; 4 | import { Story } from "@storybook/react"; 5 | import { toBoolean } from "./utils/toBool"; 6 | import { Button as ButtonComponent } from "../components/Button"; 7 | 8 | export default { 9 | title: "Button", 10 | argTypes: { 11 | children: toStr(), 12 | loading: toBoolean(), 13 | disabled: toBoolean(), 14 | color: toEnum("primary", "secondary"), 15 | }, 16 | args: { 17 | color: "primary", 18 | children: "Button", 19 | }, 20 | decorators: [ 21 | (Story: Story): JSX.Element => ( 22 |
23 | 24 |
25 | ), 26 | ], 27 | }; 28 | 29 | export const Button = ButtonComponent.bind({}) as Story; 30 | -------------------------------------------------------------------------------- /src/stories/Introduction.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/addon-docs/blocks"; 2 | 3 | 10 | 11 | 12 | 13 | Project logo 18 | 19 | # Tech With Tim 20 | 21 | [![GitHub Issues](https://img.shields.io/github/issues/Tech-With-Tim/Frontend.svg)](https://github.com/Tech-With-Tim/Frontend/issues) 22 | [![GitHub Pull Requests](https://img.shields.io/github/issues-pr/Tech-With-Tim/Frontend.svg)](https://github.com/Tech-With-Tim/Frontend/pulls) 23 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](/LICENSE) 24 | [![Discord](https://discord.com/api/guilds/501090983539245061/widget.png?style=shield)](https://discord.gg/twt) 25 | [![Test and deploy](https://github.com/Tech-With-Tim/Frontend/workflows/Test%20and%20deploy/badge.svg)](https://github.com/Tech-With-Tim/Frontend/actions?query=workflow%3A%22Test+and+deploy%22) 26 | [![codecov](https://codecov.io/gh/Tech-With-Tim/Frontend/branch/master/graph/badge.svg?token=2WULJRCY1W)](https://codecov.io/gh/Tech-With-Tim/Frontend) 27 | 28 |

Frontend for the Tech with Tim website using React.

29 | -------------------------------------------------------------------------------- /src/stories/utils/toBool.ts: -------------------------------------------------------------------------------- 1 | import { ArgType } from "@storybook/addons"; 2 | 3 | export const toBoolean = (): ArgType => ({ 4 | control: { 5 | type: "boolean", 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /src/stories/utils/toEnum.ts: -------------------------------------------------------------------------------- 1 | import { ArgType } from "@storybook/addons"; 2 | 3 | export const toEnum = (...arr: T[]): ArgType => ({ 4 | control: { 5 | type: "inline-radio", 6 | options: arr, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /src/stories/utils/toStr.ts: -------------------------------------------------------------------------------- 1 | import { ArgType } from "@storybook/addons"; 2 | 3 | export const toStr = (): ArgType => ({ 4 | control: { 5 | type: "text", 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | html, 10 | body { 11 | height: 100%; 12 | width: 100%; 13 | } 14 | 15 | #root { 16 | display: flex; 17 | flex-direction: column; 18 | 19 | overflow: auto; 20 | min-height: 100%; 21 | } 22 | 23 | body { 24 | font-family: "Poppins", sans-serif; 25 | background-color: #21232c; 26 | color: white; 27 | } 28 | 29 | a { 30 | text-decoration: none; 31 | color: inherit; 32 | } 33 | 34 | input, 35 | button { 36 | outline: none; 37 | 38 | &:focus { 39 | outline: none; 40 | } 41 | } 42 | 43 | ::-webkit-scrollbar { 44 | width: 15px; 45 | height: 16px; 46 | } 47 | 48 | ::-webkit-scrollbar-track { 49 | background: #21232c; 50 | } 51 | 52 | ::-webkit-scrollbar-thumb { 53 | background-color: rgba(0, 0, 0, 0.2); 54 | -webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, 0.1), inset 0 -1px 0 rgba(0, 0, 0, 0.07); 55 | border-radius: 25px; 56 | } 57 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | /* ./styles/globals.css */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; -------------------------------------------------------------------------------- /src/types/models.types.ts: -------------------------------------------------------------------------------- 1 | export type UserType = "USER" | "APP"; 2 | 3 | export interface User { 4 | id: string; 5 | type: UserType; 6 | avatar: string; 7 | username: string; 8 | discriminator: string; 9 | } 10 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ["./src/**/*.{js,jsx,ts,tsx}"], 3 | darkMode: false, 4 | theme: { 5 | fontFamily: { 6 | body: ['"Poppins"', "sans-serif"], 7 | }, 8 | container: { 9 | center: true, 10 | padding: { 11 | DEFAULT: "3rem", 12 | sm: "1.25rem", 13 | md: "1.5rem", 14 | lg: "2rem", 15 | xl: "2.5rem", 16 | "2xl": "3rem", 17 | }, 18 | }, 19 | fontSize: { 20 | tiny: "0.625rem", 21 | xs: ".75rem", 22 | sm: ".875rem", 23 | base: "1rem", 24 | lg: "1.125rem", 25 | xl: "1.25rem", 26 | "2xl": "1.5rem", 27 | "3xl": "1.875rem", 28 | "4xl": "2.25rem", 29 | "5xl": "3rem", 30 | "6xl": "4rem", 31 | "7xl": "5rem", 32 | }, 33 | extend: { 34 | outline: { 35 | "no-chrome": "none", 36 | }, 37 | spacing: { 96: "32rem" }, 38 | colors: { 39 | primary: { 40 | hover: "#C7441A", 41 | lighter: "#EE6F45", 42 | light: "#F9A826", 43 | DEFAULT: "#FA9722", 44 | dark: "#F39200", 45 | }, 46 | secondary: { 47 | DEFAULT: "#E94E1B", 48 | }, 49 | accent: { 50 | DEFAULT: "#21232C", 51 | dark: "#161821", 52 | }, 53 | "blue-gray": { 54 | light: "#393C49", 55 | DEFAULT: "#313440", 56 | dark: "#21232C", 57 | }, 58 | "light-gray": "#BBBBBB", 59 | }, 60 | screens: { 61 | mdlg: { 62 | min: "768px", 63 | max: "1023px", 64 | }, 65 | }, 66 | }, 67 | }, 68 | variants: { 69 | backgroundColor: ({ after }) => after(["disabled"]), 70 | textColor: ({ after }) => after(["disabled"]), 71 | scrollbar: ["rounded", "dark"], 72 | extend: { 73 | borderWidth: ["last"], 74 | }, 75 | }, 76 | plugins: [], 77 | }; 78 | -------------------------------------------------------------------------------- /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 | "baseUrl": "src", 21 | "allowSyntheticDefaultImports": true, 22 | "noFallthroughCasesInSwitch": true 23 | }, 24 | "include": [ 25 | "next-env.d.ts", 26 | "**/*.ts", 27 | "**/*.tsx" 28 | ], 29 | "exclude": [ 30 | "node_modules" 31 | ] 32 | } 33 | --------------------------------------------------------------------------------