commit 3ae6d2fac31e352d8dda82db9df35fbb3641bea2 Author: Théo LUDWIG Date: Wed Jul 24 12:35:33 2024 +0200 chore: initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b5a934d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,38 @@ +**/.git +**/.turbo +**/.next +**/out +**/dist +**/build +**/storybook-static +**/coverage +**/node_modules + +# envs +.env +.env.production +.env.development +secrets + +# IDEs and editors +.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace +.vscode + +# misc +.DS_Store +*.pem +Dockerfile +README.md + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2d351db --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# https://editorconfig.org/ + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5610125 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +WEBSITE_PORT=5000 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 0000000..135ae5b --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,33 @@ +name: "Chromatic" + +on: + push: + branches: [develop] + pull_request: + branches: [develop, staging, main] + +jobs: + chromatic: + timeout-minutes: 30 + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4.1.7" + with: + fetch-depth: 0 + + - uses: "pnpm/action-setup@v4.0.0" + + - name: "Setup Node.js" + uses: "actions/setup-node@v4.0.3" + with: + node-version: "22.x" + cache: "pnpm" + + - name: "Install dependencies" + run: "pnpm install --frozen-lockfile" + + - name: "Run Chromatic" + uses: "chromaui/action@latest" + with: + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + workingDir: "apps/storybook" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d2e897b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: "CI" + +on: + push: + branches: [develop] + pull_request: + branches: [develop, staging, main] + +jobs: + ci: + timeout-minutes: 30 + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4.1.7" + + - uses: "pnpm/action-setup@v4.0.0" + + - name: "Setup Node.js" + uses: "actions/setup-node@v4.0.3" + with: + node-version: "22.x" + cache: "pnpm" + + - name: "Install dependencies" + run: "pnpm install --frozen-lockfile" + + # - name: "Install Playwright" + # run: "pnpm exec playwright install --with-deps" + + - run: "node --run lint:editorconfig" + - run: "node --run lint:prettier" + - run: "node --run lint:eslint" + - run: "node --run lint:typescript" + - run: "node --run test" + - run: "node --run build" + + commitlint: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4.1.7" + + - uses: "wagoid/commitlint-github-action@v6.0.1" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6001b48 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,48 @@ +name: "Release" + +on: + push: + branches: [staging] + # branches: [main, staging] + +jobs: + release: + timeout-minutes: 30 + runs-on: "ubuntu-latest" + permissions: + contents: "write" + issues: "write" + pull-requests: "write" + id-token: "write" + steps: + - uses: "actions/checkout@v4.1.7" + with: + fetch-depth: 0 + persist-credentials: false + + - name: "Import GPG key" + uses: "crazy-max/ghaction-import-gpg@v6.0.0" + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + git_user_signingkey: true + git_commit_gpgsign: true + + - uses: "pnpm/action-setup@v4.0.0" + + - name: "Setup Node.js" + uses: "actions/setup-node@v4.0.3" + with: + node-version: "22.x" + cache: "pnpm" + + - name: "Install dependencies" + run: "pnpm install --frozen-lockfile" + + - name: "Release" + run: "node --run release" + env: + GH_URL: ${{ secrets.GH_URL }} + GH_PREFIX: ${{ secrets.GH_PREFIX }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + GIT_COMMITTER_NAME: ${{ secrets.GIT_NAME }} + GIT_COMMITTER_EMAIL: ${{ secrets.GIT_EMAIL }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8cc858 --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# dependencies +node_modules +.npm +package-lock.json +.pnpm-store +.pnp +.pnp.js +.yarn/install-state.gz + +# testing +coverage + +# production +.next/ +out/ +dist/ +build/ + +# misc +.DS_Store +*.pem +.turbo +bin/ + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Storybook +*storybook.log +storybook-static + +# IDEs and editors +.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# local env files +.env +.env.production +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +# next-env.d.ts diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1dab4ed --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact = true diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100755 index 0000000..30351be --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,13 @@ +{ + "semi": false, + "plugins": ["prettier-plugin-tailwindcss"], + "tailwindFunctions": ["classNames", "cva"], + "overrides": [ + { + "files": "pnpm-lock.yaml", + "options": { + "rangeEnd": 0 + } + } + ] +} diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..14d0ed5 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,40 @@ +{ + "branches": ["main", { "name": "staging", "prerelease": true }], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/exec", + { + "prepareCmd": "replace-in-files --regex='version\": *\"[^\"]*' --replacement='\"version\": \"${nextRelease.version}\"' '**/package.json' '!**/node_modules/**'" + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "package.json", + "apps/*/package.json", + "packages/*/package.json" + ], + "message": "chore(release): ${nextRelease.version} [skip ci]" + } + ], + [ + "@semantic-release/github", + { + "successComment": false, + "failComment": false + } + ], + [ + "@saithodev/semantic-release-backmerge", + { + "branches": [ + { "from": "main", "to": "develop" }, + { "from": "staging", "to": "develop" } + ] + } + ] + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..d4dcf73 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "Vercel.turbo-vsc", + "editorconfig.editorconfig", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "bradlc.vscode-tailwindcss", + "mikestead.dotenv", + "ms-azuretools.vscode-docker" + ] +} diff --git a/.vscode/react.code-snippets b/.vscode/react.code-snippets new file mode 100644 index 0000000..1a4e2c2 --- /dev/null +++ b/.vscode/react.code-snippets @@ -0,0 +1,43 @@ +{ + "React Component": { + "scope": "typescriptreact", + "prefix": "rfc", + "body": [ + "interface ${1:ComponentName}Props {}", + "", + "export const ${1:ComponentName}: React.FC<${1:ComponentName}Props> = () => {", + " return (", + "
", + "

${1:ComponentName}

", + "
", + " )", + "}", + "", + ], + "description": "React Component", + }, + "React Component Story": { + "scope": "typescriptreact", + "prefix": "rfcs", + "body": [ + "import type { Meta, StoryObj } from \"@storybook/react\"", + "", + "import { ${1:ComponentName} as ${1:ComponentName}Component } from \"./${1:ComponentName}\"", + "", + "const meta = {", + " title: \"${1:ComponentName}\",", + " component: ${1:ComponentName}Component", + "} satisfies Meta", + "", + "export default meta", + "", + "type Story = StoryObj", + "", + "export const ${1:ComponentName}: Story = {", + " args: {}", + "}", + "", + ], + "description": "React Component Story", + }, +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f6ffb0a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "editor.bracketPairColorization.enabled": true, + "editor.wordWrap": "on", + "prettier.configPath": ".prettierrc.json", + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "eslint.options": { + "ignorePath": ".gitignore" + }, + "prettier.ignorePath": ".gitignore", + "tailwindCSS.experimental.classRegex": [ + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..54e2fb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) Théo LUDWIG + +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..121eeda --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Wikipedia Game Solver + +> \[!IMPORTANT\] +> This project is a work in progress, at an early stage of development. + +## About + +The Wikipedia Game involves players competing to navigate from one [Wikipedia](https://en.wikipedia.org/) page to another using only internal links. + +[**Wikipedia Game Solver**](https://wikipedia-game-solver.theoludwig.fr) is a tool that helps you find the shortest path between two Wikipedia pages, using only internal links, basically solving the Wikipedia Game for you. + +Available online: + +> \[!NOTE\] +> The project is also a way to learn and experiment with a monorepo architecture, with [Turborepo](https://turbo.build/repo), and [TypeScript](https://www.typescriptlang.org/) as the main language. +> +> The project setup **can be used as a template/boilerplate for new projects**. + +## Getting Started + +### Prerequisites + +- [Node.js](https://nodejs.org/) >= 22.0.0 +- [pnpm](https://pnpm.io/) >= 9.5.0 + +### Installation + +```sh +# Go to the project root +cd wikipedia-game-solver + +# Configure environment variables +cp .env.example .env +cp apps/website/.env.example apps/website/.env + +# Install dependencies +pnpm install --frozen-lockfile + +# Install Playwright browser binaries and their dependencies (tests) +pnpm exec playwright install --with-deps +``` + +### Development + +```sh +# Start the development server +node --run dev + +# Lint +node --run lint:editorconfig +node --run lint:prettier +node --run lint:eslint +node --run lint:typescript + +# Tests +node --run test + +# Build +node --run build +``` + +### Production environment with [Docker](https://www.docker.com/) + +```sh +# Setup and run all the services for you +docker compose up --build +``` + +#### Services started + +`wikipedia-game-solver`: + +## License + +[MIT](./LICENSE) diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..a329339 --- /dev/null +++ b/TODO.md @@ -0,0 +1,20 @@ +# TODO + +- [x] chore: initial commit (+ mirror on GitHub) +- [x] Deploy first staging version (v1.0.0-staging.1) +- [ ] Add docs to add locale/edit translations, create component, install a dependency in a package, create a new package, technology used, architecture, links where it's deployed, how to use/install for end users, how to update dependencies with `npx taze -l` etc. +- [ ] Implement Wikipedia Game Solver (`website`) with inputs, button to submit, and list all articles to go from one to another, or none if it is not possible +- [ ] v1.0.0-staging.2 +- [ ] Implement CLI (`cli`) +- [ ] v1.0.0-staging.3 +- [ ] Implement REST API (`api`) with JSON responses ([AdonisJS](https://adonisjs.com/)) +- [ ] v1.0.0-staging.4 +- [ ] v1.0.0 + +## Links + +- +- How to get all URLs in a Wikipedia page: +- +- [YouTube (Amixem) - WIKIPEDIA CHALLENGE ! (ce jeu est génial)](https://www.youtube.com/watch?v=wgKlFNGU174) +- [YouTube (adumb) - I Made a Graph of Wikipedia... This Is What I Found](https://www.youtube.com/watch?v=JheGL6uSF-4) diff --git a/apps/cli/.eslintrc.json b/apps/cli/.eslintrc.json new file mode 100644 index 0000000..eafc2e9 --- /dev/null +++ b/apps/cli/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "root": true, + "extends": ["@repo/eslint-config"], + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "plugins": ["@typescript-eslint"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": true + } + } + ] +} diff --git a/apps/cli/package.json b/apps/cli/package.json new file mode 100644 index 0000000..b131cb0 --- /dev/null +++ b/apps/cli/package.json @@ -0,0 +1,31 @@ +{ + "name": "@repo/cli", + "version": "0.0.1", + "private": true, + "type": "module", + "imports": { + "#*": "./src/*" + }, + "bin": { + "wikipedia-game-solver": "./src/index.ts" + }, + "scripts": { + "start": "node --import=tsx ./src/index.ts", + "dev": "node --import=tsx --watch --watch-preserve-output ./src/index.ts", + "lint:eslint": "eslint src --max-warnings 0 --report-unused-disable-directives", + "lint:typescript": "tsc --noEmit" + }, + "dependencies": { + "@repo/wikipedia-game-solver": "workspace:*", + "@repo/constants": "workspace:*", + "tsx": "catalog:" + }, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@repo/config-typescript": "workspace:*", + "@types/node": "catalog:", + "@total-typescript/ts-reset": "catalog:", + "eslint": "catalog:", + "typescript": "catalog:" + } +} diff --git a/apps/cli/src/abc/def/add.ts b/apps/cli/src/abc/def/add.ts new file mode 100644 index 0000000..e1c0c50 --- /dev/null +++ b/apps/cli/src/abc/def/add.ts @@ -0,0 +1,3 @@ +export const add = (a: number, b: number): number => { + return a + b +} diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts new file mode 100755 index 0000000..2cacb3e --- /dev/null +++ b/apps/cli/src/index.ts @@ -0,0 +1,11 @@ +#!/usr/bin/env -S node --import=tsx + +import { add } from "#abc/def/add.js" + +import { VERSION } from "@repo/constants" +import { sum } from "@repo/wikipedia-game-solver/wikipedia-api" + +console.log("Hello, world!") +console.log(sum(1, 2)) +console.log(add(2, 3)) +console.log(`v${VERSION}`) diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json new file mode 100644 index 0000000..a1a81bd --- /dev/null +++ b/apps/cli/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@repo/config-typescript/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "resolveJsonModule": true, + "lib": ["ESNext"], + "types": ["@total-typescript/ts-reset", "@types/node"], + + "noEmit": true + } +} diff --git a/apps/storybook/.eslintrc.json b/apps/storybook/.eslintrc.json new file mode 100644 index 0000000..42c084e --- /dev/null +++ b/apps/storybook/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "root": true, + "extends": ["@repo/eslint-config"] +} diff --git a/apps/storybook/.storybook/main.ts b/apps/storybook/.storybook/main.ts new file mode 100644 index 0000000..d6dee7e --- /dev/null +++ b/apps/storybook/.storybook/main.ts @@ -0,0 +1,31 @@ +import type { StorybookConfig } from "@storybook/nextjs" + +const config: StorybookConfig = { + core: { + disableTelemetry: true, + }, + docs: { + defaultName: "Documentation", + }, + stories: ["../../../packages/**/*.stories.tsx", "../stories/*.mdx"], + addons: [ + "@storybook/addon-essentials", + "@storybook/addon-storysource", + "@storybook/addon-a11y", + "@storybook/addon-links", + "@chromatic-com/storybook", + "@storybook/addon-interactions", + "storybook-dark-mode", + ], + framework: { + name: "@storybook/nextjs", + options: {}, + }, + typescript: { + check: false, + reactDocgen: "react-docgen-typescript", + }, + staticDirs: ["../../website/public"], +} + +export default config diff --git a/apps/storybook/.storybook/preview.tsx b/apps/storybook/.storybook/preview.tsx new file mode 100644 index 0000000..645bbfb --- /dev/null +++ b/apps/storybook/.storybook/preview.tsx @@ -0,0 +1,50 @@ +import "@repo/config-tailwind/styles.css" +import { defaultTranslationValues } from "@repo/i18n/config" +import i18nMessagesEnglish from "@repo/i18n/translations/en-US.json" +import { ThemeProvider } from "@repo/ui/Header/SwitchTheme" +import type { Preview } from "@storybook/react" +import { NextIntlClientProvider } from "next-intl" +import React from "react" + +const preview: Preview = { + parameters: { + nextjs: { + appDirectory: true, + }, + options: { + storySort: { + order: ["Design System", "User Interface", "Feature"], + }, + }, + backgrounds: { disable: true }, + darkMode: { + darkClass: "dark", + lightClass: "light", + classTarget: "html", + stylePreview: true, + }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + decorators: [ + (Story) => { + return ( + + + + + + ) + }, + ], +} + +export default preview diff --git a/apps/storybook/.storybook/test-runner.ts b/apps/storybook/.storybook/test-runner.ts new file mode 100644 index 0000000..f667b46 --- /dev/null +++ b/apps/storybook/.storybook/test-runner.ts @@ -0,0 +1,35 @@ +import type { TestRunnerConfig } from "@storybook/test-runner" +import { getStoryContext } from "@storybook/test-runner" + +import { checkA11y, configureAxe, injectAxe } from "axe-playwright" + +/* + * See https://storybook.js.org/docs/writing-tests/test-runner#test-hook-api + * to learn more about the test-runner hooks API. + */ +const config: TestRunnerConfig = { + async preVisit(page) { + await injectAxe(page) + }, + async postVisit(page, context) { + const storyContext = await getStoryContext(page, context) + + if (storyContext.parameters?.a11y?.disable) { + return + } + + await configureAxe(page, { + rules: storyContext.parameters?.a11y?.config?.rules, + }) + + await checkA11y(page, "#storybook-root", { + verbose: false, + detailedReport: true, + detailedReportOptions: { + html: true, + }, + }) + }, +} + +export default config diff --git a/apps/storybook/chromatic.config.json b/apps/storybook/chromatic.config.json new file mode 100644 index 0000000..29f3109 --- /dev/null +++ b/apps/storybook/chromatic.config.json @@ -0,0 +1,7 @@ +{ + "projectId": "Project:668708614e9ac6d0b97ea5e5", + "buildScriptName": "build", + "storybookBaseDir": "apps/storybook", + "onlyChanged": true, + "zip": true +} diff --git a/apps/storybook/package.json b/apps/storybook/package.json new file mode 100644 index 0000000..eb7691e --- /dev/null +++ b/apps/storybook/package.json @@ -0,0 +1,54 @@ +{ + "name": "@repo/storybook", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "build": "storybook build", + "dev": "storybook dev --port 6006 --no-open", + "start": "http-server \"storybook-static\" --port 6006 --silent", + "test": "start-server-and-test \"dev\" http://127.0.0.1:6006 \"test:storybook\"", + "test:storybook": "test-storybook", + "test:storybook-coverage": "test-storybook --coverage", + "chromatic": "chromatic" + }, + "dependencies": { + "@repo/config-tailwind": "workspace:*", + "@repo/i18n": "workspace:*", + "@repo/ui": "workspace:*", + "@repo/wikipedia-game-solver": "workspace:*", + "next": "catalog:", + "next-intl": "catalog:", + "react": "catalog:", + "react-dom": "catalog:" + }, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@chromatic-com/storybook": "catalog:", + "@playwright/test": "catalog:", + "@storybook/addon-a11y": "catalog:", + "@storybook/addon-essentials": "catalog:", + "@storybook/addon-interactions": "catalog:", + "@storybook/addon-links": "catalog:", + "@storybook/addon-storysource": "catalog:", + "@storybook/addon-themes": "catalog:", + "@storybook/blocks": "catalog:", + "@storybook/nextjs": "catalog:", + "@storybook/react": "catalog:", + "@storybook/test": "catalog:", + "@storybook/test-runner": "catalog:", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "axe-playwright": "catalog:", + "chromatic": "catalog:", + "eslint": "catalog:", + "http-server": "catalog:", + "start-server-and-test": "catalog:", + "storybook": "catalog:", + "storybook-dark-mode": "catalog:", + "postcss": "catalog:", + "tailwindcss": "catalog:", + "typescript": "catalog:" + } +} diff --git a/apps/storybook/postcss.config.js b/apps/storybook/postcss.config.js new file mode 100644 index 0000000..e0410a8 --- /dev/null +++ b/apps/storybook/postcss.config.js @@ -0,0 +1,7 @@ +const config = { + plugins: { + tailwindcss: {}, + }, +} + +export default config diff --git a/apps/storybook/stories/Colors.mdx b/apps/storybook/stories/Colors.mdx new file mode 100644 index 0000000..0ca1ca2 --- /dev/null +++ b/apps/storybook/stories/Colors.mdx @@ -0,0 +1,31 @@ +import { Meta, Title, ColorPalette, ColorItem } from "@storybook/blocks" +import tailwindConfig from "@repo/config-tailwind" + + + +Colors + + + {Object.entries(tailwindConfig.theme.colors).map( + ([colorName, colorValue]) => { + const colors = {} + + if (typeof colorValue === "string") { + colors[colorName] = colorValue + } else { + colors.light = colorValue.DEFAULT + colors.dark = colorValue.dark + } + + return ( + + ) + } + +)} + + diff --git a/apps/storybook/tailwind.config.js b/apps/storybook/tailwind.config.js new file mode 100644 index 0000000..016bacc --- /dev/null +++ b/apps/storybook/tailwind.config.js @@ -0,0 +1,9 @@ +import sharedConfig from "@repo/config-tailwind" + +/** @type {Pick} */ +const config = { + content: [".storybook/preview.tsx", "../../packages/**/*.tsx"], + presets: [sharedConfig], +} + +export default config diff --git a/apps/website/.env.example b/apps/website/.env.example new file mode 100644 index 0000000..6e64944 --- /dev/null +++ b/apps/website/.env.example @@ -0,0 +1,3 @@ +HOSTNAME=0.0.0.0 +PORT=5000 +NEXT_TELEMETRY_DISABLED=1 diff --git a/apps/website/.eslintrc.json b/apps/website/.eslintrc.json new file mode 100644 index 0000000..2b6857a --- /dev/null +++ b/apps/website/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "root": true, + "extends": ["@repo/eslint-config/nextjs/.eslintrc.json"], + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "plugins": ["@typescript-eslint"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": true + } + } + ] +} diff --git a/apps/website/Dockerfile b/apps/website/Dockerfile new file mode 100644 index 0000000..abcaf7c --- /dev/null +++ b/apps/website/Dockerfile @@ -0,0 +1,36 @@ +FROM node:22.4.1-slim AS node-pnpm +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable +WORKDIR /usr/src/app + +FROM node-pnpm AS builder +RUN pnpm install --global turbo@2.0.9 +COPY ./ ./ +RUN turbo prune @repo/website --docker + +FROM node-pnpm AS installer +ENV IS_STANDALONE=true + +COPY .gitignore .gitignore +COPY --from=builder /usr/src/app/out/json/ ./ +COPY --from=builder /usr/src/app/out/pnpm-lock.yaml ./pnpm-lock.yaml +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile +COPY --from=builder /usr/src/app/out/full/ ./ +COPY turbo.json turbo.json +RUN pnpm --filter=@repo/website... exec turbo run build + +FROM node-pnpm AS runner +ENV NODE_ENV=production +ENV HOSTNAME=0.0.0.0 +ENV NEXT_TELEMETRY_DISABLED=1 +ENV IS_STANDALONE=true + +RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 applicationrunner +USER applicationrunner +COPY --from=installer /usr/src/app/apps/website/next.config.js ./ +COPY --from=installer /usr/src/app/apps/website/package.json ./ +COPY --from=installer --chown=applicationrunner:nodejs /usr/src/app/apps/website/.next/standalone ./ +COPY --from=installer --chown=applicationrunner:nodejs /usr/src/app/apps/website/.next/static ./apps/website/.next/static +COPY --from=installer --chown=applicationrunner:nodejs /usr/src/app/apps/website/public ./apps/website/public +CMD ["node", "apps/website/server.js"] diff --git a/apps/website/app/[locale]/[...rest]/page.tsx b/apps/website/app/[locale]/[...rest]/page.tsx new file mode 100644 index 0000000..c17dbc5 --- /dev/null +++ b/apps/website/app/[locale]/[...rest]/page.tsx @@ -0,0 +1,7 @@ +import { notFound } from "next/navigation" + +const CatchAllPage: React.FC = () => { + return notFound() +} + +export default CatchAllPage diff --git a/apps/website/app/[locale]/about/page.tsx b/apps/website/app/[locale]/about/page.tsx new file mode 100644 index 0000000..34a2f71 --- /dev/null +++ b/apps/website/app/[locale]/about/page.tsx @@ -0,0 +1,26 @@ +import type { LocaleProps } from "@repo/i18n/config" +import { MainLayout } from "@repo/ui/MainLayout" +import { Button } from "@repo/ui/design/Button" +import { unstable_setRequestLocale } from "next-intl/server" +import { FaRocket } from "react-icons/fa6" + +interface HomePageProps extends LocaleProps {} + +const AboutPage: React.FC = (props) => { + const { params } = props + + // Enable static rendering + unstable_setRequestLocale(params.locale) + + return ( + +
+ +
+
+ ) +} + +export default AboutPage diff --git a/apps/website/app/[locale]/error.tsx b/apps/website/app/[locale]/error.tsx new file mode 100644 index 0000000..265b29c --- /dev/null +++ b/apps/website/app/[locale]/error.tsx @@ -0,0 +1,38 @@ +"use client" + +import { MainLayout } from "@repo/ui/MainLayout" +import { useTranslations } from "next-intl" +import { useEffect } from "react" + +interface ErrorBoundaryPageProps { + error: Error & { digest?: string } + reset: () => void +} + +const ErrorBoundaryPage: React.FC = (props) => { + const { error, reset } = props + + const t = useTranslations() + + useEffect(() => { + console.error(error) + }, [error]) + + return ( + +

+ {t("errors.error")} 500 - {t("errors.server-error")} +

+

+ +

+
+ ) +} + +export default ErrorBoundaryPage diff --git a/apps/website/app/[locale]/layout.tsx b/apps/website/app/[locale]/layout.tsx new file mode 100644 index 0000000..43422e8 --- /dev/null +++ b/apps/website/app/[locale]/layout.tsx @@ -0,0 +1,63 @@ +import "@repo/config-tailwind/styles.css" +import { VERSION } from "@repo/constants" +import type { Locale, LocaleProps } from "@repo/i18n/config" +import { LOCALES } from "@repo/i18n/config" +import { Footer } from "@repo/ui/Footer" +import { Header } from "@repo/ui/Header" +import { ThemeProvider } from "@repo/ui/Header/SwitchTheme" +import type { Metadata } from "next" +import { NextIntlClientProvider } from "next-intl" +import { + getMessages, + getTranslations, + unstable_setRequestLocale, +} from "next-intl/server" + +export const generateMetadata = async ({ + params, +}: LocaleProps): Promise => { + const t = await getTranslations({ locale: params.locale }) + return { + title: t("meta.title"), + description: t("meta.description"), + } +} + +export const generateStaticParams = (): Array<{ locale: Locale }> => { + return LOCALES.map((locale) => { + return { + locale, + } + }) +} + +interface LocaleLayoutProps extends React.PropsWithChildren { + params: { + locale: Locale + } +} + +const LocaleLayout: React.FC = async (props) => { + const { children, params } = props + + // Enable static rendering + unstable_setRequestLocale(params.locale) + + const messages = await getMessages() + + return ( + + + + +
+ {children} +