diff --git a/.commitlintrc b/.commitlintrc new file mode 100644 index 0000000..0df1d25 --- /dev/null +++ b/.commitlintrc @@ -0,0 +1,5 @@ +{ + "extends": [ + "@commitlint/config-conventional" + ] +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..4c0576e --- /dev/null +++ b/.eslintignore @@ -0,0 +1,12 @@ +.cache/ +.lighthouseci/ +.next/ +.swc/ +.turbo/ +coverage/ +dist/ +e2e-report/ +e2e-results/ +out/ +storybook-static/ +*.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..0c77b63 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,9 @@ +{ + "root": true, + "extends": ["custom"], + "settings": { + "next": { + "rootDir": ["apps/*/"] + } + } +} diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..e91c96e --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,28 @@ +name: Setup +description: "Install required dependencies and build site" + +inputs: + node-version: + description: "Node version" + required: true + default: "18" + pnpm-version: + description: "pnpm version" + required: true + default: "8" + +runs: + using: "composite" + steps: + - name: Set up pnpm + uses: pnpm/action-setup@v2 + with: + version: ${{ inputs.pnpm-version }} + - name: Set up Node ${{ inputs.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ inputs.node-version }} + cache: "pnpm" + - name: Install dependencies + run: pnpm install + shell: bash \ No newline at end of file diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..db9bf27 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:best-practices", + "config:js-app", + "config:semverAllMonthly", + ":disableRateLimiting", + ":noUnscheduledUpdates", + ":automergeLinters", + ":automergeTesters", + ":automergeTypes", + ":automergeMinor" + ], + "timezone": "America/Los_Angeles", + "schedule": ["after 8pm every weekday", "every weekend"], + "automergeType": "pr", + "platformAutomerge": true, + "rebaseWhen": "conflicted", + "semanticCommits": "enabled", + "constraints": { + "pnpm": "8" + } +} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a725dc5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,19 @@ +name: Release + +on: + push: + branches: main + +permissions: + contents: write + pull-requests: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Release + uses: google-github-actions/release-please-action@v3 + with: + release-type: node + package-name: turbo-monorepo-template diff --git a/.github/workflows/run-lighthouse-on-vercel-preview.yml b/.github/workflows/run-lighthouse-on-vercel-preview.yml new file mode 100644 index 0000000..3fbc071 --- /dev/null +++ b/.github/workflows/run-lighthouse-on-vercel-preview.yml @@ -0,0 +1,71 @@ +name: Vercel Preview URL Lighthouse Audit + +on: + issue_comment: + types: [edited] + +jobs: + generate_lighthouse_audit: + timeout-minutes: 30 + runs-on: ubuntu-latest + steps: + - name: Add comment to PR + id: loading_comment_to_pr + uses: marocchino/sticky-pull-request-comment@v2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + number: ${{ github.event.issue.number }} + header: lighthouse + message: | + Running Lighthouse audit... + - name: Capture Vercel preview URL + id: vercel_preview_url + uses: aaimio/vercel-preview-url-action@v2.2.0 # aaron-binary/vercel-preview-url-action@v0.0.3 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v2 + - name: Audit preview URL with Lighthouse + id: lighthouse_audit + uses: treosh/lighthouse-ci-action@v10 + with: + urls: | + ${{ steps.vercel_preview_url.outputs.vercel_preview_url }} + uploadArtifacts: true + temporaryPublicStorage: true + - name: Format lighthouse score + id: format_lighthouse_score + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const result = ${{ steps.lighthouse_audit.outputs.manifest }}[0].summary + const links = ${{ steps.lighthouse_audit.outputs.links }} + + const formatResult = (res) => Math.round((res * 100)) + Object.keys(result).forEach(key => result[key] = formatResult(result[key])) + + const score = res => res >= 90 ? '๐ŸŸข' : res >= 50 ? '๐ŸŸ ' : '๐Ÿ”ด' + + const comment = [ + `โšก๏ธ [Lighthouse report](${Object.values(links)[0]}) for the changes in this PR:`, + '| Category | Score |', + '| --- | --- |', + `| ${score(result.performance)} Performance | ${result.performance} |`, + `| ${score(result.accessibility)} Accessibility | ${result.accessibility} |`, + `| ${score(result['best-practices'])} Best practices | ${result['best-practices']} |`, + `| ${score(result.seo)} SEO | ${result.seo} |`, + `| ${score(result.pwa)} PWA | ${result.pwa} |`, + ' ', + `*Lighthouse ran on [${Object.keys(links)[0]}](${Object.keys(links)[0]})*` + ].join('\n') + + core.setOutput("comment", comment); + - name: Add comment to PR + id: comment_to_pr + uses: marocchino/sticky-pull-request-comment@v2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + number: ${{ github.event.issue.number }} + header: lighthouse + message: | + ${{ steps.format_lighthouse_score.outputs.comment }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7f9e31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +.cache/ +.lighthouseci/ +.next/ +.swc/ +.turbo/ +coverage/ +dist/ +e2e-report/ +e2e-results/ +node_modules/ +out/ +storybook-static/ +.env +.env.* +!.env.example +.eslintcache +**/graph.svg + +# PWA files +**/public/sw.js +**/public/workbox-*.js +**/public/worker-*.js +**/public/sw.js.map +**/public/workbox-*.js.map +**/public/worker-*.js.map + +node_modules +.env +coverage +coverage.json +typechain +typechain-types + +# Default ignored files + +/shelf/ +/workspace.xml + +# Editor-based HTTP Client requests + +/httpRequests/ + +#Hardhat files +cache/ +artifacts/ + +polkadot-account.json diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000..31354ec --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..a483680 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +pnpm commitlint --edit ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..465ccf1 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +# pnpm lint-staged diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..25bf17f --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..84d1458 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,16 @@ +.cache/ +.lighthouseci/ +.next/ +.swc/ +.turbo/ +coverage/ +dist/ +e2e-report/ +e2e-results/ +out/ +storybook-static/ +.eslintcache +CHANGELOG.md +package-lock.json +pnpm-lock.yaml +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a43a07b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "printWidth": 100, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..7befee4 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "bradlc.vscode-tailwindcss", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..dad68f6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,30 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll": true + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.rulers": [{ "column": 100 }], + "editor.tabSize": 2, + "files.associations": { + "*.css": "tailwindcss" + }, + "files.readonlyInclude": { + ".cache/**": true, + ".next/**": true, + ".swc/**": true, + ".turbo/**": true, + "coverage/**": true, + "dist/**": true, + "e2e-report/**": true, + "e2e-results/**": true, + "node_modules/**": true, + "out/**": true, + "storybook-static/**": true, + ".eslintcache": true + }, + "javascript.preferences.importModuleSpecifier": "non-relative", + "typescript.preferences.importModuleSpecifier": "non-relative", + "typescript.tsdk": "root/node_modules/typescript/lib", + "workbench.editor.labelFormat": "medium" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2d63161 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Markkos98 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f43081c --- /dev/null +++ b/README.md @@ -0,0 +1,118 @@ +# Next.js & Tailwind CSS Monorepo Template + +This is a monorepo template using: + +- ๐Ÿ“ [TypeScript 5.0](https://www.typescriptlang.org/) +- โšก๏ธ [Next.js 13.2](https://nextjs.org/) +- โš›๏ธ [React 18.2](https://reactjs.org/) +- ๐ŸŒฌ๏ธ [Tailwind CSS 3.3](https://tailwindcss.com/) +- ๐Ÿ“• [Storybook 7.0](https://storybook.js.org/) +- ๐Ÿงช [Testing Library](https://testing-library.com/) +- ๐Ÿƒ [Jest](https://jestjs.io/) +- ๐ŸŽญ [Playwright](https://playwright.dev/) +- ๐Ÿ’ก [Lighthouse](https://developer.chrome.com/docs/lighthouse/) +- ๐Ÿงน [ESLint](https://eslint.org/) +- ๐Ÿค– [CommitLint](https://commitlint.js.org/) +- ๐Ÿ’– [Prettier](https://prettier.io/) +- ๐Ÿ“ฆ [pnpm](https://pnpm.io/) +- ๐ŸŽ๏ธ [Turborepo](https://turbo.build/repo) +- ๐Ÿ‘ท [Github Actions](https://github.com/features/actions) + +## What's inside? + +This monorepo includes a few apps and packages. + +### Apps and Packages + +- `apps/website-ssr`: a Next.js app with Tailwind CSS +- `apps/website`: another Next.js app with Tailwind CSS +- `packages/ui`: a stub React component library with Tailwind CSS, shared by both `website-ssr` and `website` apps +- `packages/utils`: utilities shared by both `website-ssr` and `website` apps +- `packages/eslint-config-custom`: shared ESLint configuration +- `packages/jest-config`: shared Jest configuration +- `packages/lighthouse-config`: shared Lighthouse configuration +- `packages/next-config`: shared Next.js configuration +- `packages/playwright-config`: shared Playwright configuration +- `packages/storybook-config`: shared Storybook configuration +- `packages/tailwindcss-config`: shared Tailwind CSS configuration +- `packages/typescript-config`: shared `tsconfig.json` files + +## Using this template + +Run the following command: + +``` +npx degit markkos89/turbo-monorepo-template my-monorepo +cd my-monorepo +pnpm install +``` + +### Develop Next.js + +If you want to start `apps/website-ssr` and `apps/website` in development mode, and watch for changes in `packages/ui`, run at the root: + +``` +pnpm dev +``` + +### Build Next.js + +If you want to build `apps/website-ssr` and `apps/website` for production, run at the root: + +``` +pnpm build +``` + +If you want to see an analysis of the generated bundles, specify the `ANALYZE` environment variable: + +``` +ANALYZE=true pnpm build +``` + +### Preview Next.js + +If you want to preview production builds of `apps/website-ssr` and `apps/website`, run at the root: + +``` +pnpm start +``` + +### Develop Storybook + +If you want to start all Storybook projects in development mode, run at the root: + +``` +pnpm storybook:dev +``` + +### Develop Storybook + +If you want to build all Storybook projects, run at the root: + +``` +pnpm storybook:build +``` + +### Unit tests + +If you want to run unit tests for all projects, run at the root: + +``` +pnpm test:unit +``` + +### End-to-end tests + +If you want to run e2e tests for all projects, run at the root: + +``` +pnpm test:e2e +``` + +### Lint + +If you want to run linting for all projects, run at the root: + +``` +pnpm lint +``` diff --git a/apps/website-ssr/.storybook/main.js b/apps/website-ssr/.storybook/main.js new file mode 100644 index 0000000..f8541a6 --- /dev/null +++ b/apps/website-ssr/.storybook/main.js @@ -0,0 +1 @@ +export { default } from "storybook-config/main.config"; diff --git a/apps/website-ssr/.storybook/manager.js b/apps/website-ssr/.storybook/manager.js new file mode 100644 index 0000000..2785f7c --- /dev/null +++ b/apps/website-ssr/.storybook/manager.js @@ -0,0 +1 @@ +import "storybook-config/manager.config"; diff --git a/apps/website-ssr/.storybook/preview.js b/apps/website-ssr/.storybook/preview.js new file mode 100644 index 0000000..cbe39e5 --- /dev/null +++ b/apps/website-ssr/.storybook/preview.js @@ -0,0 +1,3 @@ +import "../src/styles.css"; + +export { default } from "storybook-config/preview.config"; diff --git a/apps/website-ssr/e2e/index.e2e.ts b/apps/website-ssr/e2e/index.e2e.ts new file mode 100644 index 0000000..097f710 --- /dev/null +++ b/apps/website-ssr/e2e/index.e2e.ts @@ -0,0 +1,29 @@ +import { expect, test } from "@playwright/test"; + +const basePath = "/website-ssr"; + +test("has title", async ({ page }) => { + await page.goto(basePath); + + await expect(page).toHaveTitle("Turbo Monorepo โ€” Website SSR"); +}); + +test("has heading", async ({ page }) => { + await page.goto(basePath); + + const heading = page.getByRole("heading", { level: 1 }); + await expect(heading).toContainText("Website SSR"); +}); + +test("has navigations", async ({ page }) => { + await page.goto(basePath); + + const navMain = page.getByRole("navigation", { name: "main" }); + await expect(navMain).toBeVisible(); + + const navQuickLinks = page.getByRole("navigation", { name: "quick links" }); + await expect(navQuickLinks).toBeVisible(); + + const navSocial = page.getByRole("navigation", { name: "social" }); + await expect(navSocial).toBeVisible(); +}); diff --git a/apps/website-ssr/jest.config.js b/apps/website-ssr/jest.config.js new file mode 100644 index 0000000..a453a37 --- /dev/null +++ b/apps/website-ssr/jest.config.js @@ -0,0 +1 @@ +module.exports = require("jest-config/jest.config"); diff --git a/apps/website-ssr/lighthouse.config.js b/apps/website-ssr/lighthouse.config.js new file mode 100644 index 0000000..e0d0c98 --- /dev/null +++ b/apps/website-ssr/lighthouse.config.js @@ -0,0 +1,3 @@ +module.exports = require("lighthouse-config/lighthouse.config")({ + staticDistDir: "out/", +}); diff --git a/apps/website-ssr/next-env.d.ts b/apps/website-ssr/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/apps/website-ssr/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/website-ssr/next.config.js b/apps/website-ssr/next.config.js new file mode 100644 index 0000000..cedf516 --- /dev/null +++ b/apps/website-ssr/next.config.js @@ -0,0 +1,3 @@ +module.exports = require("next-config/nextpwa.config")({ + // basePath: "/monorepo-nextjs-tailwindcss-template", +}); diff --git a/apps/website-ssr/package.json b/apps/website-ssr/package.json new file mode 100644 index 0000000..87b3db1 --- /dev/null +++ b/apps/website-ssr/package.json @@ -0,0 +1,36 @@ +{ + "private": true, + "name": "website-ssr", + "version": "0.0.0", + "scripts": { + "clean": "rm -rf .lighthouseci .next .swc .turbo coverage dist e2e-report e2e-results out storybook-static", + "dev": "next dev --port 3001", + "build": "next build", + "lint": "next lint", + "test:e2e": "playwright test", + "test:unit": "jest --ci --coverage", + "test:watch": "jest --watch", + "storybook:dev": "storybook dev --quiet --port 6001", + "storybook:build": "storybook build", + "coupling-graph": "npx madge --extensions js,jsx,ts,tsx,css,md,mdx ./ --exclude '.next|tailwind.config.js|reset.d.ts|prettier.config.js|postcss.config.js|playwright.config.ts|next.config.js|next-env.d.ts|instrumentation.ts|e2e/|README.md|.storybook/|.eslintrc.js' --image graph.svg" + }, + "dependencies": { + "@headlessui/react": "1.7.15", + "clsx": "2.0.0", + "next": "13.4.12", + "next-themes": "0.2.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "ui": "workspace:*", + "utils": "workspace:*" + }, + "devDependencies": { + "jest-config": "workspace:*", + "lighthouse-config": "workspace:*", + "next-config": "workspace:*", + "playwright-config": "workspace:*", + "storybook-config": "workspace:*", + "tailwindcss-config": "workspace:*", + "typescript-config": "workspace:*" + } +} diff --git a/apps/website-ssr/playwright.config.js b/apps/website-ssr/playwright.config.js new file mode 100644 index 0000000..e689a91 --- /dev/null +++ b/apps/website-ssr/playwright.config.js @@ -0,0 +1,4 @@ +module.exports = require("playwright-config/playwright.config")({ + basePath: "/website-ssrr", + port: 3001, +}); diff --git a/apps/website-ssr/postcss.config.js b/apps/website-ssr/postcss.config.js new file mode 100644 index 0000000..faf8b59 --- /dev/null +++ b/apps/website-ssr/postcss.config.js @@ -0,0 +1 @@ +module.exports = require("tailwindcss-config/postcss.config"); diff --git a/apps/website-ssr/public/android-chrome-192x192.png b/apps/website-ssr/public/android-chrome-192x192.png new file mode 100644 index 0000000..1af7717 Binary files /dev/null and b/apps/website-ssr/public/android-chrome-192x192.png differ diff --git a/apps/website-ssr/public/android-chrome-512x512.png b/apps/website-ssr/public/android-chrome-512x512.png new file mode 100644 index 0000000..0db2bc0 Binary files /dev/null and b/apps/website-ssr/public/android-chrome-512x512.png differ diff --git a/apps/website-ssr/public/apple-touch-icon.png b/apps/website-ssr/public/apple-touch-icon.png new file mode 100644 index 0000000..67612c0 Binary files /dev/null and b/apps/website-ssr/public/apple-touch-icon.png differ diff --git a/apps/website-ssr/public/favicon-16x16.png b/apps/website-ssr/public/favicon-16x16.png new file mode 100644 index 0000000..731f179 Binary files /dev/null and b/apps/website-ssr/public/favicon-16x16.png differ diff --git a/apps/website-ssr/public/favicon-32x32.png b/apps/website-ssr/public/favicon-32x32.png new file mode 100644 index 0000000..fc8171a Binary files /dev/null and b/apps/website-ssr/public/favicon-32x32.png differ diff --git a/apps/website-ssr/public/favicon.ico b/apps/website-ssr/public/favicon.ico new file mode 100644 index 0000000..869ecff Binary files /dev/null and b/apps/website-ssr/public/favicon.ico differ diff --git a/apps/website-ssr/public/icon-192x192.png b/apps/website-ssr/public/icon-192x192.png new file mode 100644 index 0000000..b41abe2 Binary files /dev/null and b/apps/website-ssr/public/icon-192x192.png differ diff --git a/apps/website-ssr/public/icon-256x256.png b/apps/website-ssr/public/icon-256x256.png new file mode 100644 index 0000000..727d36b Binary files /dev/null and b/apps/website-ssr/public/icon-256x256.png differ diff --git a/apps/website-ssr/public/icon-384x384.png b/apps/website-ssr/public/icon-384x384.png new file mode 100644 index 0000000..34697a0 Binary files /dev/null and b/apps/website-ssr/public/icon-384x384.png differ diff --git a/apps/website-ssr/public/icon-512x512.png b/apps/website-ssr/public/icon-512x512.png new file mode 100644 index 0000000..35283fc Binary files /dev/null and b/apps/website-ssr/public/icon-512x512.png differ diff --git a/apps/website-ssr/public/manifest.json b/apps/website-ssr/public/manifest.json new file mode 100644 index 0000000..2f4e2c5 --- /dev/null +++ b/apps/website-ssr/public/manifest.json @@ -0,0 +1,38 @@ +{ + "theme_color": "#f69435", + "background_color": "#f69435", + "display": "standalone", + "scope": "/", + "start_url": "/", + "name": "Turbo Monorepo", + "short_name": "Turbo Monorepo", + "description": "Turbo Monorepo Template", + "icons": [ + { + "src": "/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icon-256x256.png", + "sizes": "256x256", + "type": "image/png" + }, + { + "src": "/icon-384x384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "maskable_icon_x48.png", + "sizes": "48x48", + "type": "image/x-icon", + "purpose": "maskable" + } + ] +} diff --git a/apps/website-ssr/public/maskable_icon.png b/apps/website-ssr/public/maskable_icon.png new file mode 100644 index 0000000..39a1e5e Binary files /dev/null and b/apps/website-ssr/public/maskable_icon.png differ diff --git a/apps/website-ssr/public/maskable_icon_x48.png b/apps/website-ssr/public/maskable_icon_x48.png new file mode 100644 index 0000000..0acce0f Binary files /dev/null and b/apps/website-ssr/public/maskable_icon_x48.png differ diff --git a/apps/website-ssr/public/site.webmanifest b/apps/website-ssr/public/site.webmanifest new file mode 100644 index 0000000..0b08af1 --- /dev/null +++ b/apps/website-ssr/public/site.webmanifest @@ -0,0 +1,11 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/apps/website-ssr/public/sw.js b/apps/website-ssr/public/sw.js new file mode 100644 index 0000000..6e4d13e --- /dev/null +++ b/apps/website-ssr/public/sw.js @@ -0,0 +1 @@ +if(!self.define){let e,s={};const n=(n,a)=>(n=new URL(n+".js",a).href,s[n]||new Promise((s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()})).then((()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didnโ€™t register its module`);return e})));self.define=(a,i)=>{const c=e||("document"in self?document.currentScript.src:"")||location.href;if(s[c])return;let t={};const o=e=>n(e,c),r={module:{uri:c},exports:t,require:o};s[c]=Promise.all(a.map((e=>r[e]||o(e)))).then((e=>(i(...e),t)))}}define(["./workbox-8c8aeaed"],(function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/XqduK89_4lNZQJJmt-UHy/_buildManifest.js",revision:"6ec61996a6fc5f3c66d326b9680adfb5"},{url:"/_next/static/XqduK89_4lNZQJJmt-UHy/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/framework-0f8888d1cd333966.js",revision:"0f8888d1cd333966"},{url:"/_next/static/chunks/main-bc98a3f10804c490.js",revision:"bc98a3f10804c490"},{url:"/_next/static/chunks/pages/404-9bf31a9d7d6ffe09.js",revision:"9bf31a9d7d6ffe09"},{url:"/_next/static/chunks/pages/DemoSSR-72a70aa4e999d9d5.js",revision:"72a70aa4e999d9d5"},{url:"/_next/static/chunks/pages/_app-f0a0b370653d1ea1.js",revision:"f0a0b370653d1ea1"},{url:"/_next/static/chunks/pages/_error-924c3a8f78a5d1e8.js",revision:"924c3a8f78a5d1e8"},{url:"/_next/static/chunks/pages/index-8b9d0f75cf1f1949.js",revision:"8b9d0f75cf1f1949"},{url:"/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js",revision:"79330112775102f91e1010318bae2bd3"},{url:"/_next/static/chunks/webpack-8fa1640cc84ba8fe.js",revision:"8fa1640cc84ba8fe"},{url:"/_next/static/css/a064722aa83e9cc8.css",revision:"a064722aa83e9cc8"},{url:"/android-chrome-192x192.png",revision:"b40b9799619c0fa0808d8270084741f9"},{url:"/android-chrome-512x512.png",revision:"5d231b9497d8e006095e889564624771"},{url:"/apple-touch-icon.png",revision:"5a43bee0c7f9fe0fbb441f4030c29004"},{url:"/favicon-16x16.png",revision:"ef99ba6888b371af7c57cd6a9e076a87"},{url:"/favicon-32x32.png",revision:"6f3b2403df7f7fb3acb6682fb9cdf84a"},{url:"/favicon.ico",revision:"88e2cc4585963c8eef22072457bc6589"},{url:"/icon-192x192.png",revision:"662bcbc3774cb22d27d1ea432f5dd82c"},{url:"/icon-256x256.png",revision:"5e57e07bfa3065b27494ccf1f59bf8c4"},{url:"/icon-384x384.png",revision:"9f5820b98c540d9368dbad764c5ac966"},{url:"/icon-512x512.png",revision:"8ed4a712e852a438cb951b28dc70008e"},{url:"/manifest.json",revision:"6fc62454361d79958dbf5c51a907e94d"},{url:"/maskable_icon.png",revision:"9554c211141fc92229b83790fda2be4d"},{url:"/maskable_icon_x48.png",revision:"8ebe5c753ae8d2069b2a082ebeacff8e"},{url:"/site.webmanifest",revision:"8fffcdf720e62d5b464f43d965beff3d"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:n,state:a})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute((({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")}),new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute((({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")}),new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute((({url:e})=>!(self.origin===e.origin)),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")})); diff --git a/apps/website-ssr/public/workbox-8c8aeaed.js b/apps/website-ssr/public/workbox-8c8aeaed.js new file mode 100644 index 0000000..1864fcd --- /dev/null +++ b/apps/website-ssr/public/workbox-8c8aeaed.js @@ -0,0 +1 @@ +define(["exports"],(function(t){"use strict";try{self["workbox:core:6.5.4"]&&_()}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:6.5.4"]&&_()}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class r{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s}setCatchHandler(t){this.catchHandler=n(t)}}class i extends r{constructor(t,e,s){super((({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)}),e,s)}}class a{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",(t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map((e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t})})));t.waitUntil(s),t.ports&&t.ports[0]&&s.then((()=>t.ports[0].postMessage(!0)))}}))}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:i}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let a=i&&i.handler;const o=t.method;if(!a&&this.i.has(o)&&(a=this.i.get(o)),!a)return;let c;try{c=a.handle({url:s,request:t,event:e,params:r})}catch(t){c=Promise.reject(t)}const h=i&&i.catchHandler;return c instanceof Promise&&(this.o||h)&&(c=c.catch((async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:r})}catch(t){t instanceof Error&&(n=t)}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n}))),c}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const r=this.t.get(s.method)||[];for(const i of r){let r;const a=i.match({url:t,sameOrigin:e,request:s,event:n});if(a)return r=a,(Array.isArray(r)&&0===r.length||a.constructor===Object&&0===Object.keys(a).length||"boolean"==typeof a)&&(r=void 0),{route:i,params:r}}return{}}setDefaultHandler(t,e="GET"){this.i.set(e,n(t))}setCatchHandler(t){this.o=n(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1)}}let o;const c=()=>(o||(o=new a,o.addFetchListener(),o.addCacheListener()),o);function h(t,e,n){let a;if("string"==typeof t){const s=new URL(t,location.href);a=new r((({url:t})=>t.href===s.href),e,n)}else if(t instanceof RegExp)a=new i(t,e,n);else if("function"==typeof t)a=new r(t,e,n);else{if(!(t instanceof r))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});a=t}return c().registerRoute(a),a}try{self["workbox:strategies:6.5.4"]&&_()}catch(t){}const u={cacheWillUpdate:async({response:t})=>200===t.status||0===t.status?t:null},l={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},f=t=>[l.prefix,t,l.suffix].filter((t=>t&&t.length>0)).join("-"),w=t=>t||f(l.precache),d=t=>t||f(l.runtime);function p(t,e){const s=new URL(t);for(const t of e)s.searchParams.delete(t);return s.href}class y{constructor(){this.promise=new Promise(((t,e)=>{this.resolve=t,this.reject=e}))}}const g=new Set;function m(t){return"string"==typeof t?new Request(t):t}class R{constructor(t,e){this.h={},Object.assign(this,e),this.event=e.event,this.u=t,this.l=new y,this.p=[],this.g=[...t.plugins],this.m=new Map;for(const t of this.g)this.m.set(t,{});this.event.waitUntil(this.l.promise)}async fetch(t){const{event:e}=this;let n=m(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t}const r=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e})}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message})}const i=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.u.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:i,response:t});return t}catch(t){throw r&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:r.clone(),request:i.clone()}),t}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e}async cacheMatch(t){const e=m(t);let s;const{cacheName:n,matchOptions:r}=this.u,i=await this.getCacheKey(e,"read"),a=Object.assign(Object.assign({},r),{cacheName:n});s=await caches.match(i,a);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:r,cachedResponse:s,request:i,event:this.event})||void 0;return s}async cachePut(t,e){const n=m(t);var r;await(r=0,new Promise((t=>setTimeout(t,r))));const i=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(a=i.url,new URL(String(a),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var a;const o=await this.R(e);if(!o)return!1;const{cacheName:c,matchOptions:h}=this.u,u=await self.caches.open(c),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const r=p(e.url,s);if(e.url===r)return t.match(e,n);const i=Object.assign(Object.assign({},n),{ignoreSearch:!0}),a=await t.keys(e,i);for(const e of a)if(r===p(e.url,s))return t.match(e,n)}(u,i.clone(),["__WB_REVISION__"],h):null;try{await u.put(i,l?o.clone():o)}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of g)await t()}(),t}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:c,oldResponse:f,newResponse:o.clone(),request:i,event:this.event});return!0}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.h[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=m(await t({mode:e,request:n,event:this.event,params:this.params}));this.h[s]=n}return this.h[s]}hasCallback(t){for(const e of this.u.plugins)if(t in e)return!0;return!1}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e)}*iterateCallbacks(t){for(const e of this.u.plugins)if("function"==typeof e[t]){const s=this.m.get(e),n=n=>{const r=Object.assign(Object.assign({},n),{state:s});return e[t](r)};yield n}}waitUntil(t){return this.p.push(t),t}async doneWaiting(){let t;for(;t=this.p.shift();)await t}destroy(){this.l.resolve(null)}async R(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e}}class v{constructor(t={}){this.cacheName=d(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions}handle(t){const[e]=this.handleAll(t);return e}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,r=new R(this,{event:e,request:s,params:n}),i=this.v(r,s,e);return[i,this.q(i,r,s,e)]}async v(t,e,n){let r;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(r=await this.D(e,t),!r||"error"===r.type)throw new s("no-response",{url:e.url})}catch(s){if(s instanceof Error)for(const i of t.iterateCallbacks("handlerDidError"))if(r=await i({error:s,event:n,request:e}),r)break;if(!r)throw s}for(const s of t.iterateCallbacks("handlerWillRespond"))r=await s({event:n,request:e,response:r});return r}async q(t,e,s,n){let r,i;try{r=await t}catch(i){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:r}),await e.doneWaiting()}catch(t){t instanceof Error&&(i=t)}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:r,error:i}),e.destroy(),i)throw i}}function b(t){t.then((()=>{}))}function q(){return q=Object.assign?Object.assign.bind():function(t){for(var e=1;ee.some((e=>t instanceof e));let U,x;const L=new WeakMap,I=new WeakMap,C=new WeakMap,E=new WeakMap,N=new WeakMap;let O={get(t,e,s){if(t instanceof IDBTransaction){if("done"===e)return I.get(t);if("objectStoreNames"===e)return t.objectStoreNames||C.get(t);if("store"===e)return s.objectStoreNames[1]?void 0:s.objectStore(s.objectStoreNames[0])}return B(t[e])},set:(t,e,s)=>(t[e]=s,!0),has:(t,e)=>t instanceof IDBTransaction&&("done"===e||"store"===e)||e in t};function T(t){return t!==IDBDatabase.prototype.transaction||"objectStoreNames"in IDBTransaction.prototype?(x||(x=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(t)?function(...e){return t.apply(P(this),e),B(L.get(this))}:function(...e){return B(t.apply(P(this),e))}:function(e,...s){const n=t.call(P(this),e,...s);return C.set(n,e.sort?e.sort():[e]),B(n)}}function k(t){return"function"==typeof t?T(t):(t instanceof IDBTransaction&&function(t){if(I.has(t))return;const e=new Promise(((e,s)=>{const n=()=>{t.removeEventListener("complete",r),t.removeEventListener("error",i),t.removeEventListener("abort",i)},r=()=>{e(),n()},i=()=>{s(t.error||new DOMException("AbortError","AbortError")),n()};t.addEventListener("complete",r),t.addEventListener("error",i),t.addEventListener("abort",i)}));I.set(t,e)}(t),D(t,U||(U=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction]))?new Proxy(t,O):t)}function B(t){if(t instanceof IDBRequest)return function(t){const e=new Promise(((e,s)=>{const n=()=>{t.removeEventListener("success",r),t.removeEventListener("error",i)},r=()=>{e(B(t.result)),n()},i=()=>{s(t.error),n()};t.addEventListener("success",r),t.addEventListener("error",i)}));return e.then((e=>{e instanceof IDBCursor&&L.set(e,t)})).catch((()=>{})),N.set(e,t),e}(t);if(E.has(t))return E.get(t);const e=k(t);return e!==t&&(E.set(t,e),N.set(e,t)),e}const P=t=>N.get(t);const M=["get","getKey","getAll","getAllKeys","count"],W=["put","add","delete","clear"],j=new Map;function S(t,e){if(!(t instanceof IDBDatabase)||e in t||"string"!=typeof e)return;if(j.get(e))return j.get(e);const s=e.replace(/FromIndex$/,""),n=e!==s,r=W.includes(s);if(!(s in(n?IDBIndex:IDBObjectStore).prototype)||!r&&!M.includes(s))return;const i=async function(t,...e){const i=this.transaction(t,r?"readwrite":"readonly");let a=i.store;return n&&(a=a.index(e.shift())),(await Promise.all([a[s](...e),r&&i.done]))[0]};return j.set(e,i),i}O=(t=>q({},t,{get:(e,s,n)=>S(e,s)||t.get(e,s,n),has:(e,s)=>!!S(e,s)||t.has(e,s)}))(O);try{self["workbox:expiration:6.5.4"]&&_()}catch(t){}const K="cache-entries",A=t=>{const e=new URL(t,location.href);return e.hash="",e.href};class F{constructor(t){this.U=null,this._=t}L(t){const e=t.createObjectStore(K,{keyPath:"id"});e.createIndex("cacheName","cacheName",{unique:!1}),e.createIndex("timestamp","timestamp",{unique:!1})}I(t){this.L(t),this._&&function(t,{blocked:e}={}){const s=indexedDB.deleteDatabase(t);e&&s.addEventListener("blocked",(t=>e(t.oldVersion,t))),B(s).then((()=>{}))}(this._)}async setTimestamp(t,e){const s={url:t=A(t),timestamp:e,cacheName:this._,id:this.C(t)},n=(await this.getDb()).transaction(K,"readwrite",{durability:"relaxed"});await n.store.put(s),await n.done}async getTimestamp(t){const e=await this.getDb(),s=await e.get(K,this.C(t));return null==s?void 0:s.timestamp}async expireEntries(t,e){const s=await this.getDb();let n=await s.transaction(K).store.index("timestamp").openCursor(null,"prev");const r=[];let i=0;for(;n;){const s=n.value;s.cacheName===this._&&(t&&s.timestamp=e?r.push(n.value):i++),n=await n.continue()}const a=[];for(const t of r)await s.delete(K,t.id),a.push(t.url);return a}C(t){return this._+"|"+A(t)}async getDb(){return this.U||(this.U=await function(t,e,{blocked:s,upgrade:n,blocking:r,terminated:i}={}){const a=indexedDB.open(t,e),o=B(a);return n&&a.addEventListener("upgradeneeded",(t=>{n(B(a.result),t.oldVersion,t.newVersion,B(a.transaction),t)})),s&&a.addEventListener("blocked",(t=>s(t.oldVersion,t.newVersion,t))),o.then((t=>{i&&t.addEventListener("close",(()=>i())),r&&t.addEventListener("versionchange",(t=>r(t.oldVersion,t.newVersion,t)))})).catch((()=>{})),o}("workbox-expiration",1,{upgrade:this.I.bind(this)})),this.U}}class H{constructor(t,e={}){this.N=!1,this.O=!1,this.T=e.maxEntries,this.k=e.maxAgeSeconds,this.B=e.matchOptions,this._=t,this.P=new F(t)}async expireEntries(){if(this.N)return void(this.O=!0);this.N=!0;const t=this.k?Date.now()-1e3*this.k:0,e=await this.P.expireEntries(t,this.T),s=await self.caches.open(this._);for(const t of e)await s.delete(t,this.B);this.N=!1,this.O&&(this.O=!1,b(this.expireEntries()))}async updateTimestamp(t){await this.P.setTimestamp(t,Date.now())}async isURLExpired(t){if(this.k){const e=await this.P.getTimestamp(t),s=Date.now()-1e3*this.k;return void 0===e||er||e&&e<0)throw new s("range-not-satisfiable",{size:r,end:n,start:e});let i,a;return void 0!==e&&void 0!==n?(i=e,a=n+1):void 0!==e&&void 0===n?(i=e,a=r):void 0!==n&&void 0===e&&(i=r-n,a=r),{start:i,end:a}}(i,r.start,r.end),o=i.slice(a.start,a.end),c=o.size,h=new Response(o,{status:206,statusText:"Partial Content",headers:e.headers});return h.headers.set("Content-Length",String(c)),h.headers.set("Content-Range",`bytes ${a.start}-${a.end-1}/${i.size}`),h}catch(t){return new Response("",{status:416,statusText:"Range Not Satisfiable"})}}function z(t,e){const s=e();return t.waitUntil(s),s}try{self["workbox:precaching:6.5.4"]&&_()}catch(t){}function G(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const r=new URL(n,location.href),i=new URL(n,location.href);return r.searchParams.set("__WB_REVISION__",e),{cacheKey:r.href,url:i.href}}class V{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t)},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t)}return s}}}class J{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.M.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t},this.M=t}}let Q,X;async function Y(t,e){let n=null;if(t.url){n=new URL(t.url).origin}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const r=t.clone(),i={headers:new Headers(r.headers),status:r.status,statusText:r.statusText},a=e?e(i):i,o=function(){if(void 0===Q){const t=new Response("");if("body"in t)try{new Response(t.body),Q=!0}catch(t){Q=!1}Q=!1}return Q}()?r.body:await r.blob();return new Response(o,a)}class Z extends v{constructor(t={}){t.cacheName=w(t.cacheName),super(t),this.W=!1!==t.fallbackToNetwork,this.plugins.push(Z.copyRedirectedCacheableResponsesPlugin)}async D(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.j(t,e):await this.S(t,e))}async S(t,e){let n;const r=e.params||{};if(!this.W)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=r.integrity,i=t.integrity,a=!i||i===s;n=await e.fetch(new Request(t,{integrity:"no-cors"!==t.mode?i||s:void 0})),s&&a&&"no-cors"!==t.mode&&(this.K(),await e.cachePut(t,n.clone()))}return n}async j(t,e){this.K();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n}K(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==Z.copyRedirectedCacheableResponsesPlugin&&(n===Z.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(Z.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1)}}Z.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},Z.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await Y(t):t};class tt{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.A=new Map,this.F=new Map,this.H=new Map,this.u=new Z({cacheName:w(t),plugins:[...e,new J({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.u}precache(t){this.addToCacheList(t),this.$||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.$=!0)}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:r}=G(n),i="string"!=typeof n&&n.revision?"reload":"default";if(this.A.has(r)&&this.A.get(r)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.A.get(r),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.H.has(t)&&this.H.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:r});this.H.set(t,n.integrity)}if(this.A.set(r,t),this.F.set(r,i),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}install(t){return z(t,(async()=>{const e=new V;this.strategy.plugins.push(e);for(const[e,s]of this.A){const n=this.H.get(s),r=this.F.get(e),i=new Request(e,{integrity:n,cache:r,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:i,event:t}))}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n}}))}activate(t){return z(t,(async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.A.values()),n=[];for(const r of e)s.has(r.url)||(await t.delete(r),n.push(r.url));return{deletedURLs:n}}))}getURLsToCacheKeys(){return this.A}getCachedURLs(){return[...this.A.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.A.get(e.href)}getIntegrityForCacheKey(t){return this.H.get(t)}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s)}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s))}}const et=()=>(X||(X=new tt),X);class st extends r{constructor(t,e){super((({request:s})=>{const n=t.getURLsToCacheKeys();for(const r of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:r}={}){const i=new URL(t,location.href);i.hash="",yield i.href;const a=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some((t=>t.test(s)))&&t.searchParams.delete(s);return t}(i,e);if(yield a.href,s&&a.pathname.endsWith("/")){const t=new URL(a.href);t.pathname+=s,yield t.href}if(n){const t=new URL(a.href);t.pathname+=".html",yield t.href}if(r){const t=r({url:i});for(const e of t)yield e.href}}(s.url,e)){const e=n.get(r);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)}}}}),t.strategy)}}t.CacheFirst=class extends v{async D(t,e){let n,r=await e.cacheMatch(t);if(!r)try{r=await e.fetchAndCachePut(t)}catch(t){t instanceof Error&&(n=t)}if(!r)throw new s("no-response",{url:t.url,error:n});return r}},t.ExpirationPlugin=class{constructor(t={}){this.cachedResponseWillBeUsed=async({event:t,request:e,cacheName:s,cachedResponse:n})=>{if(!n)return null;const r=this.G(n),i=this.V(s);b(i.expireEntries());const a=i.updateTimestamp(e.url);if(t)try{t.waitUntil(a)}catch(t){}return r?n:null},this.cacheDidUpdate=async({cacheName:t,request:e})=>{const s=this.V(t);await s.updateTimestamp(e.url),await s.expireEntries()},this.J=t,this.k=t.maxAgeSeconds,this.X=new Map,t.purgeOnQuotaError&&function(t){g.add(t)}((()=>this.deleteCacheAndMetadata()))}V(t){if(t===d())throw new s("expire-custom-caches-only");let e=this.X.get(t);return e||(e=new H(t,this.J),this.X.set(t,e)),e}G(t){if(!this.k)return!0;const e=this.Y(t);if(null===e)return!0;return e>=Date.now()-1e3*this.k}Y(t){if(!t.headers.has("date"))return null;const e=t.headers.get("date"),s=new Date(e).getTime();return isNaN(s)?null:s}async deleteCacheAndMetadata(){for(const[t,e]of this.X)await self.caches.delete(t),await e.delete();this.X=new Map}},t.NetworkFirst=class extends v{constructor(t={}){super(t),this.plugins.some((t=>"cacheWillUpdate"in t))||this.plugins.unshift(u),this.Z=t.networkTimeoutSeconds||0}async D(t,e){const n=[],r=[];let i;if(this.Z){const{id:s,promise:a}=this.tt({request:t,logs:n,handler:e});i=s,r.push(a)}const a=this.et({timeoutId:i,request:t,logs:n,handler:e});r.push(a);const o=await e.waitUntil((async()=>await e.waitUntil(Promise.race(r))||await a)());if(!o)throw new s("no-response",{url:t.url});return o}tt({request:t,logs:e,handler:s}){let n;return{promise:new Promise((e=>{n=setTimeout((async()=>{e(await s.cacheMatch(t))}),1e3*this.Z)})),id:n}}async et({timeoutId:t,request:e,logs:s,handler:n}){let r,i;try{i=await n.fetchAndCachePut(e)}catch(t){t instanceof Error&&(r=t)}return t&&clearTimeout(t),!r&&i||(i=await n.cacheMatch(e)),i}},t.RangeRequestsPlugin=class{constructor(){this.cachedResponseWillBeUsed=async({request:t,cachedResponse:e})=>e&&t.headers.has("range")?await $(t,e):e}},t.StaleWhileRevalidate=class extends v{constructor(t={}){super(t),this.plugins.some((t=>"cacheWillUpdate"in t))||this.plugins.unshift(u)}async D(t,e){const n=e.fetchAndCachePut(t).catch((()=>{}));e.waitUntil(n);let r,i=await e.cacheMatch(t);if(i);else try{i=await n}catch(t){t instanceof Error&&(r=t)}if(!i)throw new s("no-response",{url:t.url,error:r});return i}},t.cleanupOutdatedCaches=function(){self.addEventListener("activate",(t=>{const e=w();t.waitUntil((async(t,e="-precache-")=>{const s=(await self.caches.keys()).filter((s=>s.includes(e)&&s.includes(self.registration.scope)&&s!==t));return await Promise.all(s.map((t=>self.caches.delete(t)))),s})(e).then((t=>{})))}))},t.clientsClaim=function(){self.addEventListener("activate",(()=>self.clients.claim()))},t.precacheAndRoute=function(t,e){!function(t){et().precache(t)}(t),function(t){const e=et();h(new st(e,t))}(e)},t.registerRoute=h})); diff --git a/apps/website-ssr/src/README.md b/apps/website-ssr/src/README.md new file mode 100644 index 0000000..00e7ff3 --- /dev/null +++ b/apps/website-ssr/src/README.md @@ -0,0 +1,53 @@ +# QR Code Generator + +A QR code generator that is fully open source and built with Next.js, TypeScript, and Tailwind. The generator is designed to be fast, easy to use, and does not require users to sign in or create an account. + +[Live Demo](https://qrcode.yaoss-collection.com) + +![screenshot](/screenshot.png) + +## Features + +- Generate QR codes for text, URLs, phone numbers, SMS, email addresses, and more +- Customize QR code colors, size, and margin +- Supports generating codes for WiFi network information, such as SSID, password, and encryption type (coming soon) +- Provides a free API for generating QR codes (coming soon) + +## Installation + +To run the project locally, you will need to have Node.js and npm installed. Once you have those installed, follow these steps: + +1. Clone this repository: + +```git clone https://github.com/yaoss-collection/qrcodegenerator-frontend-nextjs.git``` + +2. Navigate into the project directory: + +```cd qrcodegenerator-frontend-nextjs``` + +3. Install the dependencies: + +```npm install``` + +4. Start the development server: + +```npm run dev``` + + +The QR code generator should now be running at http://localhost:3000. + +## Contributing + +Contributions are welcome! To contribute to the project, please follow these steps: + +1. Fork this repository. +2. Create a new branch for your feature or bug fix. +3. Make your changes and commit them with a descriptive commit message. +4. Push your changes to your forked repository. +5. Submit a pull request to this repository. + +Please make sure to include tests for any new features or bug fixes. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. diff --git a/apps/website-ssr/src/api/getGithubData.ts b/apps/website-ssr/src/api/getGithubData.ts new file mode 100644 index 0000000..b5ffb1b --- /dev/null +++ b/apps/website-ssr/src/api/getGithubData.ts @@ -0,0 +1,25 @@ +import { graphFetchApi } from '@/lib/query'; + +export const getGithubData = async () => { + try { + const query = ` + query { + repository(owner: "yaoss-collection", name: "yaoss-qrcodegenerator-frontend") { + stargazerCount + } + } + `; + + const data = await graphFetchApi.post<{ repository: { stargazerCount: number } }>({ + query, + github: true, + auth: true, + token: process.env.GITHUB_TOKEN, + }); + + return data.repository.stargazerCount; + } catch (error) { + console.error(error); + return null; + } +}; diff --git a/apps/website-ssr/src/components/Footer.tsx b/apps/website-ssr/src/components/Footer.tsx new file mode 100644 index 0000000..b744684 --- /dev/null +++ b/apps/website-ssr/src/components/Footer.tsx @@ -0,0 +1,14 @@ +import type { FunctionComponent } from "react"; +import { Footer } from "ui"; + +const links = [ + { children: "Features", href: "#features", variant: "text" }, + { children: "Testimonials", href: "#testimonials", variant: "text" }, + { children: "Pricing", href: "#pricing", variant: "text" }, +] as const; + +const PageFooter: FunctionComponent = () => { + return