diff --git a/.vscode/react.code-snippets b/.vscode/react.code-snippets index 1a4e2c2..38f5c1c 100644 --- a/.vscode/react.code-snippets +++ b/.vscode/react.code-snippets @@ -3,7 +3,7 @@ "scope": "typescriptreact", "prefix": "rfc", "body": [ - "interface ${1:ComponentName}Props {}", + "export interface ${1:ComponentName}Props {}", "", "export const ${1:ComponentName}: React.FC<${1:ComponentName}Props> = () => {", " return (", diff --git a/apps/storybook/.storybook/preview.tsx b/apps/storybook/.storybook/preview.tsx index 406d159..54777fa 100644 --- a/apps/storybook/.storybook/preview.tsx +++ b/apps/storybook/.storybook/preview.tsx @@ -1,9 +1,9 @@ import "@repo/config-tailwind/styles.css" import { defaultTranslationValues, Locale } 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 { ThemeProvider as NextThemeProvider } from "next-themes" import React from "react" const preview: Preview = { @@ -13,7 +13,7 @@ const preview: Preview = { }, options: { storySort: { - order: ["Design System", "User Interface", "Feature"], + order: ["Design System", "Layout", "Errors"], }, }, backgrounds: { disable: true }, @@ -35,7 +35,7 @@ const preview: Preview = { const locale = "en-US" satisfies Locale return ( - + - + ) }, ], diff --git a/apps/storybook/package.json b/apps/storybook/package.json index 06af0e6..c7c502c 100644 --- a/apps/storybook/package.json +++ b/apps/storybook/package.json @@ -19,6 +19,7 @@ "@repo/wikipedia-game-solver": "workspace:*", "next": "catalog:", "next-intl": "catalog:", + "next-themes": "catalog:", "react": "catalog:", "react-dom": "catalog:" }, diff --git a/apps/website/Dockerfile b/apps/website/Dockerfile index abcaf7c..899c8ef 100644 --- a/apps/website/Dockerfile +++ b/apps/website/Dockerfile @@ -5,7 +5,7 @@ RUN corepack enable WORKDIR /usr/src/app FROM node-pnpm AS builder -RUN pnpm install --global turbo@2.0.9 +RUN pnpm install --global turbo@2.0.10 COPY ./ ./ RUN turbo prune @repo/website --docker diff --git a/apps/website/app/[locale]/error.tsx b/apps/website/app/[locale]/error.tsx index 94f51c3..3905bb0 100644 --- a/apps/website/app/[locale]/error.tsx +++ b/apps/website/app/[locale]/error.tsx @@ -1,34 +1,10 @@ "use client" -import { Button } from "@repo/ui/design/Button" -import { MainLayout } from "@repo/ui/MainLayout" -import { useTranslations } from "next-intl" -import { useEffect } from "react" +import type { ErrorServerProps } from "@repo/ui/Errors/ErrorServer" +import { ErrorServer } from "@repo/ui/Errors/ErrorServer" -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")} - - - {t("errors.try-again")} - - - ) +const ErrorBoundaryPage: React.FC = (props) => { + return } export default ErrorBoundaryPage diff --git a/apps/website/app/[locale]/layout.tsx b/apps/website/app/[locale]/layout.tsx index 0c3a58a..79abd73 100644 --- a/apps/website/app/[locale]/layout.tsx +++ b/apps/website/app/[locale]/layout.tsx @@ -1,10 +1,10 @@ import "@repo/config-tailwind/styles.css" -import { VERSION } from "@repo/utils/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 { Footer } from "@repo/ui/Layout/Footer" +import { Header } from "@repo/ui/Layout/Header" +import { ThemeProvider } from "@repo/ui/Layout/Header/SwitchTheme" +import { VERSION } from "@repo/utils/constants" import type { Metadata } from "next" import { NextIntlClientProvider } from "next-intl" import { @@ -17,9 +17,37 @@ export const generateMetadata = async ({ params, }: LocaleProps): Promise => { const t = await getTranslations({ locale: params.locale }) + const title = t("meta.title") + const description = t("meta.description") + const image = "/images/Wikipedia-Logo.webp" + const url = new URL("https://wikipedia-game-solver.theoludwig.fr") + const locale = LOCALES.join(", ") + return { - title: t("meta.title"), - description: t("meta.description"), + title, + description, + metadataBase: url, + openGraph: { + title, + description, + url, + siteName: title, + images: [ + { + url: image, + width: 96, + height: 96, + }, + ], + locale, + type: "website", + }, + twitter: { + card: "summary", + title, + description, + images: [image], + }, } } diff --git a/apps/website/app/[locale]/loading.tsx b/apps/website/app/[locale]/loading.tsx index 628dbf6..57378e5 100644 --- a/apps/website/app/[locale]/loading.tsx +++ b/apps/website/app/[locale]/loading.tsx @@ -1,9 +1,9 @@ -import { MainLayout } from "@repo/ui/MainLayout" -import { Spinner } from "@repo/ui/design/Spinner" +import { Spinner } from "@repo/ui/Design/Spinner" +import { MainLayout } from "@repo/ui/Layout/MainLayout" const Loading: React.FC = () => { return ( - + ) diff --git a/apps/website/app/[locale]/not-found.tsx b/apps/website/app/[locale]/not-found.tsx index df1371f..a9aede1 100644 --- a/apps/website/app/[locale]/not-found.tsx +++ b/apps/website/app/[locale]/not-found.tsx @@ -1,24 +1,10 @@ -import { Link } from "@repo/ui/design/Link" -import { MainLayout } from "@repo/ui/MainLayout" -import { useTranslations } from "next-intl" +import { ErrorNotFound } from "@repo/ui/Errors/ErrorNotFound" /** * Note that `app/[locale]/[...rest]/page.tsx` is necessary for this page to render. */ const NotFound: React.FC = () => { - const t = useTranslations() - - return ( - - - {t("errors.error")} 404 - {t("errors.not-found")} - - - {t("errors.page-doesnt-exist")}{" "} - {t("errors.return-to-home-page")} - - - ) + return } export default NotFound diff --git a/apps/website/app/[locale]/page.tsx b/apps/website/app/[locale]/page.tsx index 526b523..572790d 100644 --- a/apps/website/app/[locale]/page.tsx +++ b/apps/website/app/[locale]/page.tsx @@ -1,13 +1,17 @@ import type { LocaleProps } from "@repo/i18n/config" -import { Link } from "@repo/ui/design/Link" -import { Typography } from "@repo/ui/design/Typography" -import { MainLayout } from "@repo/ui/MainLayout" +import { Link } from "@repo/ui/Design/Link" +import { Typography } from "@repo/ui/Design/Typography" +import { MainLayout } from "@repo/ui/Layout/MainLayout" +import { + fromLocaleToWikipediaLocale, + getWikipediaLink, +} from "@repo/wikipedia-game-solver/wikipedia-api" import { WikipediaClient } from "@repo/wikipedia-game-solver/WikipediaClient" import { useTranslations } from "next-intl" import { unstable_setRequestLocale } from "next-intl/server" import Image from "next/image" -import WikipediaLogo from "#public/images/Wikipedia-Logo.png" +import WikipediaLogo from "#public/images/Wikipedia-Logo.webp" interface HomePageProps extends LocaleProps {} @@ -19,6 +23,8 @@ const HomePage: React.FC = (props) => { const t = useTranslations() + const localeWikipedia = fromLocaleToWikipediaLocale(params.locale) + return ( @@ -28,9 +34,9 @@ const HomePage: React.FC = (props) => { {t.rich("home.description", { - wikipedia: (children) => { + "wikipedia-link": (children) => { return ( - + {children} ) diff --git a/apps/website/middleware.ts b/apps/website/middleware.ts index 755cdc9..e604e4f 100644 --- a/apps/website/middleware.ts +++ b/apps/website/middleware.ts @@ -14,6 +14,8 @@ export const config = { // Set a cookie to remember the previous locale for // all requests that have a locale prefix + // Next.js issue, middleware matcher should support template literals: + // https://github.com/vercel/next.js/issues/56398 "/(en-US|fr-FR)/:path*", // Enable redirects that add missing locales diff --git a/apps/website/package.json b/apps/website/package.json index b19b09b..f895906 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -23,7 +23,7 @@ "next-intl": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "react-icons": "catalog:" + "sharp": "catalog:" }, "devDependencies": { "@repo/eslint-config": "workspace:*", diff --git a/apps/website/public/images/Wikipedia-Logo.png b/apps/website/public/images/Wikipedia-Logo.png deleted file mode 100644 index 11957e0..0000000 Binary files a/apps/website/public/images/Wikipedia-Logo.png and /dev/null differ diff --git a/apps/website/public/images/Wikipedia-Logo.webp b/apps/website/public/images/Wikipedia-Logo.webp new file mode 100644 index 0000000..a178539 Binary files /dev/null and b/apps/website/public/images/Wikipedia-Logo.webp differ diff --git a/package.json b/package.json index 58c0fd2..0eac7d9 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,12 @@ "@semantic-release/exec": "6.0.3", "@semantic-release/git": "10.0.1", "editorconfig-checker": "5.1.8", + "playwright": "catalog:", "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.5", "replace-in-files-cli": "3.0.0", "semantic-release": "23.1.1", - "turbo": "2.0.9", + "turbo": "2.0.10", "typescript": "catalog:" } } diff --git a/packages/i18n/src/translations/en-US.json b/packages/i18n/src/translations/en-US.json index ebe4bdc..cd2cff7 100644 --- a/packages/i18n/src/translations/en-US.json +++ b/packages/i18n/src/translations/en-US.json @@ -1,13 +1,15 @@ { "meta": { "title": "Wikipedia Game Solver", - "description": "The Wikipedia Game involves players competing to navigate from one Wikipedia page to another using only internal links.", - "all-rights-reserved": "All rights reserved" + "description": "The Wikipedia Game involves players competing to navigate from one Wikipedia page to another using only internal links." }, "locales": { "en-US": "🇺🇸 English", "fr-FR": "🇫🇷 French" }, + "footer": { + "all-rights-reserved": "All rights reserved" + }, "errors": { "error": "Error", "page-doesnt-exist": "This page doesn't exist!", @@ -18,6 +20,6 @@ }, "home": { "title": "Wikipedia Game Solver", - "description": "The Wikipedia Game involves players competing to navigate from one Wikipedia page to another using only internal links." + "description": "The Wikipedia Game involves players competing to navigate from one Wikipedia page to another using only internal links." } } diff --git a/packages/i18n/src/translations/fr-FR.json b/packages/i18n/src/translations/fr-FR.json index b1712f7..ea24c77 100644 --- a/packages/i18n/src/translations/fr-FR.json +++ b/packages/i18n/src/translations/fr-FR.json @@ -1,13 +1,15 @@ { "meta": { "title": "Wikipedia Game Solver", - "description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page Wikipédia à une autre en utilisant uniquement des liens internes.", - "all-rights-reserved": "Tous droits réservés" + "description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page Wikipédia à une autre en utilisant uniquement des liens internes." }, "locales": { "en-US": "🇺🇸 Anglais", "fr-FR": "🇫🇷 Français" }, + "footer": { + "all-rights-reserved": "Tous droits réservés" + }, "errors": { "error": "Erreur", "page-doesnt-exist": "Cette page n'existe pas !", @@ -18,6 +20,6 @@ }, "home": { "title": "Wikipedia Game Solver", - "description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page Wikipedia à une autre en utilisant uniquement des liens internes." + "description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page Wikipedia à une autre en utilisant uniquement des liens internes." } } diff --git a/packages/ui/package.json b/packages/ui/package.json index bd94171..bf64ea8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -4,14 +4,17 @@ "private": true, "type": "module", "exports": { - "./design/Button": "./src/design/Button/Button.tsx", - "./design/Link": "./src/design/Link/Link.tsx", - "./design/Spinner": "./src/design/Spinner/Spinner.tsx", - "./design/Typography": "./src/design/Typography/Typography.tsx", - "./Footer": "./src/Footer/Footer.tsx", - "./Header": "./src/Header/Header.tsx", - "./Header/SwitchTheme": "./src/Header/SwitchTheme.tsx", - "./MainLayout": "./src/MainLayout/MainLayout.tsx" + "./Design/Button": "./src/Design/Button/Button.tsx", + "./Design/Link": "./src/Design/Link/Link.tsx", + "./Design/Spinner": "./src/Design/Spinner/Spinner.tsx", + "./Design/Typography": "./src/Design/Typography/Typography.tsx", + "./Errors/ErrorNotFound": "./src/Errors/ErrorNotFound/ErrorNotFound.tsx", + "./Errors/ErrorServer": "./src/Errors/ErrorServer/ErrorServer.tsx", + "./Layout/Footer": "./src/Layout/Footer/Footer.tsx", + "./Layout/Header": "./src/Layout/Header/Header.tsx", + "./Layout/Header/SwitchTheme": "./src/Layout/Header/SwitchTheme.tsx", + "./Layout/MainLayout": "./src/Layout/MainLayout/MainLayout.tsx", + "./Layout/Section": "./src/Layout/Section/Section.tsx" }, "scripts": { "lint:eslint": "eslint src --max-warnings 0 --report-unused-disable-directives", diff --git a/packages/ui/src/design/Button/Button.stories.tsx b/packages/ui/src/Design/Button/Button.stories.tsx similarity index 100% rename from packages/ui/src/design/Button/Button.stories.tsx rename to packages/ui/src/Design/Button/Button.stories.tsx diff --git a/packages/ui/src/design/Button/Button.tsx b/packages/ui/src/Design/Button/Button.tsx similarity index 100% rename from packages/ui/src/design/Button/Button.tsx rename to packages/ui/src/Design/Button/Button.tsx diff --git a/packages/ui/src/design/Button/Ripple.tsx b/packages/ui/src/Design/Button/Ripple.tsx similarity index 100% rename from packages/ui/src/design/Button/Ripple.tsx rename to packages/ui/src/Design/Button/Ripple.tsx diff --git a/packages/ui/src/design/Link/Link.stories.tsx b/packages/ui/src/Design/Link/Link.stories.tsx similarity index 100% rename from packages/ui/src/design/Link/Link.stories.tsx rename to packages/ui/src/Design/Link/Link.stories.tsx diff --git a/packages/ui/src/design/Link/Link.tsx b/packages/ui/src/Design/Link/Link.tsx similarity index 100% rename from packages/ui/src/design/Link/Link.tsx rename to packages/ui/src/Design/Link/Link.tsx diff --git a/packages/ui/src/design/Spinner/Spinner.stories.tsx b/packages/ui/src/Design/Spinner/Spinner.stories.tsx similarity index 100% rename from packages/ui/src/design/Spinner/Spinner.stories.tsx rename to packages/ui/src/Design/Spinner/Spinner.stories.tsx diff --git a/packages/ui/src/design/Spinner/Spinner.tsx b/packages/ui/src/Design/Spinner/Spinner.tsx similarity index 100% rename from packages/ui/src/design/Spinner/Spinner.tsx rename to packages/ui/src/Design/Spinner/Spinner.tsx diff --git a/packages/ui/src/design/Typography/Typography.stories.tsx b/packages/ui/src/Design/Typography/Typography.stories.tsx similarity index 100% rename from packages/ui/src/design/Typography/Typography.stories.tsx rename to packages/ui/src/Design/Typography/Typography.stories.tsx diff --git a/packages/ui/src/design/Typography/Typography.tsx b/packages/ui/src/Design/Typography/Typography.tsx similarity index 94% rename from packages/ui/src/design/Typography/Typography.tsx rename to packages/ui/src/Design/Typography/Typography.tsx index dd9b48a..566eedb 100644 --- a/packages/ui/src/design/Typography/Typography.tsx +++ b/packages/ui/src/Design/Typography/Typography.tsx @@ -11,8 +11,8 @@ const typographyVariants = cva({ h4: "text-xl font-semibold", h5: "text-xl font-medium", h6: "text-lg font-medium", - text1: "text-base", - text2: "text-sm", + text1: "break-words text-base", + text2: "break-words text-sm", }, }, }) diff --git a/packages/ui/src/Errors/ErrorNotFound/ErrorNotFound.stories.tsx b/packages/ui/src/Errors/ErrorNotFound/ErrorNotFound.stories.tsx new file mode 100644 index 0000000..4f9f9f0 --- /dev/null +++ b/packages/ui/src/Errors/ErrorNotFound/ErrorNotFound.stories.tsx @@ -0,0 +1,16 @@ +import type { Meta, StoryObj } from "@storybook/react" + +import { ErrorNotFound as ErrorNotFoundComponent } from "./ErrorNotFound" + +const meta = { + title: "Errors/ErrorNotFound", + component: ErrorNotFoundComponent, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const ErrorNotFound: Story = { + args: {}, +} diff --git a/packages/ui/src/Errors/ErrorNotFound/ErrorNotFound.tsx b/packages/ui/src/Errors/ErrorNotFound/ErrorNotFound.tsx new file mode 100644 index 0000000..62ef931 --- /dev/null +++ b/packages/ui/src/Errors/ErrorNotFound/ErrorNotFound.tsx @@ -0,0 +1,26 @@ +import { useTranslations } from "next-intl" +import { Link } from "../../Design/Link/Link" +import { Typography } from "../../Design/Typography/Typography" +import { MainLayout } from "../../Layout/MainLayout/MainLayout" +import { Section } from "../../Layout/Section/Section" + +export interface ErrorNotFoundProps {} + +export const ErrorNotFound: React.FC = () => { + const t = useTranslations() + + return ( + + + + {t("errors.error")} 404 - {t("errors.not-found")} + + + + {t("errors.page-doesnt-exist")}{" "} + {t("errors.return-to-home-page")} + + + + ) +} diff --git a/packages/ui/src/Errors/ErrorServer/ErrorServer.stories.tsx b/packages/ui/src/Errors/ErrorServer/ErrorServer.stories.tsx new file mode 100644 index 0000000..71a0547 --- /dev/null +++ b/packages/ui/src/Errors/ErrorServer/ErrorServer.stories.tsx @@ -0,0 +1,22 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { expect, fn, userEvent, within } from "@storybook/test" + +import { ErrorServer as ErrorServerComponent } from "./ErrorServer" + +const meta = { + title: "Errors/ErrorServer", + component: ErrorServerComponent, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const ErrorServer: Story = { + args: { reset: fn(), error: new Error("Server error") }, + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement) + await userEvent.click(canvas.getByText("Try again?")) + await expect(args.reset).toHaveBeenCalled() + }, +} diff --git a/packages/ui/src/Errors/ErrorServer/ErrorServer.tsx b/packages/ui/src/Errors/ErrorServer/ErrorServer.tsx new file mode 100644 index 0000000..9acc944 --- /dev/null +++ b/packages/ui/src/Errors/ErrorServer/ErrorServer.tsx @@ -0,0 +1,37 @@ +"use client" + +import { useTranslations } from "next-intl" +import { useEffect } from "react" +import { Button } from "../../Design/Button/Button" +import { Typography } from "../../Design/Typography/Typography" +import { MainLayout } from "../../Layout/MainLayout/MainLayout" +import { Section } from "../../Layout/Section/Section" + +export interface ErrorServerProps { + error: Error & { digest?: string } + reset: () => void +} + +export const ErrorServer: React.FC = (props) => { + const { error, reset } = props + + const t = useTranslations() + + useEffect(() => { + console.error(error) + }, [error]) + + return ( + + + + {t("errors.error")} 500 - {t("errors.server-error")} + + + + {t("errors.try-again")} + + + + ) +} diff --git a/packages/ui/src/Footer/Footer.stories.tsx b/packages/ui/src/Layout/Footer/Footer.stories.tsx similarity index 90% rename from packages/ui/src/Footer/Footer.stories.tsx rename to packages/ui/src/Layout/Footer/Footer.stories.tsx index 3300169..e88c609 100644 --- a/packages/ui/src/Footer/Footer.stories.tsx +++ b/packages/ui/src/Layout/Footer/Footer.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react" import { Footer as FooterComponent } from "./Footer" const meta = { - title: "User Interface/Footer", + title: "Layout/Footer", component: FooterComponent, } satisfies Meta diff --git a/packages/ui/src/Footer/Footer.tsx b/packages/ui/src/Layout/Footer/Footer.tsx similarity index 77% rename from packages/ui/src/Footer/Footer.tsx rename to packages/ui/src/Layout/Footer/Footer.tsx index 25f92f6..38aedca 100644 --- a/packages/ui/src/Footer/Footer.tsx +++ b/packages/ui/src/Layout/Footer/Footer.tsx @@ -1,7 +1,7 @@ import { useTranslations } from "next-intl" import { GIT_REPO_LINK } from "@repo/utils/constants" -import { Link } from "../design/Link/Link" +import { Link } from "../../Design/Link/Link" export interface FooterProps { version: string @@ -13,12 +13,12 @@ export const Footer: React.FC = (props) => { const t = useTranslations() return ( -
- {t("errors.try-again")} -
- {t("errors.page-doesnt-exist")}{" "} - {t("errors.return-to-home-page")} -