diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5d40893f..d61fd7bf 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,8 +6,8 @@ To set up the project GitHub-Stats-Extended locally, run the following commands: ```bash ./vercel-preparation.sh -(cd ./backend/ && npm install) -(cd ./frontend/frontend/ && yarn install && yarn build-trends) +pnpm install +pnpm --filter frontend run build-trends ``` The easiest way to run and test the project is to deploy it to Vercel as described in the [deployment guide](../docs/deploy.md). @@ -40,9 +40,9 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue] > **Ans:** Please read all the related issues/comments before opening any issues regarding language card stats: > -> - +> - > -> - +> - **Q:** How to count private stats? @@ -52,6 +52,6 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue] **Great Feature Requests** tend to have: -- A quick idea summary -- What & why do you want to add the specific feature -- Additional context like images, links to resources to implement the feature, etc. +- A quick idea summary +- What & why do you want to add the specific feature +- Additional context like images, links to resources to implement the feature, etc. diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml new file mode 100644 index 00000000..901d2f63 --- /dev/null +++ b/.github/actions/install-dependencies/action.yml @@ -0,0 +1,37 @@ +name: Setup Node.js + PNPM and install Dependencies +description: | + This is a composite GitHub Action that: + - Sets up pnpm package manager + - Configures Node.js environment + - Installs project dependencies with caching + +inputs: + node-version: + description: "Explicit node version. Otherwise fallback reading `.nvmrc`" + +runs: + using: composite + + steps: + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js (via input) + if: ${{ inputs.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ inputs.node-version }} + cache: "pnpm" + registry-url: "https://registry.npmjs.org" + + - name: Setup Node.js (via .nvmrc) + if: ${{ !inputs.node-version }} + uses: actions/setup-node@v6 + with: + node-version-file: ".nvmrc" + cache: "pnpm" + registry-url: "https://registry.npmjs.org" + + - name: Install Dependencies + shell: bash + run: pnpm install --frozen-lockfile diff --git a/.github/workflows/basic-build.yml b/.github/workflows/basic-build.yml deleted file mode 100644 index 29e0bcd8..00000000 --- a/.github/workflows/basic-build.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Basic Build - -on: - workflow_dispatch: # Allows you to run this manually from the Actions tab - push: - pull_request: - -jobs: - execute: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} - - steps: - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: latest - - - name: Install Yarn - run: npm install -g yarn - - - name: Checkout code - uses: actions/checkout@v6 - - - name: Install and Build - run: | - chmod +x ./vercel-preparation.sh - ./vercel-preparation.sh - (cd ./backend/ && npm install) - (cd ./frontend/frontend/ && yarn install && yarn build-trends) - - - name: Run Backend Tests - run: | - (cd ./backend/ && npm test) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..3e091116 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: + - master + pull_request: + branches: + - master + - monorepo # to be removed once we complete monorepo tasks + +permissions: {} + +jobs: + build-and-test: + name: Build and test ${{ matrix.node }} on ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + node: [24] + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install Dependencies + uses: ./.github/actions/install-dependencies + with: + node-version: ${{ matrix.node }} + + - name: Run vercel-preparation.sh + run: | + chmod +x ./vercel-preparation.sh + ./vercel-preparation.sh + + - name: Build frontend + run: pnpm --filter frontend run build-trends + + code-checks: + name: Code checks + + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install Dependencies + uses: ./.github/actions/install-dependencies + + # needed to resolve .vercel folder from apps/frontend/src/components/Card/SVG.js + - name: Run vercel-preparation.sh + run: | + chmod +x ./vercel-preparation.sh + ./vercel-preparation.sh + + - name: Format + run: pnpm run format:check + + - name: Lint + run: pnpm run lint + + - name: Lint (knip) + run: pnpm run lint:knip diff --git a/.gitignore b/.gitignore index 37497ffa..5ae9ed25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,33 @@ -backend/.vercel -backend/.env -backend/node_modules -backend/*.lock -backend/coverage -backend/benchmarks -backend/vercel_token -backend-copy -frontend/frontend/.env -frontend/frontend/src/backend +node_modules + +.env.local +.env.development.local +.env.test.local +.env.production.local + +# OS +.DS_Store + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Project +coverage + +apps/backend/.vercel +apps/backend/.env +apps/backend/node_modules +apps/backend/*.lock +apps/backend/coverage +apps/backend/benchmarks +apps/backend/vercel_token +apps/backend-copy + +apps/frontend/.env +apps/frontend/src/backend +apps/frontend/build # IDE .idea/ diff --git a/backend/.husky/.gitignore b/.husky/.gitignore similarity index 100% rename from backend/.husky/.gitignore rename to .husky/.gitignore diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..b4c15f46 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +pnpm lint-staged +pnpm run lint +# TODO enable +# npm test diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..18c92ea9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v24 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..2acdeb17 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +pnpm-lock.yaml + +# https://github.com/stats-organization/github-stats-extended/pull/26#discussion_r2709033732 +# Avoid prettier format on md files to simplify merge on upstream repo +*.md \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..f28cd600 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "useTabs": false, + "semi": true, + "trailingComma": "all", + "singleQuote": false, + "printWidth": 80, + "tabWidth": 2, + "overrides": [ + { + "files": ["*.jsonc"], + "options": { + "trailingComma": "none" + } + } + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..b01e17dd --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "yzhang.markdown-all-in-one", + "prettier.prettier-vscode", + "dbaeumer.vscode-eslint", + "github.vscode-github-actions" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..f8c22a76 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "markdown.extension.toc.levels": "1..3", + "editor.formatOnSave": true, + "editor.defaultFormatter": "prettier.prettier-vscode", + "[javascript]": { + "editor.tabSize": 2 + } +} diff --git a/apps/backend/.devcontainer/devcontainer.json b/apps/backend/.devcontainer/devcontainer.json new file mode 100644 index 00000000..cbeb9162 --- /dev/null +++ b/apps/backend/.devcontainer/devcontainer.json @@ -0,0 +1,33 @@ +{ + "name": "GitHub Readme Stats Dev", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "22" } + }, + "forwardPorts": [3000], + "portsAttributes": { + "3000": { "label": "HTTP" } + }, + "appPort": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "npm install -g vercel", + + // Use 'postStartCommand' to run commands after the container is started. + "postStartCommand": "hostname dev && npm install", + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "yzhang.markdown-all-in-one", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "github.vscode-github-actions" + ] + } + }, + + "remoteUser": "root", + "privileged": true +} diff --git a/backend/.github/workflows/codeql-analysis.yml b/apps/backend/.github/workflows/codeql-analysis.yml similarity index 100% rename from backend/.github/workflows/codeql-analysis.yml rename to apps/backend/.github/workflows/codeql-analysis.yml diff --git a/backend/.github/workflows/deploy-prep.py b/apps/backend/.github/workflows/deploy-prep.py similarity index 100% rename from backend/.github/workflows/deploy-prep.py rename to apps/backend/.github/workflows/deploy-prep.py diff --git a/backend/.github/workflows/deploy-prep.yml b/apps/backend/.github/workflows/deploy-prep.yml similarity index 100% rename from backend/.github/workflows/deploy-prep.yml rename to apps/backend/.github/workflows/deploy-prep.yml diff --git a/backend/.github/workflows/e2e-test.yml b/apps/backend/.github/workflows/e2e-test.yml similarity index 100% rename from backend/.github/workflows/e2e-test.yml rename to apps/backend/.github/workflows/e2e-test.yml diff --git a/backend/.github/workflows/empty-issues-closer.yml b/apps/backend/.github/workflows/empty-issues-closer.yml similarity index 85% rename from backend/.github/workflows/empty-issues-closer.yml rename to apps/backend/.github/workflows/empty-issues-closer.yml index e1437a9e..3de09d82 100644 --- a/backend/.github/workflows/empty-issues-closer.yml +++ b/apps/backend/.github/workflows/empty-issues-closer.yml @@ -37,11 +37,9 @@ jobs: close_comment: Closing this issue because it appears to be empty. Please update the issue for it to be reopened. - open_comment: - Reopening this issue because the author provided more information. + open_comment: Reopening this issue because the author provided more information. check_templates: true template_close_comment: Closing this issue since the issue template was not filled in. Please provide us with more information to have this issue reopened. - template_open_comment: - Reopening this issue because the author provided more information. + template_open_comment: Reopening this issue because the author provided more information. diff --git a/backend/.github/workflows/generate-theme-doc.yml b/apps/backend/.github/workflows/generate-theme-doc.yml similarity index 99% rename from backend/.github/workflows/generate-theme-doc.yml rename to apps/backend/.github/workflows/generate-theme-doc.yml index c258889e..baef8965 100644 --- a/backend/.github/workflows/generate-theme-doc.yml +++ b/apps/backend/.github/workflows/generate-theme-doc.yml @@ -3,7 +3,6 @@ on: push: branches: - master - - next paths: - "themes/index.js" workflow_dispatch: diff --git a/backend/.github/workflows/label-pr.yml b/apps/backend/.github/workflows/label-pr.yml similarity index 100% rename from backend/.github/workflows/label-pr.yml rename to apps/backend/.github/workflows/label-pr.yml diff --git a/backend/.github/workflows/ossf-analysis.yml b/apps/backend/.github/workflows/ossf-analysis.yml similarity index 100% rename from backend/.github/workflows/ossf-analysis.yml rename to apps/backend/.github/workflows/ossf-analysis.yml diff --git a/backend/.github/workflows/preview-theme.yml b/apps/backend/.github/workflows/preview-theme.yml similarity index 100% rename from backend/.github/workflows/preview-theme.yml rename to apps/backend/.github/workflows/preview-theme.yml diff --git a/backend/.github/workflows/prs-cache-clean.yml b/apps/backend/.github/workflows/prs-cache-clean.yml similarity index 100% rename from backend/.github/workflows/prs-cache-clean.yml rename to apps/backend/.github/workflows/prs-cache-clean.yml diff --git a/backend/.github/workflows/stale-theme-pr-closer.yml b/apps/backend/.github/workflows/stale-theme-pr-closer.yml similarity index 100% rename from backend/.github/workflows/stale-theme-pr-closer.yml rename to apps/backend/.github/workflows/stale-theme-pr-closer.yml diff --git a/backend/.github/workflows/test.yml b/apps/backend/.github/workflows/test.yml similarity index 97% rename from backend/.github/workflows/test.yml rename to apps/backend/.github/workflows/test.yml index bd29d4c1..390aefb4 100644 --- a/backend/.github/workflows/test.yml +++ b/apps/backend/.github/workflows/test.yml @@ -4,11 +4,9 @@ on: push: branches: - master - - next pull_request: branches: - master - - next permissions: read-all diff --git a/backend/.github/workflows/theme-prs-closer.yml b/apps/backend/.github/workflows/theme-prs-closer.yml similarity index 100% rename from backend/.github/workflows/theme-prs-closer.yml rename to apps/backend/.github/workflows/theme-prs-closer.yml diff --git a/backend/.github/workflows/top-issues-dashboard.yml b/apps/backend/.github/workflows/top-issues-dashboard.yml similarity index 100% rename from backend/.github/workflows/top-issues-dashboard.yml rename to apps/backend/.github/workflows/top-issues-dashboard.yml diff --git a/backend/.github/workflows/update-langs.yml b/apps/backend/.github/workflows/update-langs.yml similarity index 95% rename from backend/.github/workflows/update-langs.yml rename to apps/backend/.github/workflows/update-langs.yml index 7e523989..262f7563 100644 --- a/backend/.github/workflows/update-langs.yml +++ b/apps/backend/.github/workflows/update-langs.yml @@ -29,8 +29,7 @@ permissions: jobs: updateLanguages: - if: - github.repository == 'anuraghazra/github-readme-stats' || + if: github.repository == 'anuraghazra/github-readme-stats' || github.repository == 'stats-organization/github-readme-stats' name: Update supported languages runs-on: ubuntu-latest @@ -62,8 +61,7 @@ jobs: branch: "update_langs/patch" delete-branch: true title: Update languages JSON - body: - "The + body: "The [update-langs](https://github.com/anuraghazra/github-readme-stats/actions/workflows/update-langs.yaml) action found new/updated languages in the [upstream languages JSON file](https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml)." diff --git a/backend/.vercelignore b/apps/backend/.vercelignore similarity index 64% rename from backend/.vercelignore rename to apps/backend/.vercelignore index 7dc1e951..cdf6243d 100644 --- a/backend/.vercelignore +++ b/apps/backend/.vercelignore @@ -1,7 +1,5 @@ -.devcontainer .github .husky -.vscode benchmarks coverage scripts @@ -10,6 +8,4 @@ tests **/*.md **/*.svg .eslintrc.json -.prettierignore -.pretterrc.json codecov.yml diff --git a/backend/_dot_vercel_copy/output/config.json b/apps/backend/_dot_vercel_copy/output/config.json similarity index 100% rename from backend/_dot_vercel_copy/output/config.json rename to apps/backend/_dot_vercel_copy/output/config.json diff --git a/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json b/apps/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json rename to apps/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json diff --git a/backend/_dot_vercel_copy/output/functions/api.func/router.js b/apps/backend/_dot_vercel_copy/output/functions/api.func/router.js similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api.func/router.js rename to apps/backend/_dot_vercel_copy/output/functions/api.func/router.js diff --git a/backend/_dot_vercel_copy/output/functions/api.prerender-config.json b/apps/backend/_dot_vercel_copy/output/functions/api.prerender-config.json similarity index 98% rename from backend/_dot_vercel_copy/output/functions/api.prerender-config.json rename to apps/backend/_dot_vercel_copy/output/functions/api.prerender-config.json index d7747ff4..f848b3a0 100644 --- a/backend/_dot_vercel_copy/output/functions/api.prerender-config.json +++ b/apps/backend/_dot_vercel_copy/output/functions/api.prerender-config.json @@ -2,4 +2,4 @@ "expiration": 39600, "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", "passQuery": true -} \ No newline at end of file +} diff --git a/backend/_dot_vercel_copy/output/functions/api/authenticate.func b/apps/backend/_dot_vercel_copy/output/functions/api/authenticate.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/authenticate.func rename to apps/backend/_dot_vercel_copy/output/functions/api/authenticate.func diff --git a/backend/_dot_vercel_copy/output/functions/api/delete-user.func b/apps/backend/_dot_vercel_copy/output/functions/api/delete-user.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/delete-user.func rename to apps/backend/_dot_vercel_copy/output/functions/api/delete-user.func diff --git a/backend/_dot_vercel_copy/output/functions/api/downgrade.func b/apps/backend/_dot_vercel_copy/output/functions/api/downgrade.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/downgrade.func rename to apps/backend/_dot_vercel_copy/output/functions/api/downgrade.func diff --git a/backend/_dot_vercel_copy/output/functions/api/gist.func b/apps/backend/_dot_vercel_copy/output/functions/api/gist.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/gist.func rename to apps/backend/_dot_vercel_copy/output/functions/api/gist.func diff --git a/backend/_dot_vercel_copy/output/functions/api/gist.prerender-config.json b/apps/backend/_dot_vercel_copy/output/functions/api/gist.prerender-config.json similarity index 98% rename from backend/_dot_vercel_copy/output/functions/api/gist.prerender-config.json rename to apps/backend/_dot_vercel_copy/output/functions/api/gist.prerender-config.json index d7747ff4..f848b3a0 100644 --- a/backend/_dot_vercel_copy/output/functions/api/gist.prerender-config.json +++ b/apps/backend/_dot_vercel_copy/output/functions/api/gist.prerender-config.json @@ -2,4 +2,4 @@ "expiration": 39600, "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", "passQuery": true -} \ No newline at end of file +} diff --git a/backend/_dot_vercel_copy/output/functions/api/pin.func b/apps/backend/_dot_vercel_copy/output/functions/api/pin.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/pin.func rename to apps/backend/_dot_vercel_copy/output/functions/api/pin.func diff --git a/backend/_dot_vercel_copy/output/functions/api/pin.prerender-config.json b/apps/backend/_dot_vercel_copy/output/functions/api/pin.prerender-config.json similarity index 98% rename from backend/_dot_vercel_copy/output/functions/api/pin.prerender-config.json rename to apps/backend/_dot_vercel_copy/output/functions/api/pin.prerender-config.json index d7747ff4..f848b3a0 100644 --- a/backend/_dot_vercel_copy/output/functions/api/pin.prerender-config.json +++ b/apps/backend/_dot_vercel_copy/output/functions/api/pin.prerender-config.json @@ -2,4 +2,4 @@ "expiration": 39600, "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", "passQuery": true -} \ No newline at end of file +} diff --git a/backend/_dot_vercel_copy/output/functions/api/repeat-recent.func b/apps/backend/_dot_vercel_copy/output/functions/api/repeat-recent.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/repeat-recent.func rename to apps/backend/_dot_vercel_copy/output/functions/api/repeat-recent.func diff --git a/backend/_dot_vercel_copy/output/functions/api/status/pat-info.func b/apps/backend/_dot_vercel_copy/output/functions/api/status/pat-info.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/status/pat-info.func rename to apps/backend/_dot_vercel_copy/output/functions/api/status/pat-info.func diff --git a/backend/_dot_vercel_copy/output/functions/api/status/up.func b/apps/backend/_dot_vercel_copy/output/functions/api/status/up.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/status/up.func rename to apps/backend/_dot_vercel_copy/output/functions/api/status/up.func diff --git a/backend/_dot_vercel_copy/output/functions/api/top-langs.func b/apps/backend/_dot_vercel_copy/output/functions/api/top-langs.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/top-langs.func rename to apps/backend/_dot_vercel_copy/output/functions/api/top-langs.func diff --git a/backend/_dot_vercel_copy/output/functions/api/top-langs.prerender-config.json b/apps/backend/_dot_vercel_copy/output/functions/api/top-langs.prerender-config.json similarity index 98% rename from backend/_dot_vercel_copy/output/functions/api/top-langs.prerender-config.json rename to apps/backend/_dot_vercel_copy/output/functions/api/top-langs.prerender-config.json index d7747ff4..f848b3a0 100644 --- a/backend/_dot_vercel_copy/output/functions/api/top-langs.prerender-config.json +++ b/apps/backend/_dot_vercel_copy/output/functions/api/top-langs.prerender-config.json @@ -2,4 +2,4 @@ "expiration": 39600, "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", "passQuery": true -} \ No newline at end of file +} diff --git a/backend/_dot_vercel_copy/output/functions/api/user-access.func b/apps/backend/_dot_vercel_copy/output/functions/api/user-access.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/user-access.func rename to apps/backend/_dot_vercel_copy/output/functions/api/user-access.func diff --git a/backend/_dot_vercel_copy/output/functions/api/wakatime-proxy.func b/apps/backend/_dot_vercel_copy/output/functions/api/wakatime-proxy.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/wakatime-proxy.func rename to apps/backend/_dot_vercel_copy/output/functions/api/wakatime-proxy.func diff --git a/backend/_dot_vercel_copy/output/functions/api/wakatime.func b/apps/backend/_dot_vercel_copy/output/functions/api/wakatime.func similarity index 100% rename from backend/_dot_vercel_copy/output/functions/api/wakatime.func rename to apps/backend/_dot_vercel_copy/output/functions/api/wakatime.func diff --git a/backend/_dot_vercel_copy/output/functions/api/wakatime.prerender-config.json b/apps/backend/_dot_vercel_copy/output/functions/api/wakatime.prerender-config.json similarity index 98% rename from backend/_dot_vercel_copy/output/functions/api/wakatime.prerender-config.json rename to apps/backend/_dot_vercel_copy/output/functions/api/wakatime.prerender-config.json index d7747ff4..f848b3a0 100644 --- a/backend/_dot_vercel_copy/output/functions/api/wakatime.prerender-config.json +++ b/apps/backend/_dot_vercel_copy/output/functions/api/wakatime.prerender-config.json @@ -2,4 +2,4 @@ "expiration": 39600, "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", "passQuery": true -} \ No newline at end of file +} diff --git a/backend/api-renamed/authenticate.js b/apps/backend/api-renamed/authenticate.js similarity index 100% rename from backend/api-renamed/authenticate.js rename to apps/backend/api-renamed/authenticate.js diff --git a/backend/api-renamed/delete-user.js b/apps/backend/api-renamed/delete-user.js similarity index 100% rename from backend/api-renamed/delete-user.js rename to apps/backend/api-renamed/delete-user.js diff --git a/backend/api-renamed/downgrade.js b/apps/backend/api-renamed/downgrade.js similarity index 88% rename from backend/api-renamed/downgrade.js rename to apps/backend/api-renamed/downgrade.js index 39b69db6..c4714a37 100644 --- a/backend/api-renamed/downgrade.js +++ b/apps/backend/api-renamed/downgrade.js @@ -53,10 +53,10 @@ export default async (req, res) => { }, ); } catch (err) { - logger.error(err); - res.statusCode = 500; - res.send("Failed to delete GitHub authorization with private access"); - return; + logger.error(err); + res.statusCode = 500; + res.send("Failed to delete GitHub authorization with private access"); + return; } await deleteUser(user_key); @@ -68,6 +68,9 @@ export default async (req, res) => { }).toString(); res.statusCode = 302; - res.setHeader("Location", `https://github.com/login/oauth/authorize?${params}`); + res.setHeader( + "Location", + `https://github.com/login/oauth/authorize?${params}`, + ); res.end(); }; diff --git a/backend/api-renamed/gist.js b/apps/backend/api-renamed/gist.js similarity index 100% rename from backend/api-renamed/gist.js rename to apps/backend/api-renamed/gist.js diff --git a/backend/api-renamed/index.js b/apps/backend/api-renamed/index.js similarity index 91% rename from backend/api-renamed/index.js rename to apps/backend/api-renamed/index.js index 196ac69a..3f68ef85 100644 --- a/backend/api-renamed/index.js +++ b/apps/backend/api-renamed/index.js @@ -87,26 +87,25 @@ export default async (req, res) => { ); } - const safePattern = /^[-\w\/.,]+$/; + const safePattern = /^[-\w/.,]+$/; if ( (username && !safePattern.test(username)) || (repo && !safePattern.test(repo)) || (owner && !safePattern.test(owner)) ) { return res.send( - renderError( - { - message: "Something went wrong", - secondaryMessage: "Username, repository or owner contains unsafe characters", - renderOptions: { - title_color, - text_color, - bg_color, - border_color, - theme, - }, + renderError({ + message: "Something went wrong", + secondaryMessage: + "Username, repository or owner contains unsafe characters", + renderOptions: { + title_color, + text_color, + bg_color, + border_color, + theme, }, - ), + }), ); } @@ -124,7 +123,7 @@ export default async (req, res) => { parseBoolean(include_all_commits), parseArray(exclude_repo), showStats.includes("prs_merged") || - showStats.includes("prs_merged_percentage"), + showStats.includes("prs_merged_percentage"), showStats.includes("discussions_started"), showStats.includes("discussions_answered"), parseInt(commits_year, 10), @@ -147,7 +146,9 @@ export default async (req, res) => { setCacheHeaders(res, cacheSeconds); return res.send( - renderStatsCard(stats, { + renderStatsCard( + stats, + { hide: parseArray(hide), show_icons: parseBoolean(show_icons), hide_title: parseBoolean(hide_title), diff --git a/backend/api-renamed/pin.js b/apps/backend/api-renamed/pin.js similarity index 99% rename from backend/api-renamed/pin.js rename to apps/backend/api-renamed/pin.js index bed3dd10..e34a4b9a 100644 --- a/backend/api-renamed/pin.js +++ b/apps/backend/api-renamed/pin.js @@ -78,7 +78,7 @@ export default async (req, res) => { ); } - const safePattern = /^[-\w\/.,]+$/; + const safePattern = /^[-\w/.,]+$/; if ( (username && !safePattern.test(username)) || (repo && !safePattern.test(repo)) diff --git a/backend/api-renamed/repeat-recent.js b/apps/backend/api-renamed/repeat-recent.js similarity index 100% rename from backend/api-renamed/repeat-recent.js rename to apps/backend/api-renamed/repeat-recent.js diff --git a/backend/api-renamed/status/pat-info.js b/apps/backend/api-renamed/status/pat-info.js similarity index 100% rename from backend/api-renamed/status/pat-info.js rename to apps/backend/api-renamed/status/pat-info.js diff --git a/backend/api-renamed/status/up.js b/apps/backend/api-renamed/status/up.js similarity index 100% rename from backend/api-renamed/status/up.js rename to apps/backend/api-renamed/status/up.js diff --git a/backend/api-renamed/top-langs.js b/apps/backend/api-renamed/top-langs.js similarity index 100% rename from backend/api-renamed/top-langs.js rename to apps/backend/api-renamed/top-langs.js diff --git a/backend/api-renamed/user-access.js b/apps/backend/api-renamed/user-access.js similarity index 95% rename from backend/api-renamed/user-access.js rename to apps/backend/api-renamed/user-access.js index a41af4cf..4fde6aa0 100644 --- a/backend/api-renamed/user-access.js +++ b/apps/backend/api-renamed/user-access.js @@ -18,7 +18,7 @@ export default async (req, res) => { res.send({ privateAccess: result.privateAccess, - token: result.token + token: result.token, }); } catch (err) { logger.error(err); diff --git a/backend/api-renamed/wakatime-proxy.js b/apps/backend/api-renamed/wakatime-proxy.js similarity index 100% rename from backend/api-renamed/wakatime-proxy.js rename to apps/backend/api-renamed/wakatime-proxy.js diff --git a/backend/api-renamed/wakatime.js b/apps/backend/api-renamed/wakatime.js similarity index 100% rename from backend/api-renamed/wakatime.js rename to apps/backend/api-renamed/wakatime.js diff --git a/backend/codecov.yml b/apps/backend/codecov.yml similarity index 100% rename from backend/codecov.yml rename to apps/backend/codecov.yml diff --git a/backend/express.js b/apps/backend/express.js similarity index 64% rename from backend/express.js rename to apps/backend/express.js index 92a7fb16..9948221a 100644 --- a/backend/express.js +++ b/apps/backend/express.js @@ -1,9 +1,9 @@ import "dotenv/config"; -import statsCard from "./api/index.js"; -import repoCard from "./api/pin.js"; -import langCard from "./api/top-langs.js"; -import wakatimeCard from "./api/wakatime.js"; -import gistCard from "./api/gist.js"; +import statsCard from "./api-renamed/index.js"; +import repoCard from "./api-renamed/pin.js"; +import langCard from "./api-renamed/top-langs.js"; +import wakatimeCard from "./api-renamed/wakatime.js"; +import gistCard from "./api-renamed/gist.js"; import express from "express"; const app = express(); diff --git a/backend/jest.bench.config.js b/apps/backend/jest.bench.config.js similarity index 100% rename from backend/jest.bench.config.js rename to apps/backend/jest.bench.config.js diff --git a/backend/jest.config.js b/apps/backend/jest.config.js similarity index 100% rename from backend/jest.config.js rename to apps/backend/jest.config.js diff --git a/backend/jest.e2e.config.js b/apps/backend/jest.e2e.config.js similarity index 100% rename from backend/jest.e2e.config.js rename to apps/backend/jest.e2e.config.js diff --git a/backend/package.json b/apps/backend/package.json similarity index 82% rename from backend/package.json rename to apps/backend/package.json index 9183eeac..cad22140 100644 --- a/backend/package.json +++ b/apps/backend/package.json @@ -27,9 +27,6 @@ "preview-theme": "node scripts/preview-theme", "close-stale-theme-prs": "node scripts/close-stale-theme-prs", "generate-langs-json": "node scripts/generate-langs-json", - "format": "prettier --write .", - "format:check": "prettier --check .", - "prepare": "husky", "lint": "npx eslint --max-warnings 0 \"./src/**/*.js\" \"./scripts/**/*.js\" \"./tests/**/*.js\" \"./api-renamed/**/*.js\" \"./themes/**/*.js\"", "bench": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.bench.config.js" }, @@ -38,27 +35,19 @@ "devDependencies": { "@actions/core": "^2.0.1", "@actions/github": "^6.0.1", - "@eslint/eslintrc": "^3.3.3", - "@eslint/js": "^9.39.2", + "@jest/globals": "30.2.0", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", "@uppercod/css-to-object": "^1.1.1", "axios-mock-adapter": "^2.1.0", "color-contrast-checker": "^2.1.0", - "eslint": "^9.39.2", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-jsdoc": "^62.4.1", "express": "^5.2.1", - "globals": "^17.1.0", "hjson": "^3.2.2", - "husky": "^9.1.7", "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", "js-yaml": "^4.1.1", - "lint-staged": "^16.2.7", "lodash.snakecase": "^4.1.1", - "parse-diff": "^0.11.1", - "prettier": "^3.8.1" + "parse-diff": "^0.11.1" }, "dependencies": { "axios": "^1.13.1", @@ -68,9 +57,6 @@ "pg": "^8.16.2", "word-wrap": "^1.2.5" }, - "lint-staged": { - "*.{js,css,md}": "prettier --write" - }, "engines": { "node": "24.x" } diff --git a/backend/powered-by-vercel.svg b/apps/backend/powered-by-vercel.svg similarity index 100% rename from backend/powered-by-vercel.svg rename to apps/backend/powered-by-vercel.svg diff --git a/backend/scripts/close-stale-theme-prs.js b/apps/backend/scripts/close-stale-theme-prs.js similarity index 100% rename from backend/scripts/close-stale-theme-prs.js rename to apps/backend/scripts/close-stale-theme-prs.js diff --git a/backend/scripts/generate-langs-json.js b/apps/backend/scripts/generate-langs-json.js similarity index 100% rename from backend/scripts/generate-langs-json.js rename to apps/backend/scripts/generate-langs-json.js diff --git a/backend/scripts/generate-theme-doc.js b/apps/backend/scripts/generate-theme-doc.js similarity index 100% rename from backend/scripts/generate-theme-doc.js rename to apps/backend/scripts/generate-theme-doc.js diff --git a/backend/scripts/helpers.js b/apps/backend/scripts/helpers.js similarity index 100% rename from backend/scripts/helpers.js rename to apps/backend/scripts/helpers.js diff --git a/backend/scripts/preview-theme.js b/apps/backend/scripts/preview-theme.js similarity index 100% rename from backend/scripts/preview-theme.js rename to apps/backend/scripts/preview-theme.js diff --git a/backend/scripts/push-theme-readme.sh b/apps/backend/scripts/push-theme-readme.sh similarity index 100% rename from backend/scripts/push-theme-readme.sh rename to apps/backend/scripts/push-theme-readme.sh diff --git a/backend/src/calculateRank.js b/apps/backend/src/calculateRank.js similarity index 98% rename from backend/src/calculateRank.js rename to apps/backend/src/calculateRank.js index a7741985..377b3fbe 100644 --- a/backend/src/calculateRank.js +++ b/apps/backend/src/calculateRank.js @@ -84,4 +84,3 @@ function calculateRank({ } export { calculateRank }; -export default calculateRank; diff --git a/backend/src/cards/gist.js b/apps/backend/src/cards/gist.js similarity index 97% rename from backend/src/cards/gist.js rename to apps/backend/src/cards/gist.js index da822e0e..b71c44f9 100644 --- a/backend/src/cards/gist.js +++ b/apps/backend/src/cards/gist.js @@ -14,7 +14,6 @@ import { icons } from "../common/icons.js"; import languageColors from "../common/languageColors.json" with { type: "json" }; import { parseEmojis } from "../common/ops.js"; - const ICON_SIZE = 16; const CARD_DEFAULT_WIDTH = 400; const HEADER_MAX_LENGTH = 35; @@ -139,5 +138,4 @@ const renderGistCard = (gistData, options = {}) => { `); }; -export { renderGistCard, HEADER_MAX_LENGTH }; -export default renderGistCard; +export { renderGistCard }; diff --git a/backend/src/cards/index.js b/apps/backend/src/cards/index.js similarity index 100% rename from backend/src/cards/index.js rename to apps/backend/src/cards/index.js diff --git a/backend/src/cards/repo.js b/apps/backend/src/cards/repo.js similarity index 99% rename from backend/src/cards/repo.js rename to apps/backend/src/cards/repo.js index c9f13999..fbbbdf36 100644 --- a/backend/src/cards/repo.js +++ b/apps/backend/src/cards/repo.js @@ -322,4 +322,3 @@ const renderRepoCard = (repo, options = {}) => { }; export { renderRepoCard }; -export default renderRepoCard; diff --git a/backend/src/cards/stats.js b/apps/backend/src/cards/stats.js similarity index 99% rename from backend/src/cards/stats.js rename to apps/backend/src/cards/stats.js index 5a786fed..f3063ac1 100644 --- a/backend/src/cards/stats.js +++ b/apps/backend/src/cards/stats.js @@ -672,4 +672,3 @@ const renderStatsCard = ( }; export { renderStatsCard, createTextNode }; -export default renderStatsCard; diff --git a/backend/src/cards/top-languages.js b/apps/backend/src/cards/top-languages.js similarity index 100% rename from backend/src/cards/top-languages.js rename to apps/backend/src/cards/top-languages.js diff --git a/backend/src/cards/types.d.ts b/apps/backend/src/cards/types.d.ts similarity index 98% rename from backend/src/cards/types.d.ts rename to apps/backend/src/cards/types.d.ts index 98ff3ed2..27e04c2a 100644 --- a/backend/src/cards/types.d.ts +++ b/apps/backend/src/cards/types.d.ts @@ -1,7 +1,7 @@ type ThemeNames = keyof typeof import("../../themes/index.js"); type RankIcon = "default" | "github" | "percentile"; -export type CommonOptions = { +type CommonOptions = { title_color: string; icon_color: string; text_color: string; diff --git a/backend/src/cards/wakatime.js b/apps/backend/src/cards/wakatime.js similarity index 99% rename from backend/src/cards/wakatime.js rename to apps/backend/src/cards/wakatime.js index 3df11fd0..2012f9c2 100644 --- a/backend/src/cards/wakatime.js +++ b/apps/backend/src/cards/wakatime.js @@ -378,7 +378,8 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { // @ts-ignore progressBarColor: titleColor, // @ts-ignore - progressBarBackgroundColor: textColor === titleColor ? bgColor : textColor, + progressBarBackgroundColor: + textColor === titleColor ? bgColor : textColor, hideProgress: hide_progress, progressBarWidth: normalizedWidth - TOTAL_TEXT_WIDTH, }); @@ -468,4 +469,3 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { }; export { renderWakatimeCard }; -export default renderWakatimeCard; diff --git a/backend/src/common/Card.js b/apps/backend/src/common/Card.js similarity index 100% rename from backend/src/common/Card.js rename to apps/backend/src/common/Card.js diff --git a/backend/src/common/I18n.js b/apps/backend/src/common/I18n.js similarity index 97% rename from backend/src/common/I18n.js rename to apps/backend/src/common/I18n.js index 75b9c91a..8cf1f445 100644 --- a/backend/src/common/I18n.js +++ b/apps/backend/src/common/I18n.js @@ -40,4 +40,3 @@ class I18n { } export { I18n }; -export default I18n; diff --git a/backend/src/common/access.js b/apps/backend/src/common/access.js similarity index 100% rename from backend/src/common/access.js rename to apps/backend/src/common/access.js diff --git a/backend/src/common/blacklist.js b/apps/backend/src/common/blacklist.js similarity index 83% rename from backend/src/common/blacklist.js rename to apps/backend/src/common/blacklist.js index c363a071..c9598b83 100644 --- a/backend/src/common/blacklist.js +++ b/apps/backend/src/common/blacklist.js @@ -7,4 +7,3 @@ const blacklist = [ ]; export { blacklist }; -export default blacklist; diff --git a/backend/src/common/cache.js b/apps/backend/src/common/cache.js similarity index 100% rename from backend/src/common/cache.js rename to apps/backend/src/common/cache.js diff --git a/backend/src/common/color.js b/apps/backend/src/common/color.js similarity index 100% rename from backend/src/common/color.js rename to apps/backend/src/common/color.js diff --git a/backend/src/common/database.js b/apps/backend/src/common/database.js similarity index 98% rename from backend/src/common/database.js rename to apps/backend/src/common/database.js index 5c5e4950..e62988cc 100644 --- a/backend/src/common/database.js +++ b/apps/backend/src/common/database.js @@ -215,7 +215,7 @@ export async function getUserAccessByKey(userKey) { } return { token: rows[0].access_token, - privateAccess: rows[0].private_access + privateAccess: rows[0].private_access, }; } catch (err) { if (err.code === "42P01") { @@ -250,7 +250,7 @@ export async function getUserAccessByName(userName) { } return { token: rows[0].access_token, - privateAccess: rows[0].private_access + privateAccess: rows[0].private_access, }; } catch (err) { if (err.code === "42P01") { diff --git a/backend/src/common/envs.js b/apps/backend/src/common/envs.js similarity index 100% rename from backend/src/common/envs.js rename to apps/backend/src/common/envs.js diff --git a/backend/src/common/error.js b/apps/backend/src/common/error.js similarity index 100% rename from backend/src/common/error.js rename to apps/backend/src/common/error.js diff --git a/backend/src/common/fmt.js b/apps/backend/src/common/fmt.js similarity index 100% rename from backend/src/common/fmt.js rename to apps/backend/src/common/fmt.js diff --git a/backend/src/common/html.js b/apps/backend/src/common/html.js similarity index 53% rename from backend/src/common/html.js rename to apps/backend/src/common/html.js index 2b1db470..3c5d44af 100644 --- a/backend/src/common/html.js +++ b/apps/backend/src/common/html.js @@ -9,11 +9,14 @@ * @returns {string} Encoded string. */ const encodeHTML = (str) => { - return str - .replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => { - return "&#" + i.charCodeAt(0) + ";"; - }) - .replace(/\u0008/gim, ""); + return ( + str + .replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => { + return "&#" + i.charCodeAt(0) + ";"; + }) + // eslint-disable-next-line no-control-regex + .replace(/\u0008/gim, "") + ); }; export { encodeHTML }; diff --git a/backend/src/common/http.js b/apps/backend/src/common/http.js similarity index 100% rename from backend/src/common/http.js rename to apps/backend/src/common/http.js diff --git a/backend/src/common/icons.js b/apps/backend/src/common/icons.js similarity index 99% rename from backend/src/common/icons.js rename to apps/backend/src/common/icons.js index 18e556eb..4a805d44 100644 --- a/backend/src/common/icons.js +++ b/apps/backend/src/common/icons.js @@ -53,4 +53,3 @@ const rankIcon = (rankIcon, rankLevel, percentile) => { }; export { icons, rankIcon }; -export default icons; diff --git a/backend/src/common/index.js b/apps/backend/src/common/index.js similarity index 100% rename from backend/src/common/index.js rename to apps/backend/src/common/index.js diff --git a/apps/backend/src/common/languageColors.json b/apps/backend/src/common/languageColors.json new file mode 100644 index 00000000..63bbbe94 --- /dev/null +++ b/apps/backend/src/common/languageColors.json @@ -0,0 +1,663 @@ +{ + "1C Enterprise": "#814CCC", + "2-Dimensional Array": "#38761D", + "4D": "#004289", + "ABAP": "#E8274B", + "ABAP CDS": "#555e25", + "AGS Script": "#B9D9FF", + "AIDL": "#34EB6B", + "AL": "#3AA2B5", + "ALGOL": "#D1E0DB", + "AMPL": "#E6EFBB", + "ANTLR": "#9DC3FF", + "API Blueprint": "#2ACCA8", + "APL": "#5A8164", + "ASP.NET": "#9400ff", + "ATS": "#1ac620", + "ActionScript": "#882B0F", + "Ada": "#02f88c", + "Adblock Filter List": "#800000", + "Adobe Font Metrics": "#fa0f00", + "Agda": "#315665", + "Aiken": "#640ff8", + "Alloy": "#64C800", + "Alpine Abuild": "#0D597F", + "Altium Designer": "#A89663", + "AngelScript": "#C7D7DC", + "Answer Set Programming": "#A9CC29", + "Ant Build System": "#A9157E", + "Antlers": "#ff269e", + "ApacheConf": "#d12127", + "Apex": "#1797c0", + "Apollo Guidance Computer": "#0B3D91", + "AppleScript": "#101F1F", + "Arc": "#aa2afe", + "AsciiDoc": "#73a0c5", + "AspectJ": "#a957b0", + "Assembly": "#6E4C13", + "Astro": "#ff5a03", + "Asymptote": "#ff0000", + "Augeas": "#9CC134", + "AutoHotkey": "#6594b9", + "AutoIt": "#1C3552", + "Avro IDL": "#0040FF", + "Awk": "#c30e9b", + "B (Formal Method)": "#8aa8c5", + "B4X": "#00e4ff", + "BASIC": "#ff0000", + "BQN": "#2b7067", + "Ballerina": "#FF5000", + "Batchfile": "#C1F12E", + "Beef": "#a52f4e", + "Berry": "#15A13C", + "BibTeX": "#778899", + "Bicep": "#519aba", + "Bikeshed": "#5562ac", + "Bison": "#6A463F", + "BitBake": "#00bce4", + "Blade": "#f7523f", + "BlitzBasic": "#00FFAE", + "BlitzMax": "#cd6400", + "Bluespec": "#12223c", + "Bluespec BH": "#12223c", + "Boo": "#d4bec1", + "Boogie": "#c80fa0", + "Brainfuck": "#2F2530", + "BrighterScript": "#66AABB", + "Brightscript": "#662D91", + "Browserslist": "#ffd539", + "Bru": "#F4AA41", + "BuildStream": "#006bff", + "C": "#555555", + "C#": "#178600", + "C++": "#f34b7d", + "C3": "#2563eb", + "CAP CDS": "#0092d1", + "CLIPS": "#00A300", + "CMake": "#DA3434", + "COLLADA": "#F1A42B", + "CQL": "#006091", + "CSON": "#244776", + "CSS": "#663399", + "CSV": "#237346", + "CUE": "#5886E1", + "CWeb": "#00007a", + "Cabal Config": "#483465", + "Caddyfile": "#22b638", + "Cadence": "#00ef8b", + "Cairo": "#ff4a48", + "Cairo Zero": "#ff4a48", + "CameLIGO": "#3be133", + "Cangjie": "#00868B", + "Cap'n Proto": "#c42727", + "Carbon": "#222222", + "Ceylon": "#dfa535", + "Chapel": "#8dc63f", + "ChucK": "#3f8000", + "Circom": "#707575", + "Cirru": "#ccccff", + "Clarion": "#db901e", + "Clarity": "#5546ff", + "Classic ASP": "#6a40fd", + "Clean": "#3F85AF", + "Click": "#E4E6F3", + "Clojure": "#db5855", + "Closure Templates": "#0d948f", + "Cloud Firestore Security Rules": "#FFA000", + "Clue": "#0009b5", + "CodeQL": "#140f46", + "CoffeeScript": "#244776", + "ColdFusion": "#ed2cd6", + "ColdFusion CFC": "#ed2cd6", + "Common Lisp": "#3fb68b", + "Common Workflow Language": "#B5314C", + "Component Pascal": "#B0CE4E", + "Cooklang": "#E15A29", + "Crystal": "#000100", + "Csound": "#1a1a1a", + "Csound Document": "#1a1a1a", + "Csound Score": "#1a1a1a", + "Cuda": "#3A4E3A", + "Curry": "#531242", + "Cylc": "#00b3fd", + "Cypher": "#34c0eb", + "Cython": "#fedf5b", + "D": "#ba595e", + "D2": "#526ee8", + "DM": "#447265", + "Dafny": "#FFEC25", + "Darcs Patch": "#8eff23", + "Dart": "#00B4AB", + "Daslang": "#d3d3d3", + "DataWeave": "#003a52", + "Debian Package Control File": "#D70751", + "DenizenScript": "#FBEE96", + "Dhall": "#dfafff", + "DirectX 3D File": "#aace60", + "Dockerfile": "#384d54", + "Dogescript": "#cca760", + "Dotenv": "#e5d559", + "Dune": "#89421e", + "Dylan": "#6c616e", + "E": "#ccce35", + "ECL": "#8a1267", + "ECLiPSe": "#001d9d", + "EJS": "#a91e50", + "EQ": "#a78649", + "Earthly": "#2af0ff", + "Easybuild": "#069406", + "Ecere Projects": "#913960", + "Ecmarkup": "#eb8131", + "Edge": "#0dffe0", + "EdgeQL": "#31A7FF", + "EditorConfig": "#fff1f2", + "Eiffel": "#4d6977", + "Elixir": "#6e4a7e", + "Elm": "#60B5CC", + "Elvish": "#55BB55", + "Elvish Transcript": "#55BB55", + "Emacs Lisp": "#c065db", + "EmberScript": "#FFF4F3", + "Erlang": "#B83998", + "Euphoria": "#FF790B", + "F#": "#b845fc", + "F*": "#572e30", + "FIGlet Font": "#FFDDBB", + "FIRRTL": "#2f632f", + "FLUX": "#88ccff", + "Factor": "#636746", + "Fancy": "#7b9db4", + "Fantom": "#14253c", + "Faust": "#c37240", + "Fennel": "#fff3d7", + "Filebench WML": "#F6B900", + "Flix": "#d44a45", + "Fluent": "#ffcc33", + "Forth": "#341708", + "Fortran": "#4d41b1", + "Fortran Free Form": "#4d41b1", + "FreeBASIC": "#141AC9", + "FreeMarker": "#0050b2", + "Frege": "#00cafe", + "Futhark": "#5f021f", + "G-code": "#D08CF2", + "GAML": "#FFC766", + "GAMS": "#f49a22", + "GAP": "#0000cc", + "GCC Machine Description": "#FFCFAB", + "GDScript": "#355570", + "GDShader": "#478CBF", + "GEDCOM": "#003058", + "GLSL": "#5686a5", + "GSC": "#FF6800", + "Game Maker Language": "#71b417", + "Gemfile.lock": "#701516", + "Gemini": "#ff6900", + "Genero 4gl": "#63408e", + "Genero per": "#d8df39", + "Genie": "#fb855d", + "Genshi": "#951531", + "Gentoo Ebuild": "#9400ff", + "Gentoo Eclass": "#9400ff", + "Gerber Image": "#d20b00", + "Gherkin": "#5B2063", + "Git Attributes": "#F44D27", + "Git Commit": "#F44D27", + "Git Config": "#F44D27", + "Git Revision List": "#F44D27", + "Gleam": "#ffaff3", + "Glimmer JS": "#F5835F", + "Glimmer TS": "#3178c6", + "Glyph": "#c1ac7f", + "Gnuplot": "#f0a9f0", + "Go": "#00ADD8", + "Go Checksums": "#00ADD8", + "Go Module": "#00ADD8", + "Go Template": "#00ADD8", + "Go Workspace": "#00ADD8", + "Godot Resource": "#355570", + "Golo": "#88562A", + "Gosu": "#82937f", + "Grace": "#615f8b", + "Gradle": "#02303a", + "Gradle Kotlin DSL": "#02303a", + "Grammatical Framework": "#ff0000", + "GraphQL": "#e10098", + "Graphviz (DOT)": "#2596be", + "Groovy": "#4298b8", + "Groovy Server Pages": "#4298b8", + "HAProxy": "#106da9", + "HCL": "#844FBA", + "HIP": "#4F3A4F", + "HLSL": "#aace60", + "HOCON": "#9ff8ee", + "HTML": "#e34c26", + "HTML+ECR": "#2e1052", + "HTML+EEX": "#6e4a7e", + "HTML+ERB": "#701516", + "HTML+PHP": "#4f5d95", + "HTML+Razor": "#512be4", + "HTTP": "#005C9C", + "HXML": "#f68712", + "Hack": "#878787", + "Haml": "#ece2a9", + "Handlebars": "#f7931e", + "Harbour": "#0e60e3", + "Hare": "#9d7424", + "Haskell": "#5e5086", + "Haxe": "#df7900", + "HiveQL": "#dce200", + "HolyC": "#ffefaf", + "Hosts File": "#308888", + "Hurl": "#FF0288", + "Hy": "#7790B2", + "IDL": "#a3522f", + "IGOR Pro": "#0000cc", + "INI": "#d1dbe0", + "ISPC": "#2D68B1", + "Idris": "#b30000", + "Ignore List": "#000000", + "ImageJ Macro": "#99AAFF", + "Imba": "#16cec6", + "Inno Setup": "#264b99", + "Io": "#a9188d", + "Ioke": "#078193", + "Isabelle": "#FEFE00", + "Isabelle ROOT": "#FEFE00", + "J": "#9EEDFF", + "JAR Manifest": "#b07219", + "JCL": "#d90e09", + "JFlex": "#DBCA00", + "JSON": "#292929", + "JSON with Comments": "#292929", + "JSON5": "#267CB9", + "JSONLD": "#0c479c", + "JSONiq": "#40d47e", + "Jac": "#FC792D", + "Jai": "#ab8b4b", + "Janet": "#0886a5", + "Jasmin": "#d03600", + "Java": "#b07219", + "Java Properties": "#2A6277", + "Java Server Pages": "#2A6277", + "Java Template Engine": "#2A6277", + "JavaScript": "#f1e05a", + "JavaScript+ERB": "#f1e05a", + "Jest Snapshot": "#15c213", + "JetBrains MPS": "#21D789", + "Jinja": "#a52a22", + "Jison": "#56b3cb", + "Jison Lex": "#56b3cb", + "Jolie": "#843179", + "Jsonnet": "#0064bd", + "Julia": "#a270ba", + "Julia REPL": "#a270ba", + "Jupyter Notebook": "#DA5B0B", + "Just": "#384d54", + "KCL": "#7ABABF", + "KDL": "#ffb3b3", + "KFramework": "#4195c5", + "KRL": "#28430A", + "Kaitai Struct": "#773b37", + "KakouneScript": "#6f8042", + "KerboScript": "#41adf0", + "KiCad Layout": "#2f4aab", + "KiCad Legacy Layout": "#2f4aab", + "KiCad Schematic": "#2f4aab", + "KoLmafia ASH": "#B9D9B9", + "Koka": "#215166", + "Kotlin": "#A97BFF", + "LFE": "#4C3023", + "LLVM": "#185619", + "LOLCODE": "#cc9900", + "LSL": "#3d9970", + "LabVIEW": "#fede06", + "Lambdapi": "#8027a3", + "Langium": "#2c8c87", + "Lark": "#2980B9", + "Lasso": "#999999", + "Latte": "#f2a542", + "Leo": "#C4FFC2", + "Less": "#1d365d", + "Lex": "#DBCA00", + "LigoLANG": "#0e74ff", + "LilyPond": "#9ccc7c", + "Liquid": "#67b8de", + "Literate Agda": "#315665", + "Literate CoffeeScript": "#244776", + "Literate Haskell": "#5e5086", + "LiveCode Script": "#0c5ba5", + "LiveScript": "#499886", + "Logtalk": "#295b9a", + "LookML": "#652B81", + "Lua": "#000080", + "Luau": "#00A2FF", + "M3U": "#179C7D", + "MATLAB": "#e16737", + "MAXScript": "#00a6a6", + "MDX": "#fcb32c", + "MLIR": "#5EC8DB", + "MQL4": "#62A8D6", + "MQL5": "#4A76B8", + "MTML": "#b7e1f4", + "Macaulay2": "#d8ffff", + "Makefile": "#427819", + "Mako": "#7e858d", + "Markdown": "#083fa1", + "Marko": "#42bff2", + "Mask": "#f97732", + "Mathematical Programming System": "#0530ad", + "Max": "#c4a79c", + "Mercury": "#ff2b2b", + "Mermaid": "#ff3670", + "Meson": "#007800", + "Metal": "#8f14e9", + "MiniYAML": "#ff1111", + "MiniZinc": "#06a9e6", + "Mint": "#02b046", + "Mirah": "#c7a938", + "Modelica": "#de1d31", + "Modula-2": "#10253f", + "Modula-3": "#223388", + "Mojo": "#ff4c1f", + "Monkey C": "#8D6747", + "MoonBit": "#b92381", + "MoonScript": "#ff4585", + "Motoko": "#fbb03b", + "Motorola 68K Assembly": "#005daa", + "Move": "#4a137a", + "Mustache": "#724b3b", + "NCL": "#28431f", + "NMODL": "#00356B", + "NPM Config": "#cb3837", + "NWScript": "#111522", + "Nasal": "#1d2c4e", + "Nearley": "#990000", + "Nemerle": "#3d3c6e", + "NetLinx": "#0aa0ff", + "NetLinx+ERB": "#747faa", + "NetLogo": "#ff6375", + "NewLisp": "#87AED7", + "Nextflow": "#3ac486", + "Nginx": "#009639", + "Nickel": "#E0C3FC", + "Nim": "#ffc200", + "Nit": "#009917", + "Nix": "#7e7eff", + "Noir": "#2f1f49", + "Nu": "#c9df40", + "NumPy": "#9C8AF9", + "Nunjucks": "#3d8137", + "Nushell": "#4E9906", + "OASv2-json": "#85ea2d", + "OASv2-yaml": "#85ea2d", + "OASv3-json": "#85ea2d", + "OASv3-yaml": "#85ea2d", + "OCaml": "#ef7a08", + "OMNeT++ MSG": "#a0e0a0", + "OMNeT++ NED": "#08607c", + "ObjectScript": "#424893", + "Objective-C": "#438eff", + "Objective-C++": "#6866fb", + "Objective-J": "#ff0c5a", + "Odin": "#60AFFE", + "Omgrofl": "#cabbff", + "Opal": "#f7ede0", + "Open Policy Agent": "#7d9199", + "OpenAPI Specification v2": "#85ea2d", + "OpenAPI Specification v3": "#85ea2d", + "OpenCL": "#ed2e2d", + "OpenEdge ABL": "#5ce600", + "OpenQASM": "#AA70FF", + "OpenSCAD": "#e5cd45", + "Option List": "#476732", + "Org": "#77aa99", + "OverpassQL": "#cce2aa", + "Oxygene": "#cdd0e3", + "Oz": "#fab738", + "P4": "#7055b5", + "PDDL": "#0d00ff", + "PEG.js": "#234d6b", + "PHP": "#4F5D95", + "PLSQL": "#dad8d8", + "PLpgSQL": "#336790", + "POV-Ray SDL": "#6bac65", + "Pact": "#F7A8B8", + "Pan": "#cc0000", + "Papyrus": "#6600cc", + "Parrot": "#f3ca0a", + "Pascal": "#E3F171", + "Pawn": "#dbb284", + "Pep8": "#C76F5B", + "Perl": "#0298c3", + "PicoLisp": "#6067af", + "PigLatin": "#fcd7de", + "Pike": "#005390", + "Pip Requirements": "#FFD343", + "Pkl": "#6b9543", + "PlantUML": "#fbbd16", + "PogoScript": "#d80074", + "Polar": "#ae81ff", + "Portugol": "#f8bd00", + "PostCSS": "#dc3a0c", + "PostScript": "#da291c", + "PowerBuilder": "#8f0f8d", + "PowerShell": "#012456", + "Praat": "#c8506d", + "Prisma": "#0c344b", + "Processing": "#0096D8", + "Procfile": "#3B2F63", + "Prolog": "#74283c", + "Promela": "#de0000", + "Propeller Spin": "#7fa2a7", + "Pug": "#a86454", + "Puppet": "#302B6D", + "PureBasic": "#5a6986", + "PureScript": "#1D222D", + "Pyret": "#ee1e10", + "Python": "#3572A5", + "Python console": "#3572A5", + "Python traceback": "#3572A5", + "Q#": "#fed659", + "QML": "#44a51c", + "Qt Script": "#00b841", + "Quake": "#882233", + "QuakeC": "#975777", + "QuickBASIC": "#008080", + "R": "#198CE7", + "RAML": "#77d9fb", + "RAScript": "#2C97FA", + "RBS": "#701516", + "RDoc": "#701516", + "REXX": "#d90e09", + "RMarkdown": "#198ce7", + "RON": "#a62c00", + "ROS Interface": "#22314e", + "RPGLE": "#2BDE21", + "RUNOFF": "#665a4e", + "Racket": "#3c5caa", + "Ragel": "#9d5200", + "Raku": "#0000fb", + "Rascal": "#fffaa0", + "ReScript": "#ed5051", + "Reason": "#ff5847", + "ReasonLIGO": "#ff5847", + "Rebol": "#358a5b", + "Record Jar": "#0673ba", + "Red": "#f50000", + "Regular Expression": "#009a00", + "Ren'Py": "#ff7f7f", + "Rez": "#FFDAB3", + "Ring": "#2D54CB", + "Riot": "#A71E49", + "RobotFramework": "#00c0b5", + "Roc": "#7c38f5", + "Rocq Prover": "#d0b68c", + "Roff": "#ecdebe", + "Roff Manpage": "#ecdebe", + "Rouge": "#cc0088", + "RouterOS Script": "#DE3941", + "Ruby": "#701516", + "Rust": "#dea584", + "SAS": "#B34936", + "SCSS": "#c6538c", + "SPARQL": "#0C4597", + "SQF": "#3F3F3F", + "SQL": "#e38c00", + "SQLPL": "#e38c00", + "SRecode Template": "#348a34", + "STL": "#373b5e", + "SVG": "#ff9900", + "Sail": "#259dd5", + "SaltStack": "#646464", + "Sass": "#a53b70", + "Scala": "#c22d40", + "Scaml": "#bd181a", + "Scenic": "#fdc700", + "Scheme": "#1e4aec", + "Scilab": "#ca0f21", + "Self": "#0579aa", + "ShaderLab": "#222c37", + "Shell": "#89e051", + "ShellCheck Config": "#cecfcb", + "Shen": "#120F14", + "Simple File Verification": "#C9BFED", + "Singularity": "#64E6AD", + "Slang": "#1fbec9", + "Slash": "#007eff", + "Slice": "#003fa2", + "Slim": "#2b2b2b", + "Slint": "#2379F4", + "SmPL": "#c94949", + "Smalltalk": "#596706", + "Smarty": "#f0c040", + "Smithy": "#c44536", + "Snakemake": "#419179", + "Solidity": "#AA6746", + "SourcePawn": "#f69e1d", + "Squirrel": "#800000", + "Stan": "#b2011d", + "Standard ML": "#dc566d", + "Starlark": "#76d275", + "Stata": "#1a5f91", + "StringTemplate": "#3fb34f", + "Stylus": "#ff6347", + "SubRip Text": "#9e0101", + "SugarSS": "#2fcc9f", + "SuperCollider": "#46390b", + "SurrealQL": "#ff00a0", + "Survex data": "#ffcc99", + "Svelte": "#ff3e00", + "Sway": "#00F58C", + "Sweave": "#198ce7", + "Swift": "#F05138", + "SystemVerilog": "#DAE1C2", + "TI Program": "#A0AA87", + "TL-Verilog": "#C40023", + "TLA": "#4b0079", + "TMDL": "#f0c913", + "TOML": "#9c4221", + "TSQL": "#e38c00", + "TSV": "#237346", + "TSX": "#3178c6", + "TXL": "#0178b8", + "Tact": "#48b5ff", + "Talon": "#333333", + "Tcl": "#e4cc98", + "TeX": "#3D6117", + "Teal": "#00B1BC", + "Terra": "#00004c", + "Terraform Template": "#7b42bb", + "TextGrid": "#c8506d", + "TextMate Properties": "#df66e4", + "Textile": "#ffe7ac", + "Thrift": "#D12127", + "Toit": "#c2c9fb", + "Tor Config": "#59316b", + "Tree-sitter Query": "#8ea64c", + "Turing": "#cf142b", + "Twig": "#c1d026", + "TypeScript": "#3178c6", + "TypeSpec": "#4A3665", + "Typst": "#239dad", + "Unified Parallel C": "#4e3617", + "Unity3D Asset": "#222c37", + "Uno": "#9933cc", + "UnrealScript": "#a54c4d", + "Untyped Plutus Core": "#36adbd", + "UrWeb": "#ccccee", + "V": "#4f87c4", + "VBA": "#867db1", + "VBScript": "#15dcdc", + "VCL": "#148AA8", + "VHDL": "#adb2cb", + "Vala": "#a56de2", + "Valve Data Format": "#f26025", + "Velocity Template Language": "#507cff", + "Vento": "#ff0080", + "Verilog": "#b2b7f8", + "Vim Help File": "#199f4b", + "Vim Script": "#199f4b", + "Vim Snippet": "#199f4b", + "Visual Basic .NET": "#945db7", + "Visual Basic 6.0": "#2c6353", + "Volt": "#1F1F1F", + "Vue": "#41b883", + "Vyper": "#9F4CF2", + "WDL": "#42f1f4", + "WGSL": "#1a5e9a", + "Web Ontology Language": "#5b70bd", + "WebAssembly": "#04133b", + "WebAssembly Interface Type": "#6250e7", + "Whiley": "#d5c397", + "Wikitext": "#fc5757", + "Windows Registry Entries": "#52d5ff", + "Witcher Script": "#ff0000", + "Wolfram Language": "#dd1100", + "Wollok": "#a23738", + "World of Warcraft Addon Data": "#f7e43f", + "Wren": "#383838", + "X10": "#4B6BEF", + "XC": "#99DA07", + "XML": "#0060ac", + "XML Property List": "#0060ac", + "XQuery": "#5232e7", + "XSLT": "#EB8CEB", + "Xmake": "#22a079", + "Xojo": "#81bd41", + "Xonsh": "#285EEF", + "Xtend": "#24255d", + "YAML": "#cb171e", + "YARA": "#220000", + "YASnippet": "#32AB90", + "Yacc": "#4B6C4B", + "Yul": "#794932", + "ZAP": "#0d665e", + "ZIL": "#dc75e5", + "ZenScript": "#00BCD1", + "Zephir": "#118f9e", + "Zig": "#ec915c", + "Zimpl": "#d67711", + "Zmodel": "#ff7100", + "crontab": "#ead7ac", + "eC": "#913960", + "fish": "#4aae47", + "hoon": "#00b171", + "iCalendar": "#ec564c", + "jq": "#c7254e", + "kvlang": "#1da6e0", + "mIRC Script": "#3d57c3", + "mcfunction": "#E22837", + "mdsvex": "#5f9ea0", + "mupad": "#244963", + "nanorc": "#2d004d", + "nesC": "#94B0C7", + "ooc": "#b0b77e", + "q": "#0040cd", + "reStructuredText": "#141414", + "sed": "#64b970", + "templ": "#66D0DD", + "vCard": "#ee2647", + "wisp": "#7582D1", + "xBase": "#403a40" +} diff --git a/backend/src/common/log.js b/apps/backend/src/common/log.js similarity index 92% rename from backend/src/common/log.js rename to apps/backend/src/common/log.js index 8a0e58b6..5836d50a 100644 --- a/backend/src/common/log.js +++ b/apps/backend/src/common/log.js @@ -11,4 +11,3 @@ const logger = process.env.NODE_ENV === "test" ? { log: noop, error: noop } : console; export { logger }; -export default logger; diff --git a/backend/src/common/ops.js b/apps/backend/src/common/ops.js similarity index 99% rename from backend/src/common/ops.js rename to apps/backend/src/common/ops.js index 0c3e3900..e9c9ab40 100644 --- a/backend/src/common/ops.js +++ b/apps/backend/src/common/ops.js @@ -1,6 +1,7 @@ // @ts-check import toEmoji from "emoji-name-map"; +import { CustomError } from "./error.js"; const OWNER_AFFILIATIONS = ["OWNER", "COLLABORATOR", "ORGANIZATION_MEMBER"]; diff --git a/backend/src/common/render.js b/apps/backend/src/common/render.js similarity index 100% rename from backend/src/common/render.js rename to apps/backend/src/common/render.js diff --git a/backend/src/common/retryer.js b/apps/backend/src/common/retryer.js similarity index 86% rename from backend/src/common/retryer.js rename to apps/backend/src/common/retryer.js index 46c01e24..f85cde27 100644 --- a/backend/src/common/retryer.js +++ b/apps/backend/src/common/retryer.js @@ -2,8 +2,18 @@ import { CustomError } from "./error.js"; import { logger } from "./log.js"; -import { getUserAccessByKey, getUserAccessByName } from "./database.js"; +import { getUserAccessByName } from "./database.js"; +/** + * Returns a random integer from 0 (inclusive) to `max` (exclusive). + * + * The value is generated using `Math.random()` and uniformly distributed + * across the range. + * + * @param {number} max The upper bound (exclusive). Must be a positive number. + * + * @returns {number} A random integer `n` such that `0 <= n < max`. + */ function getRandomInt(max) { return Math.floor(Math.random() * max); } @@ -17,7 +27,7 @@ function getRandomInt(max) { * Try to execute the fetcher function until it succeeds or the max number of retries is reached. * * @param {FetcherFunction} fetcher The fetcher function. - * @param username GitHub username of the user whose PAT to use, if available + * @param {string?} username GitHub username of the user whose PAT to use, if available * @param {any} variables Object with arguments to pass to the fetcher function. * @returns {Promise} The response from the fetcher function. */ diff --git a/backend/src/fetchers/gist.js b/apps/backend/src/fetchers/gist.js similarity index 99% rename from backend/src/fetchers/gist.js rename to apps/backend/src/fetchers/gist.js index ea4ec183..da27f994 100644 --- a/backend/src/fetchers/gist.js +++ b/apps/backend/src/fetchers/gist.js @@ -111,4 +111,3 @@ const fetchGist = async (id) => { }; export { fetchGist }; -export default fetchGist; diff --git a/backend/src/fetchers/repo.js b/apps/backend/src/fetchers/repo.js similarity index 99% rename from backend/src/fetchers/repo.js rename to apps/backend/src/fetchers/repo.js index 74d76f84..04371c5b 100644 --- a/backend/src/fetchers/repo.js +++ b/apps/backend/src/fetchers/repo.js @@ -159,4 +159,3 @@ const fetchRepo = async ( }; export { fetchRepo }; -export default fetchRepo; diff --git a/backend/src/fetchers/stats.js b/apps/backend/src/fetchers/stats.js similarity index 98% rename from backend/src/fetchers/stats.js rename to apps/backend/src/fetchers/stats.js index 7daab0a5..535f9d7e 100644 --- a/backend/src/fetchers/stats.js +++ b/apps/backend/src/fetchers/stats.js @@ -5,10 +5,7 @@ import * as dotenv from "dotenv"; import githubUsernameRegex from "github-username-regex"; import { calculateRank } from "../calculateRank.js"; import { retryer } from "../common/retryer.js"; -import { - buildSearchFilter, - parseOwnerAffiliations, -} from "../common/ops.js"; +import { buildSearchFilter, parseOwnerAffiliations } from "../common/ops.js"; import { logger } from "../common/log.js"; import { excludeRepositories } from "../common/envs.js"; import { CustomError, MissingParamError } from "../common/error.js"; @@ -191,10 +188,7 @@ const fetchTotalItems = (variables, token) => { `https://api.github.com/search/` + variables.type + `?per_page=1&q=` + - buildSearchFilter(variables.repo, variables.owner).replaceAll( - " ", - "+", - ) + + buildSearchFilter(variables.repo, variables.owner).replaceAll(" ", "+") + variables.filter, headers: { "Content-Type": "application/json", @@ -461,4 +455,3 @@ const fetchStats = async ( }; export { fetchStats, fetchRepoUserStats }; -export default fetchStats; diff --git a/backend/src/fetchers/top-languages.js b/apps/backend/src/fetchers/top-languages.js similarity index 97% rename from backend/src/fetchers/top-languages.js rename to apps/backend/src/fetchers/top-languages.js index e49319f1..60e6d6f8 100644 --- a/backend/src/fetchers/top-languages.js +++ b/apps/backend/src/fetchers/top-languages.js @@ -73,7 +73,10 @@ const fetchTopLanguages = async ( } ownerAffiliations = parseOwnerAffiliations(ownerAffiliations); - const res = await retryer(fetcher, username, { login: username, ownerAffiliations }); + const res = await retryer(fetcher, username, { + login: username, + ownerAffiliations, + }); if (res.data.errors) { logger.error(res.data.errors); @@ -163,4 +166,3 @@ const fetchTopLanguages = async ( }; export { fetchTopLanguages }; -export default fetchTopLanguages; diff --git a/backend/src/fetchers/types.d.ts b/apps/backend/src/fetchers/types.d.ts similarity index 100% rename from backend/src/fetchers/types.d.ts rename to apps/backend/src/fetchers/types.d.ts diff --git a/backend/src/fetchers/wakatime.js b/apps/backend/src/fetchers/wakatime.js similarity index 96% rename from backend/src/fetchers/wakatime.js rename to apps/backend/src/fetchers/wakatime.js index a081dbd2..2cf11e87 100644 --- a/backend/src/fetchers/wakatime.js +++ b/apps/backend/src/fetchers/wakatime.js @@ -34,4 +34,3 @@ const fetchWakatimeStats = async ({ username, api_domain }) => { }; export { fetchWakatimeStats }; -export default fetchWakatimeStats; diff --git a/backend/src/index.js b/apps/backend/src/index.js similarity index 100% rename from backend/src/index.js rename to apps/backend/src/index.js diff --git a/backend/src/repeatRequests.js b/apps/backend/src/repeatRequests.js similarity index 100% rename from backend/src/repeatRequests.js rename to apps/backend/src/repeatRequests.js diff --git a/backend/src/translations.js b/apps/backend/src/translations.js similarity index 99% rename from backend/src/translations.js rename to apps/backend/src/translations.js index 5ba7e96e..dd2643e5 100644 --- a/backend/src/translations.js +++ b/apps/backend/src/translations.js @@ -1126,7 +1126,6 @@ const isLocaleAvailable = (locale) => { }; export { - availableLocales, isLocaleAvailable, langCardLocales, repoCardLocales, diff --git a/backend/src/users.js b/apps/backend/src/users.js similarity index 100% rename from backend/src/users.js rename to apps/backend/src/users.js diff --git a/backend/tests/__snapshots__/renderWakatimeCard.test.js.snap b/apps/backend/tests/__snapshots__/renderWakatimeCard.test.js.snap similarity index 100% rename from backend/tests/__snapshots__/renderWakatimeCard.test.js.snap rename to apps/backend/tests/__snapshots__/renderWakatimeCard.test.js.snap diff --git a/backend/tests/api.test.js b/apps/backend/tests/api.test.js similarity index 100% rename from backend/tests/api.test.js rename to apps/backend/tests/api.test.js diff --git a/backend/tests/bench/api.bench.js b/apps/backend/tests/bench/api.bench.js similarity index 97% rename from backend/tests/bench/api.bench.js rename to apps/backend/tests/bench/api.bench.js index d6581da3..2106df54 100644 --- a/backend/tests/bench/api.bench.js +++ b/apps/backend/tests/bench/api.bench.js @@ -1,4 +1,4 @@ -import api from "../../api/index.js"; +import api from "../../api-renamed/index.js"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import { it, jest } from "@jest/globals"; diff --git a/backend/tests/bench/calculateRank.bench.js b/apps/backend/tests/bench/calculateRank.bench.js similarity index 100% rename from backend/tests/bench/calculateRank.bench.js rename to apps/backend/tests/bench/calculateRank.bench.js diff --git a/backend/tests/bench/gist.bench.js b/apps/backend/tests/bench/gist.bench.js similarity index 96% rename from backend/tests/bench/gist.bench.js rename to apps/backend/tests/bench/gist.bench.js index 66e618ec..f310ea37 100644 --- a/backend/tests/bench/gist.bench.js +++ b/apps/backend/tests/bench/gist.bench.js @@ -1,4 +1,4 @@ -import gist from "../../api/gist.js"; +import gist from "../../api-renamed/gist.js"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import { it, jest } from "@jest/globals"; diff --git a/backend/tests/bench/pin.bench.js b/apps/backend/tests/bench/pin.bench.js similarity index 96% rename from backend/tests/bench/pin.bench.js rename to apps/backend/tests/bench/pin.bench.js index 9a249846..e862617b 100644 --- a/backend/tests/bench/pin.bench.js +++ b/apps/backend/tests/bench/pin.bench.js @@ -1,4 +1,4 @@ -import pin from "../../api/pin.js"; +import pin from "../../api-renamed/pin.js"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import { it, jest } from "@jest/globals"; diff --git a/backend/tests/bench/utils.js b/apps/backend/tests/bench/utils.js similarity index 74% rename from backend/tests/bench/utils.js rename to apps/backend/tests/bench/utils.js index f3b1e03e..cb7327f8 100644 --- a/backend/tests/bench/utils.js +++ b/apps/backend/tests/bench/utils.js @@ -42,11 +42,22 @@ const measurePerformance = async (fn) => { * Computes basic & extended statistics. * * @param {bigint[]} samples Array of samples in nanoseconds. - * @returns {object} Stats + * @returns {{ + * runs: number + * min: number + * max: number + * average: number + * median: number + * p75: number + * p95: number + * p99: number + * stdev: number + * totalTime: number + * }} Stats */ const computeStats = (samples) => { const sorted = [...samples].sort((a, b) => (a < b ? -1 : 1)); - const toNumber = (b) => Number(b); // safe for typical short benches + const toNumber = (/** @type {bigint} */ b) => Number(b); // safe for typical short benches const n = sorted.length; const sum = sorted.reduce((a, b) => a + b, 0n); const avg = Number(sum) / n; @@ -54,7 +65,7 @@ const computeStats = (samples) => { n % 2 ? toNumber(sorted[(n - 1) / 2]) : (toNumber(sorted[n / 2 - 1]) + toNumber(sorted[n / 2])) / 2; - const p = (q) => { + const p = (/** @type {number} */ q) => { const idx = Math.min(n - 1, Math.floor((q / 100) * n)); return toNumber(sorted[idx]); }; @@ -120,7 +131,7 @@ export const runAndLogStats = async ( const stats = computeStats(processed); - const fmt = (ns) => formatTime(BigInt(Math.round(ns))); + const fmt = (/** @type {number} */ ns) => formatTime(BigInt(Math.round(ns))); console.log( `${fnName} | runs=${stats.runs} avg=${fmt(stats.average)} median=${fmt( stats.median, @@ -132,11 +143,37 @@ export const runAndLogStats = async ( return stats; }; +/** + * Creates an asymmetric matcher for approximate numeric equality. + * + * This helper is intended for use in test frameworks (e.g., Jest) where + * values need to be compared within a configurable decimal precision + * instead of strict equality. + * + * The comparison succeeds when: + * + * |actual - expected| < 10^(-precision) + * + * For example, with `precision = 3`, values must be within `0.001`. + * + * @param {number} expected The expected numeric value to compare against. + * + * @param {number} [precision=10] + * The number of decimal places of tolerance. Higher values mean stricter + * comparison. Internally converted to epsilon = 10^-precision. + * + * @returns {{ + * asymmetricMatch(actual: unknown): boolean, + * toAsymmetricMatcher(): string + * }} An object implementing Jest-style asymmetric matcher methods. + * + */ export function approxNumber(expected, precision = 10) { return { asymmetricMatch(actual) { - if (typeof actual !== "number" || typeof expected !== "number") + if (typeof actual !== "number" || typeof expected !== "number") { return false; + } const epsilon = Math.pow(10, -precision); return Math.abs(actual - expected) < epsilon; }, diff --git a/backend/tests/calculateRank.test.js b/apps/backend/tests/calculateRank.test.js similarity index 100% rename from backend/tests/calculateRank.test.js rename to apps/backend/tests/calculateRank.test.js diff --git a/backend/tests/card.test.js b/apps/backend/tests/card.test.js similarity index 100% rename from backend/tests/card.test.js rename to apps/backend/tests/card.test.js diff --git a/backend/tests/color.test.js b/apps/backend/tests/color.test.js similarity index 100% rename from backend/tests/color.test.js rename to apps/backend/tests/color.test.js diff --git a/backend/tests/e2e/e2e.test.js b/apps/backend/tests/e2e/e2e.test.js similarity index 100% rename from backend/tests/e2e/e2e.test.js rename to apps/backend/tests/e2e/e2e.test.js diff --git a/backend/tests/fetchGist.test.js b/apps/backend/tests/fetchGist.test.js similarity index 100% rename from backend/tests/fetchGist.test.js rename to apps/backend/tests/fetchGist.test.js diff --git a/backend/tests/fetchRepo.test.js b/apps/backend/tests/fetchRepo.test.js similarity index 100% rename from backend/tests/fetchRepo.test.js rename to apps/backend/tests/fetchRepo.test.js diff --git a/backend/tests/fetchStats.test.js b/apps/backend/tests/fetchStats.test.js similarity index 100% rename from backend/tests/fetchStats.test.js rename to apps/backend/tests/fetchStats.test.js diff --git a/backend/tests/fetchTopLanguages.test.js b/apps/backend/tests/fetchTopLanguages.test.js similarity index 100% rename from backend/tests/fetchTopLanguages.test.js rename to apps/backend/tests/fetchTopLanguages.test.js diff --git a/backend/tests/fetchWakatime.test.js b/apps/backend/tests/fetchWakatime.test.js similarity index 100% rename from backend/tests/fetchWakatime.test.js rename to apps/backend/tests/fetchWakatime.test.js diff --git a/backend/tests/flexLayout.test.js b/apps/backend/tests/flexLayout.test.js similarity index 100% rename from backend/tests/flexLayout.test.js rename to apps/backend/tests/flexLayout.test.js diff --git a/backend/tests/fmt.test.js b/apps/backend/tests/fmt.test.js similarity index 100% rename from backend/tests/fmt.test.js rename to apps/backend/tests/fmt.test.js diff --git a/backend/tests/gist.test.js b/apps/backend/tests/gist.test.js similarity index 100% rename from backend/tests/gist.test.js rename to apps/backend/tests/gist.test.js diff --git a/backend/tests/html.test.js b/apps/backend/tests/html.test.js similarity index 100% rename from backend/tests/html.test.js rename to apps/backend/tests/html.test.js diff --git a/backend/tests/i18n.test.js b/apps/backend/tests/i18n.test.js similarity index 100% rename from backend/tests/i18n.test.js rename to apps/backend/tests/i18n.test.js diff --git a/backend/tests/ops.test.js b/apps/backend/tests/ops.test.js similarity index 100% rename from backend/tests/ops.test.js rename to apps/backend/tests/ops.test.js diff --git a/backend/tests/pat-info.test.js b/apps/backend/tests/pat-info.test.js similarity index 100% rename from backend/tests/pat-info.test.js rename to apps/backend/tests/pat-info.test.js diff --git a/backend/tests/pin.test.js b/apps/backend/tests/pin.test.js similarity index 100% rename from backend/tests/pin.test.js rename to apps/backend/tests/pin.test.js diff --git a/backend/tests/render.test.js b/apps/backend/tests/render.test.js similarity index 100% rename from backend/tests/render.test.js rename to apps/backend/tests/render.test.js diff --git a/backend/tests/renderGistCard.test.js b/apps/backend/tests/renderGistCard.test.js similarity index 100% rename from backend/tests/renderGistCard.test.js rename to apps/backend/tests/renderGistCard.test.js diff --git a/backend/tests/renderRepoCard.test.js b/apps/backend/tests/renderRepoCard.test.js similarity index 100% rename from backend/tests/renderRepoCard.test.js rename to apps/backend/tests/renderRepoCard.test.js diff --git a/backend/tests/renderStatsCard.test.js b/apps/backend/tests/renderStatsCard.test.js similarity index 100% rename from backend/tests/renderStatsCard.test.js rename to apps/backend/tests/renderStatsCard.test.js diff --git a/backend/tests/renderTopLanguagesCard.test.js b/apps/backend/tests/renderTopLanguagesCard.test.js similarity index 100% rename from backend/tests/renderTopLanguagesCard.test.js rename to apps/backend/tests/renderTopLanguagesCard.test.js diff --git a/backend/tests/renderWakatimeCard.test.js b/apps/backend/tests/renderWakatimeCard.test.js similarity index 100% rename from backend/tests/renderWakatimeCard.test.js rename to apps/backend/tests/renderWakatimeCard.test.js diff --git a/backend/tests/retryer.test.js b/apps/backend/tests/retryer.test.js similarity index 100% rename from backend/tests/retryer.test.js rename to apps/backend/tests/retryer.test.js diff --git a/backend/tests/setup.jest.js b/apps/backend/tests/setup.jest.js similarity index 100% rename from backend/tests/setup.jest.js rename to apps/backend/tests/setup.jest.js diff --git a/backend/tests/status.up.test.js b/apps/backend/tests/status.up.test.js similarity index 100% rename from backend/tests/status.up.test.js rename to apps/backend/tests/status.up.test.js diff --git a/backend/tests/top-langs.test.js b/apps/backend/tests/top-langs.test.js similarity index 100% rename from backend/tests/top-langs.test.js rename to apps/backend/tests/top-langs.test.js diff --git a/backend/tests/wakatime.test.js b/apps/backend/tests/wakatime.test.js similarity index 100% rename from backend/tests/wakatime.test.js rename to apps/backend/tests/wakatime.test.js diff --git a/backend/themes/README.md b/apps/backend/themes/README.md similarity index 100% rename from backend/themes/README.md rename to apps/backend/themes/README.md diff --git a/backend/themes/index.js b/apps/backend/themes/index.js similarity index 99% rename from backend/themes/index.js rename to apps/backend/themes/index.js index f5d8d916..98fab2d3 100644 --- a/backend/themes/index.js +++ b/apps/backend/themes/index.js @@ -463,5 +463,3 @@ export const themes = { bg_color: "35,4158d0,c850c0,ffcc70", }, }; - -export default themes; diff --git a/backend/vercel.json b/apps/backend/vercel.json similarity index 69% rename from backend/vercel.json rename to apps/backend/vercel.json index 964d7ac7..eea5ca26 100644 --- a/backend/vercel.json +++ b/apps/backend/vercel.json @@ -1,5 +1,6 @@ { - "buildCommand": "npm install && ../vercel-preparation.sh", + "$schema": "https://openapi.vercel.sh/vercel.json", + "buildCommand": "pnpm install && ../../vercel-preparation.sh", "redirects": [ { "source": "/", @@ -12,4 +13,4 @@ "destination": "https://gse-frontend-preview.vercel.app/:match*" } ] -} \ No newline at end of file +} diff --git a/frontend/frontend/README.md b/apps/frontend/README.md similarity index 100% rename from frontend/frontend/README.md rename to apps/frontend/README.md diff --git a/frontend/frontend/deploy/Dockerfile b/apps/frontend/deploy/Dockerfile similarity index 100% rename from frontend/frontend/deploy/Dockerfile rename to apps/frontend/deploy/Dockerfile diff --git a/frontend/frontend/public/index.html b/apps/frontend/index.html similarity index 56% rename from frontend/frontend/public/index.html rename to apps/frontend/index.html index 17ebf4a2..1707e0a0 100644 --- a/frontend/frontend/public/index.html +++ b/apps/frontend/index.html @@ -1,8 +1,8 @@ - + - + @@ -13,33 +13,32 @@ /> - + - + + + - - + GitHub Stats Extended
+ diff --git a/apps/frontend/package.json b/apps/frontend/package.json new file mode 100644 index 00000000..f6d1d775 --- /dev/null +++ b/apps/frontend/package.json @@ -0,0 +1,52 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "dependencies": { + "axios": "^1", + "axios-cache-interceptor": "^1", + "daisyui": "2.31.0", + "emoji-name-map": "^2.0.3", + "github-username-regex": "^1.0.0", + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-icons": "^4.11.0", + "react-loading-skeleton": "^3.3.1", + "react-redux": "^8.1.3", + "react-router-dom": "^6.18.0", + "react-spinners": "^0.13.8", + "react-toastify": "^9.1.3", + "redux": "^4.2.1", + "save-svg-as-png": "^1.4.17", + "uuid": "^9.0.1", + "word-wrap": "^1.2.5" + }, + "devDependencies": { + "@vitejs/plugin-react-swc": "4.2.2", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.5", + "vite": "7.3.1", + "vite-plugin-node-polyfills": "0.25.0" + }, + "scripts": { + "dev": "vite", + "build": "vite build", + "build-trends": "vite build", + "preview": "vite preview" + }, + "homepage": "/frontend", + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/frontend/frontend/public/_redirects b/apps/frontend/public/_redirects similarity index 100% rename from frontend/frontend/public/_redirects rename to apps/frontend/public/_redirects diff --git a/frontend/frontend/public/favicon.ico b/apps/frontend/public/favicon.ico similarity index 100% rename from frontend/frontend/public/favicon.ico rename to apps/frontend/public/favicon.ico diff --git a/frontend/frontend/public/logo192.png b/apps/frontend/public/logo192.png similarity index 100% rename from frontend/frontend/public/logo192.png rename to apps/frontend/public/logo192.png diff --git a/frontend/frontend/public/logo512.png b/apps/frontend/public/logo512.png similarity index 100% rename from frontend/frontend/public/logo512.png rename to apps/frontend/public/logo512.png diff --git a/frontend/frontend/public/manifest.json b/apps/frontend/public/manifest.json similarity index 100% rename from frontend/frontend/public/manifest.json rename to apps/frontend/public/manifest.json diff --git a/frontend/frontend/public/robots.txt b/apps/frontend/public/robots.txt similarity index 100% rename from frontend/frontend/public/robots.txt rename to apps/frontend/public/robots.txt diff --git a/apps/frontend/src/api/index.js b/apps/frontend/src/api/index.js new file mode 100644 index 00000000..a5d65dfb --- /dev/null +++ b/apps/frontend/src/api/index.js @@ -0,0 +1,3 @@ +import { authenticate, getUserMetadata, deleteAccount } from "./user"; + +export { authenticate, getUserMetadata, deleteAccount }; diff --git a/frontend/frontend/src/api/user.js b/apps/frontend/src/api/user.js similarity index 92% rename from frontend/frontend/src/api/user.js rename to apps/frontend/src/api/user.js index 1d336535..0eb9441d 100644 --- a/frontend/frontend/src/api/user.js +++ b/apps/frontend/src/api/user.js @@ -1,6 +1,6 @@ -import axios from 'axios'; +import axios from "axios"; -import { HOST } from '../constants'; +import { HOST } from "../constants"; const authenticate = async (code, privateAccess, userKey) => { try { @@ -16,7 +16,7 @@ const authenticate = async (code, privateAccess, userKey) => { return userId; } catch (error) { console.error(error); - return ''; + return ""; } }; @@ -38,7 +38,7 @@ const deleteAccount = async (userId, userKey) => { return result.data; // no decorator } catch (error) { console.error(error); - return ''; + return ""; } }; diff --git a/frontend/frontend/src/assets/appLogo64.png b/apps/frontend/src/assets/appLogo64.png similarity index 100% rename from frontend/frontend/src/assets/appLogo64.png rename to apps/frontend/src/assets/appLogo64.png diff --git a/apps/frontend/src/axios-override.js b/apps/frontend/src/axios-override.js new file mode 100644 index 00000000..83b06694 --- /dev/null +++ b/apps/frontend/src/axios-override.js @@ -0,0 +1,126 @@ +import axios, { getAdapter } from "axios"; +import { setupCache } from "axios-cache-interceptor"; +import { HOST } from "./constants"; + +import additionalUserStars from "./mockData/additional_user_stars.json" with { type: "json" }; +import commentedIssues from "./mockData/commented_issues.json" with { type: "json" }; +import commentedPrs from "./mockData/commented_prs.json" with { type: "json" }; +import commits from "./mockData/commits.json" with { type: "json" }; +import gist_graphql from "./mockData/gist-graphql.json" with { type: "json" }; +import gist_rest from "./mockData/gist-rest.json" with { type: "json" }; +import repository from "./mockData/repository.json" with { type: "json" }; +import reviewedPrs from "./mockData/reviewed_prs.json" with { type: "json" }; +import topLanguages from "./mockData/top_languages.json" with { type: "json" }; +import userStats from "./mockData/user_stats.json" with { type: "json" }; +import wakatimeProxy from "./mockData/wakatime_proxy.json" with { type: "json" }; + +const cachedAxios = setupCache(axios, { + // Cache for 30 minutes + ttl: 30 * 60 * 1000, + interpretHeader: false, + cacheTakeover: false, + methods: ["get", "post"], + cachePredicate: { + allowUrls: ["api.github.com", HOST], + }, +}); + +axios.get = cachedAxios.get.bind(cachedAxios); +axios.post = cachedAxios.post.bind(cachedAxios); + +export function clearAxiosCache() { + cachedAxios.storage.clear(); +} + +function createMockResponse(data, config) { + return Promise.resolve({ + data, + status: 200, + statusText: "OK", + headers: {}, + request: {}, + config, + }); +} + +// store shouldMock outside React context so the interceptor can access it +let shouldMock = null; + +export function setShouldMock(newShouldMock) { + shouldMock = newShouldMock; +} + +const defaultAdapter = getAdapter(axios.defaults.adapter); + +// mock responses to "anuraghazra" requests +axios.defaults.adapter = async (config) => { + if (!shouldMock) { + return defaultAdapter(config); + } + + const params = config.data ? JSON.parse(config.data) : {}; + + if ( + config.url === "https://api.github.com/graphql" && + params.query?.includes( + "query userInfo($login: String!, $after: String, $includeMergedPullRequests:", + ) && + params.variables?.login === "anuraghazra" + ) { + return createMockResponse(userStats, config); + } + + if ( + config.url === "https://api.github.com/graphql" && + params.query?.includes( + "query userInfo($login: String!, $after: String, $ownerAffiliations:", + ) && + params.variables?.login === "anuraghazra" + ) { + return createMockResponse(additionalUserStars, config); + } + + if ( + config.url === "https://api.github.com/graphql" && + params.query?.includes( + "query userInfo($login: String!, $ownerAffiliations:", + ) && + params.variables?.login === "anuraghazra" + ) { + return createMockResponse(topLanguages, config); + } + + if ( + config.url === "https://api.github.com/graphql" && + params.query?.includes("fragment RepoInfo on Repository {") && + params.variables?.login === "anuraghazra" && + params.variables?.repo === "github-readme-stats" + ) { + return createMockResponse(repository, config); + } + + if ( + config.url === "https://api.github.com/graphql" && + params.query?.includes("query gistInfo(") && + params.variables?.gistName === "bbfce31e0217a3689c8d961a356cb10d" + ) { + return createMockResponse(gist_graphql, config); + } + + switch (config.url) { + case "https://api.github.com/gists/bbfce31e0217a3689c8d961a356cb10d": + return createMockResponse(gist_rest, config); + case "https://api.github.com/search/commits?per_page=1&q=author:anuraghazra": + return createMockResponse(commits, config); + case "https://api.github.com/search/issues?per_page=1&q=commenter:anuraghazra+-author:anuraghazra+type:pr": + return createMockResponse(commentedPrs, config); + case "https://api.github.com/search/issues?per_page=1&q=reviewed-by:anuraghazra+-author:anuraghazra+type:pr": + return createMockResponse(reviewedPrs, config); + case "https://api.github.com/search/issues?per_page=1&q=commenter:anuraghazra+-author:anuraghazra+type:issue": + return createMockResponse(commentedIssues, config); + case `https://${HOST}/api/wakatime-proxy?username=ffflabs`: + return createMockResponse(wakatimeProxy, config); + default: + return defaultAdapter(config); + } +}; diff --git a/frontend/frontend/src/components/Card/Card.js b/apps/frontend/src/components/Card/Card.jsx similarity index 75% rename from frontend/frontend/src/components/Card/Card.js rename to apps/frontend/src/components/Card/Card.jsx index 495f3f86..9e1f0d52 100644 --- a/frontend/frontend/src/components/Card/Card.js +++ b/apps/frontend/src/components/Card/Card.jsx @@ -1,11 +1,11 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -import SVG from './SVG'; -import { classnames } from '../../utils'; -import { HOST } from '../../constants'; +import SVG from "./SVG"; +import { classnames } from "../../utils"; +import { HOST } from "../../constants"; -export const Image = ({ imageSrc, stage, compact, extraClasses = '' }) => { +export const Image = ({ imageSrc, stage, compact, extraClasses = "" }) => { const fullImageSrc = `https://${HOST}/api${imageSrc}&client=wizard`; return ( @@ -29,7 +29,7 @@ Image.propTypes = { Image.defaultProps = { compact: false, - extraClasses: '', + extraClasses: "", }; export const Card = ({ @@ -44,11 +44,11 @@ export const Card = ({ return (

{title}

@@ -56,7 +56,7 @@ export const Card = ({
diff --git a/frontend/frontend/src/components/Card/SVG.js b/apps/frontend/src/components/Card/SVG.jsx similarity index 71% rename from frontend/frontend/src/components/Card/SVG.js rename to apps/frontend/src/components/Card/SVG.jsx index 7a640908..3ba06b6a 100644 --- a/frontend/frontend/src/components/Card/SVG.js +++ b/apps/frontend/src/components/Card/SVG.jsx @@ -1,20 +1,17 @@ -/* eslint-disable react/jsx-props-no-spreading */ -/* eslint-disable react/no-danger */ +import React, { useEffect, useRef, useState } from "react"; +import PropTypes from "prop-types"; -import React, { useEffect, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; +import Skeleton from "react-loading-skeleton"; +import "react-loading-skeleton/dist/skeleton.css"; -import Skeleton from 'react-loading-skeleton'; -import 'react-loading-skeleton/dist/skeleton.css'; - -import { createMockReq, createMockRes } from '../../mock-http'; -import { default as router } from '../../backend/.vercel/output/functions/api.func/router.js'; -import { setShouldMock } from '../../axios-override'; +import { createMockReq, createMockRes } from "../../mock-http"; +import { default as router } from "../../backend/.vercel/output/functions/api.func/router.js"; +import { setShouldMock } from "../../axios-override"; import { useIsAuthenticated, useUserToken, -} from '../../redux/selectors/userSelectors'; -import axios from 'axios'; +} from "../../redux/selectors/userSelectors"; +import axios from "axios"; const SvgInline = (props) => { const [svg, setSvg] = useState(null); @@ -40,7 +37,7 @@ const SvgInline = (props) => { let body; let status; - if (isAuthenticated && (!userToken || userToken === 'placeholderPAT')) { + if (isAuthenticated && (!userToken || userToken === "placeholderPAT")) { // waiting for backend call to private-access return; } @@ -51,7 +48,7 @@ const SvgInline = (props) => { status = res.status; } else { const req = createMockReq({ - method: 'GET', + method: "GET", url: url, }); const res = createMockRes(); @@ -61,7 +58,7 @@ const SvgInline = (props) => { } if (status >= 300) { - console.error('failed to fetch/generate SVG'); + console.error("failed to fetch/generate SVG"); return; } @@ -83,12 +80,12 @@ const SvgInline = (props) => { // Attach shadow root if not already present let shadow = containerRef.current.shadowRoot; if (!shadow) { - shadow = containerRef.current.attachShadow({ mode: 'open' }); + shadow = containerRef.current.attachShadow({ mode: "open" }); } // Clear previous content - shadow.innerHTML = ''; + shadow.innerHTML = ""; // Insert SVG - const wrapper = document.createElement('div'); + const wrapper = document.createElement("div"); wrapper.innerHTML = svg; shadow.appendChild(wrapper); } @@ -96,7 +93,7 @@ const SvgInline = (props) => { if (props.forceLoading || !loaded) { if (props.compact) { - return ; + return ; } // maximum dimensions of cards in SelectCard stage return ; @@ -115,7 +112,7 @@ SvgInline.propTypes = { }; SvgInline.defaultProps = { - className: '', + className: "", forceLoading: false, compact: false, }; diff --git a/frontend/frontend/src/components/Generic/Button.js b/apps/frontend/src/components/Generic/Button.jsx similarity index 53% rename from frontend/frontend/src/components/Generic/Button.js rename to apps/frontend/src/components/Generic/Button.jsx index b12947b0..802dc57b 100644 --- a/frontend/frontend/src/components/Generic/Button.js +++ b/apps/frontend/src/components/Generic/Button.jsx @@ -1,8 +1,7 @@ -/* eslint-disable react/jsx-props-no-spreading */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -import { classnames } from '../../utils'; +import { classnames } from "../../utils"; const Button = (props) => { return ( @@ -11,7 +10,7 @@ const Button = (props) => { {...props} className={classnames( props.className, - 'border-0 py-2 px-6 inline-flex focus:outline-none rounded-[0.25rem] text-lg', + "border-0 py-2 px-6 inline-flex focus:outline-none rounded-[0.25rem] text-lg", )} > {props.children} @@ -25,7 +24,7 @@ Button.propTypes = { }; Button.defaultProps = { - className: '', + className: "", }; -export default Button; +export { Button }; diff --git a/frontend/frontend/src/components/Generic/Checkbox.js b/apps/frontend/src/components/Generic/Checkbox.jsx similarity index 81% rename from frontend/frontend/src/components/Generic/Checkbox.js rename to apps/frontend/src/components/Generic/Checkbox.jsx index 58ef2ad8..657e3bce 100644 --- a/frontend/frontend/src/components/Generic/Checkbox.js +++ b/apps/frontend/src/components/Generic/Checkbox.jsx @@ -1,5 +1,5 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; const Checkbox = ({ question, variable, setVariable, disabled }) => { return ( @@ -11,7 +11,7 @@ const Checkbox = ({ question, variable, setVariable, disabled }) => { setVariable(!variable)} /> @@ -31,4 +31,4 @@ Checkbox.defaultProps = { disabled: false, }; -export default Checkbox; +export { Checkbox }; diff --git a/frontend/frontend/src/components/Generic/Input.js b/apps/frontend/src/components/Generic/Input.jsx similarity index 75% rename from frontend/frontend/src/components/Generic/Input.js rename to apps/frontend/src/components/Generic/Input.jsx index 14948ed2..2d2260e1 100644 --- a/frontend/frontend/src/components/Generic/Input.js +++ b/apps/frontend/src/components/Generic/Input.jsx @@ -1,7 +1,7 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -import { classnames } from '../../utils'; +import { classnames } from "../../utils"; // options is of form [{value: '', label: '', disabled: true/false}] const Input = ({ @@ -14,15 +14,15 @@ const Input = ({ return ( setInternalValue(e.target.value)} @@ -68,8 +66,8 @@ TextSection.propTypes = { TextSection.defaultProps = { disabled: false, - placeholder: '', + placeholder: "", onPaste: undefined, }; -export default TextSection; +export { TextSection }; diff --git a/frontend/frontend/src/components/Home/WakatimeLayoutSection.js b/apps/frontend/src/components/Home/WakatimeLayoutSection.jsx similarity index 67% rename from frontend/frontend/src/components/Home/WakatimeLayoutSection.js rename to apps/frontend/src/components/Home/WakatimeLayoutSection.jsx index b8e4b94c..56bdf26a 100644 --- a/frontend/frontend/src/components/Home/WakatimeLayoutSection.js +++ b/apps/frontend/src/components/Home/WakatimeLayoutSection.jsx @@ -1,25 +1,25 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -import Section from './Section'; -import { Input } from '../Generic'; +import { Section } from "./Section"; +import { Input } from "../Generic/Input"; export const DEFAULT_OPTION = { id: 1, - label: 'Normal', + label: "Normal", disabled: false, - value: 'default', + value: "default", }; const WakatimeLayoutSection = ({ selectedOption, setSelectedOption }) => { const options = [ DEFAULT_OPTION, - { id: 2, label: 'Compact', disabled: false, value: 'compact' }, + { id: 2, label: "Compact", disabled: false, value: "compact" }, { id: 3, - label: 'Text Only', + label: "Text Only", disabled: false, - value: 'default&hide_progress=true&card_width=315', + value: "default&hide_progress=true&card_width=315", }, ]; diff --git a/apps/frontend/src/constants.js b/apps/frontend/src/constants.js new file mode 100644 index 00000000..6d45c90d --- /dev/null +++ b/apps/frontend/src/constants.js @@ -0,0 +1,26 @@ +const PROD = false; + +export const USE_LOGGER = true; + +export const CLIENT_ID = "Ov23lilAc5biyyRY0K1u"; + +export const HOST = PROD + ? "github-stats-extended.vercel.app" + : "github-stats-extended-preview.vercel.app"; + +const REDIRECT_URI = `https://${HOST}/frontend`; + +export const GITHUB_PRIVATE_AUTH_URL = `https://github.com/login/oauth/authorize?scope=user,repo&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}/private`; +export const GITHUB_PUBLIC_AUTH_URL = `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}/public`; + +export const DEMO_USER = "anuraghazra"; +export const DEMO_REPO = "anuraghazra/github-readme-stats"; +export const DEMO_GIST = "bbfce31e0217a3689c8d961a356cb10d"; +export const DEMO_WAKATIME_USER = "ffflabs"; + +window.process = { + env: { + FETCH_MULTI_PAGE_STARS: 10, + PAT_1: "placeholderPAT", // so the backend's retryer.js sees there is 1 PAT and sets `RETRIES` accordingly + }, +}; diff --git a/frontend/frontend/src/dotenv-browser-stub.js b/apps/frontend/src/dotenv-browser-stub.js similarity index 100% rename from frontend/frontend/src/dotenv-browser-stub.js rename to apps/frontend/src/dotenv-browser-stub.js diff --git a/frontend/frontend/src/index.css b/apps/frontend/src/index.css similarity index 79% rename from frontend/frontend/src/index.css rename to apps/frontend/src/index.css index 33b26daf..7652c239 100644 --- a/frontend/frontend/src/index.css +++ b/apps/frontend/src/index.css @@ -5,7 +5,7 @@ body { margin: 0; - font-family: 'Segoe UI', Ubuntu, Sans-Serif; + font-family: "Segoe UI", Ubuntu, Sans-Serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } diff --git a/apps/frontend/src/index.jsx b/apps/frontend/src/index.jsx new file mode 100644 index 00000000..9ec266a4 --- /dev/null +++ b/apps/frontend/src/index.jsx @@ -0,0 +1,18 @@ +import "./axios-override"; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { Provider } from "react-redux"; + +import configureStore from "./redux/store"; +import { AppTrends } from "./pages/App"; +import "./index.css"; + +export const store = configureStore(); + +const root = ReactDOM.createRoot(document.getElementById("root")); + +root.render( + + + , +); diff --git a/frontend/frontend/src/mock-http.js b/apps/frontend/src/mock-http.js similarity index 90% rename from frontend/frontend/src/mock-http.js rename to apps/frontend/src/mock-http.js index 74081696..3c926765 100644 --- a/frontend/frontend/src/mock-http.js +++ b/apps/frontend/src/mock-http.js @@ -1,5 +1,5 @@ export function createMockReq({ - method = 'GET', + method = "GET", url, headers = {}, body = null, @@ -32,7 +32,7 @@ export function createMockRes() { }, write(chunk) { - if (typeof chunk !== 'string') { + if (typeof chunk !== "string") { chunk = String(chunk); } chunks.push(chunk); @@ -50,7 +50,7 @@ export function createMockRes() { return { ...headers }; }, _getBody() { - return chunks.join(''); + return chunks.join(""); }, }; diff --git a/frontend/frontend/src/mockData/additional_user_stars.json b/apps/frontend/src/mockData/additional_user_stars.json similarity index 100% rename from frontend/frontend/src/mockData/additional_user_stars.json rename to apps/frontend/src/mockData/additional_user_stars.json diff --git a/frontend/frontend/src/mockData/commented_issues.json b/apps/frontend/src/mockData/commented_issues.json similarity index 99% rename from frontend/frontend/src/mockData/commented_issues.json rename to apps/frontend/src/mockData/commented_issues.json index 4c7aae6f..881b3012 100644 --- a/frontend/frontend/src/mockData/commented_issues.json +++ b/apps/frontend/src/mockData/commented_issues.json @@ -34,9 +34,7 @@ "user_view_type": "public", "site_admin": false }, - "labels": [ - - ], + "labels": [], "state": "closed", "locked": false, "assignee": { diff --git a/frontend/frontend/src/mockData/commented_prs.json b/apps/frontend/src/mockData/commented_prs.json similarity index 98% rename from frontend/frontend/src/mockData/commented_prs.json rename to apps/frontend/src/mockData/commented_prs.json index ad391b32..01912b8f 100644 --- a/frontend/frontend/src/mockData/commented_prs.json +++ b/apps/frontend/src/mockData/commented_prs.json @@ -34,15 +34,11 @@ "user_view_type": "public", "site_admin": false }, - "labels": [ - - ], + "labels": [], "state": "open", "locked": false, "assignee": null, - "assignees": [ - - ], + "assignees": [], "milestone": null, "comments": 2, "created_at": "2025-11-18T09:48:46Z", diff --git a/frontend/frontend/src/mockData/commits.json b/apps/frontend/src/mockData/commits.json similarity index 100% rename from frontend/frontend/src/mockData/commits.json rename to apps/frontend/src/mockData/commits.json diff --git a/frontend/frontend/src/mockData/gist-graphql.json b/apps/frontend/src/mockData/gist-graphql.json similarity index 100% rename from frontend/frontend/src/mockData/gist-graphql.json rename to apps/frontend/src/mockData/gist-graphql.json diff --git a/frontend/frontend/src/mockData/gist-rest.json b/apps/frontend/src/mockData/gist-rest.json similarity index 100% rename from frontend/frontend/src/mockData/gist-rest.json rename to apps/frontend/src/mockData/gist-rest.json diff --git a/frontend/frontend/src/mockData/repository.json b/apps/frontend/src/mockData/repository.json similarity index 94% rename from frontend/frontend/src/mockData/repository.json rename to apps/frontend/src/mockData/repository.json index 89ba2e16..6e2e4681 100644 --- a/frontend/frontend/src/mockData/repository.json +++ b/apps/frontend/src/mockData/repository.json @@ -24,9 +24,7 @@ "errors": [ { "type": "NOT_FOUND", - "path": [ - "organization" - ], + "path": ["organization"], "locations": [ { "line": 25, diff --git a/frontend/frontend/src/mockData/reviewed_prs.json b/apps/frontend/src/mockData/reviewed_prs.json similarity index 98% rename from frontend/frontend/src/mockData/reviewed_prs.json rename to apps/frontend/src/mockData/reviewed_prs.json index d2e2b7ad..31455234 100644 --- a/frontend/frontend/src/mockData/reviewed_prs.json +++ b/apps/frontend/src/mockData/reviewed_prs.json @@ -34,15 +34,11 @@ "user_view_type": "public", "site_admin": false }, - "labels": [ - - ], + "labels": [], "state": "open", "locked": false, "assignee": null, - "assignees": [ - - ], + "assignees": [], "milestone": null, "comments": 2, "created_at": "2025-11-18T09:48:46Z", diff --git a/frontend/frontend/src/mockData/top_languages.json b/apps/frontend/src/mockData/top_languages.json similarity index 100% rename from frontend/frontend/src/mockData/top_languages.json rename to apps/frontend/src/mockData/top_languages.json diff --git a/frontend/frontend/src/mockData/user_stats.json b/apps/frontend/src/mockData/user_stats.json similarity index 100% rename from frontend/frontend/src/mockData/user_stats.json rename to apps/frontend/src/mockData/user_stats.json diff --git a/frontend/frontend/src/mockData/wakatime_proxy.json b/apps/frontend/src/mockData/wakatime_proxy.json similarity index 100% rename from frontend/frontend/src/mockData/wakatime_proxy.json rename to apps/frontend/src/mockData/wakatime_proxy.json diff --git a/frontend/frontend/src/pages/App/AppTrends.js b/apps/frontend/src/pages/App/AppTrends.jsx similarity index 66% rename from frontend/frontend/src/pages/App/AppTrends.js rename to apps/frontend/src/pages/App/AppTrends.jsx index bba5f1c1..e1de250d 100644 --- a/frontend/frontend/src/pages/App/AppTrends.js +++ b/apps/frontend/src/pages/App/AppTrends.jsx @@ -1,39 +1,45 @@ -import React, { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; +import React, { useEffect, useState } from "react"; +import { useDispatch } from "react-redux"; -import { BrowserRouter as Router } from 'react-router-dom'; +import { BrowserRouter as Router } from "react-router-dom"; import { logout as _logout, setUserAccess as _setUserAccess, -} from '../../redux/actions/userActions'; +} from "../../redux/actions/userActions"; -import Header from './Header'; -import HomeScreen from '../Home'; -import { getUserMetadata } from '../../api'; +import Header from "./Header"; +import HomeScreen from "../Home"; +import { getUserMetadata } from "../../api"; import { useIsAuthenticated, useUserKey, useUserToken, -} from '../../redux/selectors/userSelectors'; -import { toast, ToastContainer } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; -import { clearAxiosCache } from '../../axios-override'; +} from "../../redux/selectors/userSelectors"; +import { toast, ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import { clearAxiosCache } from "../../axios-override"; function App() { const toMessage = (input) => { - if (typeof input === 'string') return input; - if (input.reason?.message) return input.reason.message; - if (input.message) return input.message; + if (typeof input === "string") { + return input; + } + if (input.reason?.message) { + return input.reason.message; + } + if (input.message) { + return input.message; + } try { return JSON.stringify(input); } catch { - return 'Unknown error'; + return "Unknown error"; } }; const showError = (event) => { toast.error(toMessage(event), { - position: 'bottom-right', + position: "bottom-right", autoClose: 1500, hideProgressBar: true, closeOnClick: false, @@ -43,10 +49,10 @@ function App() { }); }; - window.addEventListener('error', (event) => { + window.addEventListener("error", (event) => { showError(event); }); - window.addEventListener('unhandledrejection', (event) => { + window.addEventListener("unhandledrejection", (event) => { showError(event); }); diff --git a/frontend/frontend/src/pages/App/Header.js b/apps/frontend/src/pages/App/Header.jsx similarity index 76% rename from frontend/frontend/src/pages/App/Header.js rename to apps/frontend/src/pages/App/Header.jsx index b50be0b1..d418a166 100644 --- a/frontend/frontend/src/pages/App/Header.js +++ b/apps/frontend/src/pages/App/Header.jsx @@ -1,13 +1,12 @@ -import React, { useState } from 'react'; -import { useDispatch } from 'react-redux'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -import { Link } from 'react-router-dom'; +import { Link } from "react-router-dom"; -import appIcon from '../../assets/appLogo64.png'; -import { classnames } from '../../utils'; -import { FaGithub as GithubIcon } from 'react-icons/fa'; -import { ProgressBar } from '../../components'; +import appIcon from "../../assets/appLogo64.png"; +import { classnames } from "../../utils"; +import { FaGithub as GithubIcon } from "react-icons/fa"; +import { ProgressBar } from "../../components/Home/Progress"; const propTypes = { to: PropTypes.string.isRequired, @@ -25,7 +24,7 @@ const StandardLink = ({ to, children, onClick, className }) => ( ( { - const dispatch = useDispatch(); - return ( <>
@@ -90,11 +87,11 @@ const Header = ({ stage, setStage }) => {
{ const [isLoading, setIsLoading] = useState(false); @@ -49,7 +48,7 @@ const HomeScreen = ({ stage, setStage }) => { const [gist, setGist] = useState(DEMO_GIST); const [wakatimeUser, setWakatimeUser] = useState(DEMO_WAKATIME_USER); - const [selectedCard, setSelectedCard] = useState('stats'); + const [selectedCard, setSelectedCard] = useState("stats"); useEffect(() => { setSelectedUserId(userId); @@ -68,7 +67,7 @@ const HomeScreen = ({ stage, setStage }) => { const [showTitle, setShowTitle] = useState(true); const [showOwner, setShowOwner] = useState(false); const [descriptionLines, setDescriptionLines] = useState(); - const [customTitle, setCustomTitle] = useState(''); + const [customTitle, setCustomTitle] = useState(""); const [langsCount, setLangsCount] = useState(); const [showAllStats, setShowAllStats] = useState(false); const [showIcons, setShowIcons] = useState(false); @@ -76,6 +75,8 @@ const HomeScreen = ({ stage, setStage }) => { const [enableAnimations, setEnableAnimations] = useState(true); const [usePercent, setUsePercent] = useState(false); + const [theme, setTheme] = useState("default"); + const resetCustomization = () => { if (selectedCard === CardTypes.TOP_LANGS) { setLangsCount(4); @@ -91,11 +92,11 @@ const HomeScreen = ({ stage, setStage }) => { setSelectedLanguagesLayout(LANGUAGES_DEFAULT_LAYOUT); } - if (theme === 'default' || theme === 'default_repocard') { + if (theme === "default" || theme === "default_repocard") { if (selectedCard === CardTypes.PIN || selectedCard === CardTypes.GIST) { - setTheme('default_repocard'); + setTheme("default_repocard"); } else { - setTheme('default'); + setTheme("default"); } } }; @@ -104,7 +105,7 @@ const HomeScreen = ({ stage, setStage }) => { resetCustomization(); }, [selectedCard]); - let fullSuffix = `${selectedCard === CardTypes.STATS ? '' : '/' + selectedCard}?`; + let fullSuffix = `${selectedCard === CardTypes.STATS ? "" : "/" + selectedCard}?`; switch (selectedCard) { case CardTypes.STATS: @@ -148,14 +149,14 @@ const HomeScreen = ({ stage, setStage }) => { selectedCard === CardTypes.TOP_LANGS || selectedCard === CardTypes.WAKATIME) ) { - fullSuffix += '&hide_title=true'; + fullSuffix += "&hide_title=true"; } if ( showOwner && (selectedCard === CardTypes.PIN || selectedCard === CardTypes.GIST) ) { - fullSuffix += '&show_owner=true'; + fullSuffix += "&show_owner=true"; } if (descriptionLines && selectedCard === CardTypes.PIN) { @@ -204,16 +205,15 @@ const HomeScreen = ({ stage, setStage }) => { } // for stage four - const [theme, setTheme] = useState('default'); let themeSuffix = fullSuffix; if ( !( - (theme === 'default' && + (theme === "default" && [CardTypes.STATS, CardTypes.TOP_LANGS, CardTypes.WAKATIME].includes( selectedCard, )) || - (theme === 'default_repocard' && + (theme === "default_repocard" && [CardTypes.PIN, CardTypes.GIST].includes(selectedCard)) ) ) { @@ -221,7 +221,7 @@ const HomeScreen = ({ stage, setStage }) => { } // for stage five - const [gistUrl, setGistUrl] = useState(''); + const [gistUrl, setGistUrl] = useState(""); let guestHint; switch (selectedCard) { @@ -247,7 +247,7 @@ const HomeScreen = ({ stage, setStage }) => { return result.data.html_url; } catch (error) { console.error(error); - return ''; + return ""; } }; @@ -274,9 +274,9 @@ const HomeScreen = ({ stage, setStage }) => { const url = window.location.href; // If Github API returns the code parameter - if (url.includes('code=')) { - const tempPrivateAccess = url.includes('private'); - const newUrl = url.split('?code='); + if (url.includes("code=")) { + const tempPrivateAccess = url.includes("private"); + const newUrl = url.split("?code="); const redirect = `${url.split(HOST)[0]}${HOST}/frontend`; window.history.pushState({}, null, redirect); setIsLoading(true); @@ -313,11 +313,11 @@ const HomeScreen = ({ stage, setStage }) => {
{ [ - 'Login', - 'Select a Card', - 'Modify Card Parameters', - 'Choose a Theme', - 'Display your Card', + "Login", + "Select a Card", + "Modify Card Parameters", + "Choose a Theme", + "Display your Card", ][stage] }
@@ -325,7 +325,7 @@ const HomeScreen = ({ stage, setStage }) => { {stage === 0 && isAuthenticated ? ( ) : ( [ - '', - 'You will be able to customize your card in future steps.', - '', - '', - 'Display the finished card on GitHub, Twitter/X, LinkedIn, or anywhere else!', + "", + "You will be able to customize your card in future steps.", + "", + "", + "Display the finished card on GitHub, Twitter/X, LinkedIn, or anywhere else!", ][stage] )} @@ -419,6 +419,7 @@ const HomeScreen = ({ stage, setStage }) => { )} {stage === 4 && ( { switch (selectedCard) { case CardTypes.STATS: @@ -432,17 +433,20 @@ const HomeScreen = ({ stage, setStage }) => { return `${wakatimeUser}_card`; } })()} + // eslint-disable-next-line consistent-return link={(() => { switch (selectedCard) { case CardTypes.STATS: case CardTypes.TOP_LANGS: return `https://${HOST}/api${themeSuffix}`; - case CardTypes.PIN: + + case CardTypes.PIN: { let myRepo = repo; - if (!myRepo.includes('/')) { + if (!myRepo.includes("/")) { myRepo = `${userId}/${myRepo}`; } return `https://github.com/${myRepo}`; + } case CardTypes.GIST: return gistUrl; case CardTypes.WAKATIME: diff --git a/apps/frontend/src/pages/Home/index.jsx b/apps/frontend/src/pages/Home/index.jsx new file mode 100644 index 00000000..bcbb7a52 --- /dev/null +++ b/apps/frontend/src/pages/Home/index.jsx @@ -0,0 +1,3 @@ +import HomeScreen from "./Home"; + +export default HomeScreen; diff --git a/frontend/frontend/src/pages/Home/stages/Customize.js b/apps/frontend/src/pages/Home/stages/Customize.jsx similarity index 87% rename from frontend/frontend/src/pages/Home/stages/Customize.js rename to apps/frontend/src/pages/Home/stages/Customize.jsx index a5b3bfd5..4e066b86 100644 --- a/frontend/frontend/src/pages/Home/stages/Customize.js +++ b/apps/frontend/src/pages/Home/stages/Customize.jsx @@ -1,20 +1,21 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -import { CheckboxSection, Image } from '../../../components'; -import { CardTypes } from '../../../utils'; -import TextSection from '../../../components/Home/TextSection'; -import NumericSection from '../../../components/Home/NumericSection'; -import StatsRankSection from '../../../components/Home/StatsRankSection'; -import LanguagesLayoutSection from '../../../components/Home/LanguagesLayoutSection'; -import WakatimeLayoutSection from '../../../components/Home/WakatimeLayoutSection'; +import { CardTypes } from "../../../utils"; +import { Image } from "../../../components/Card/Card"; +import { CheckboxSection } from "../../../components/Home/CheckboxSection"; +import { TextSection } from "../../../components/Home/TextSection"; +import { NumericSection } from "../../../components/Home/NumericSection"; +import { StatsRankSection } from "../../../components/Home/StatsRankSection"; +import { LanguagesLayoutSection } from "../../../components/Home/LanguagesLayoutSection"; +import WakatimeLayoutSection from "../../../components/Home/WakatimeLayoutSection"; import { DEMO_GIST, DEMO_REPO, DEMO_USER, DEMO_WAKATIME_USER, -} from '../../../constants'; -import { useIsAuthenticated } from '../../../redux/selectors/userSelectors'; +} from "../../../constants"; +import { useIsAuthenticated } from "../../../redux/selectors/userSelectors"; const CustomizeStage = ({ selectedCard, @@ -70,7 +71,7 @@ const CustomizeStage = ({
{!isAuthenticated && ( <> - Please{' '} + Please{" "}
{ @@ -80,7 +81,7 @@ const CustomizeStage = ({ className="underline text-blue-900" > log in - {' '} + {" "} to change the username. )} @@ -91,14 +92,14 @@ const CustomizeStage = ({ setValue={setSelectedUserId} onPaste={(e) => { e.preventDefault(); - let newValue = e.clipboardData.getData('text'); + let newValue = e.clipboardData.getData("text"); // if the user pasted a full GitHub URL, extract username - if (newValue.endsWith('/')) { + if (newValue.endsWith("/")) { newValue = newValue.slice(0, -1); } - let parts = newValue.split('/'); + let parts = newValue.split("/"); if (parts.length > 1) { - newValue = parts.slice(-1).join('/'); + newValue = parts.slice(-1).join("/"); } setSelectedUserId(newValue); }} @@ -114,7 +115,7 @@ const CustomizeStage = ({
{!isAuthenticated && ( <> - Please{' '} + Please{" "} { @@ -124,7 +125,7 @@ const CustomizeStage = ({ className="underline text-blue-900" > log in - {' '} + {" "} to change the repo. )} @@ -135,14 +136,14 @@ const CustomizeStage = ({ setValue={setRepo} onPaste={(e) => { e.preventDefault(); - let newValue = e.clipboardData.getData('text'); + let newValue = e.clipboardData.getData("text"); // if the user pasted a full GitHub URL, extract owner/repo - if (newValue.endsWith('/')) { + if (newValue.endsWith("/")) { newValue = newValue.slice(0, -1); } - let parts = newValue.split('/'); + let parts = newValue.split("/"); if (parts.length > 2) { - newValue = parts.slice(-2).join('/'); + newValue = parts.slice(-2).join("/"); } setRepo(newValue); }} @@ -158,7 +159,7 @@ const CustomizeStage = ({
{!isAuthenticated && ( <> - Please{' '} + Please{" "} { @@ -168,7 +169,7 @@ const CustomizeStage = ({ className="underline text-blue-900" > log in - {' '} + {" "} to change the gist id. )} @@ -179,14 +180,14 @@ const CustomizeStage = ({ setValue={setGist} onPaste={(e) => { e.preventDefault(); - let newValue = e.clipboardData.getData('text'); + let newValue = e.clipboardData.getData("text"); // if the user pasted a full GitHub URL, extract Gist ID - if (newValue.endsWith('/')) { + if (newValue.endsWith("/")) { newValue = newValue.slice(0, -1); } - let parts = newValue.split('/'); + let parts = newValue.split("/"); if (parts.length > 1) { - newValue = parts.slice(-1).join('/'); + newValue = parts.slice(-1).join("/"); } setGist(newValue); }} @@ -198,14 +199,14 @@ const CustomizeStage = ({ title="WakaTime Username" description={ <> - Set your{' '} + Set your{" "} WakaTime - {' '} + {" "} username to fetch your stats. } @@ -336,14 +337,14 @@ const CustomizeStage = ({ /> )}
- For more customization options check the{' '} + For more customization options check the{" "} customization documentation - {' '} + {" "} after you copied your card URL in step 5.
@@ -396,4 +397,4 @@ CustomizeStage.propTypes = { setStage: PropTypes.func.isRequired, }; -export default CustomizeStage; +export { CustomizeStage }; diff --git a/frontend/frontend/src/pages/Home/stages/Display.js b/apps/frontend/src/pages/Home/stages/Display.jsx similarity index 69% rename from frontend/frontend/src/pages/Home/stages/Display.js rename to apps/frontend/src/pages/Home/stages/Display.jsx index c6752114..899b05fc 100644 --- a/frontend/frontend/src/pages/Home/stages/Display.js +++ b/apps/frontend/src/pages/Home/stages/Display.jsx @@ -1,23 +1,22 @@ /* eslint-disable react/no-array-index-key */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -import { toast } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; +import { toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; -import { saveSvgAsPng } from 'save-svg-as-png'; +import { saveSvgAsPng } from "save-svg-as-png"; -import { Button, Image } from '../../../components'; -import { classnames } from '../../../utils'; -import { HOST } from '../../../constants'; +import { Button } from "../../../components/Generic/Button"; +import { Image } from "../../../components/Card/Card"; +import { classnames } from "../../../utils"; +import { HOST } from "../../../constants"; const DisplayStage = ({ filename, link, themeSuffix, guestHint }) => { - const card = themeSuffix.split('?')[0]; - const downloadPNG = () => { saveSvgAsPng( - document.getElementById('svgWrapper').shadowRoot.firstElementChild + document.getElementById("svgWrapper").shadowRoot.firstElementChild .firstElementChild, `${filename}.png`, { @@ -31,8 +30,8 @@ const DisplayStage = ({ filename, link, themeSuffix, guestHint }) => { navigator.clipboard.writeText( `[![GitHub Stats](https://${HOST}/api${themeSuffix})](${link})`, ); - toast.info('Copied to Clipboard!', { - position: 'bottom-right', + toast.info("Copied to Clipboard!", { + position: "bottom-right", autoClose: 1500, hideProgressBar: true, closeOnClick: false, @@ -44,8 +43,8 @@ const DisplayStage = ({ filename, link, themeSuffix, guestHint }) => { const copyUrl = () => { navigator.clipboard.writeText(`https://${HOST}/api${themeSuffix}`); - toast.info('Copied to Clipboard!', { - position: 'bottom-right', + toast.info("Copied to Clipboard!", { + position: "bottom-right", autoClose: 1500, hideProgressBar: true, closeOnClick: false, @@ -62,20 +61,20 @@ const DisplayStage = ({ filename, link, themeSuffix, guestHint }) => {
{[ { - title: 'Copy Markdown', + title: "Copy Markdown", highlight: true, onClick: copyMarkdown, }, - { title: 'Copy URL', highlight: false, onClick: copyUrl }, - { title: 'Download PNG', highlight: false, onClick: downloadPNG }, + { title: "Copy URL", highlight: false, onClick: copyUrl }, + { title: "Download PNG", highlight: false, onClick: downloadPNG }, ].map((item, index) => (