├── .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 |
2 |
3 |
Tech With Tim website frontend
4 |
5 |
6 |
7 | [](https://github.com/Tech-With-Tim/Frontend/issues)
8 | [](https://github.com/Tech-With-Tim/Frontend/pulls)
9 | [](/LICENSE)
10 | [](https://discord.gg/twt)
11 | [](https://github.com/Tech-With-Tim/Frontend/actions?query=workflow%3A%22Test+and+deploy%22)
12 | [](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 |
41 |
--------------------------------------------------------------------------------
/public/images/home/challengesSmall.svg:
--------------------------------------------------------------------------------
1 |
45 |
--------------------------------------------------------------------------------
/public/images/home/home1.svg:
--------------------------------------------------------------------------------
1 |
94 |
--------------------------------------------------------------------------------
/public/images/home/home1small.svg:
--------------------------------------------------------------------------------
1 |
31 |
--------------------------------------------------------------------------------
/public/images/home/home2.svg:
--------------------------------------------------------------------------------
1 |
45 |
--------------------------------------------------------------------------------
/public/images/navbar/Drop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/navbar/close.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/timathon/history.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/timathon/historyCard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/timathon/prizes.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/timathon/prizesCard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/timathon/teamsCard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/timathon/timeline.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
25 |
26 |
27 |
28 |
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 | {/*
*/}
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 | {/*
*/}
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
history
120 |
121 |
122 | {/*
*/}
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 |
18 |
19 | # Tech With Tim
20 |
21 | [](https://github.com/Tech-With-Tim/Frontend/issues)
22 | [](https://github.com/Tech-With-Tim/Frontend/pulls)
23 | [](/LICENSE)
24 | [](https://discord.gg/twt)
25 | [](https://github.com/Tech-With-Tim/Frontend/actions?query=workflow%3A%22Test+and+deploy%22)
26 | [](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 |
--------------------------------------------------------------------------------