fix not found page

This commit is contained in:
d-kimsuon
2025-10-26 18:05:35 +09:00
parent e0b94e2885
commit 343096b82e
11 changed files with 221 additions and 25 deletions

View File

@@ -0,0 +1,55 @@
import { Trans } from "@lingui/react";
import { FileQuestion, Home } from "lucide-react";
import type { FC, ReactNode } from "react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
interface NotFoundProps {
message?: ReactNode;
description?: ReactNode;
}
export const NotFound: FC<NotFoundProps> = ({
message = <Trans id="notfound.default.title" message="Page Not Found" />,
description = (
<Trans
id="notfound.default.description"
message="The page you are looking for does not exist or has been moved."
/>
),
}) => {
return (
<div className="flex min-h-screen items-center justify-center p-4">
<Card className="w-full max-w-2xl">
<CardHeader>
<div className="flex items-center gap-3">
<FileQuestion className="size-6 text-muted-foreground" />
<div>
<CardTitle>{message}</CardTitle>
<CardDescription>{description}</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex gap-2">
<Button
onClick={() => {
window.location.href = "/";
}}
variant="default"
>
<Home />
<Trans id="notfound.button.go_home" message="Go to Home" />
</Button>
</div>
</CardContent>
</Card>
</div>
);
};

View File

@@ -2,17 +2,13 @@ import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { type FC, type PropsWithChildren, useEffect } from "react";
import { useConfig } from "../../app/hooks/useConfig";
import { i18nMessages } from ".";
for (const { locale, messages } of i18nMessages) {
i18n.load(locale, messages);
}
import { activateLocale } from ".";
export const LinguiClientProvider: FC<PropsWithChildren> = ({ children }) => {
const { config } = useConfig();
useEffect(() => {
i18n.activate(config.locale);
void activateLocale(config.locale);
}, [config.locale]);
return <I18nProvider i18n={i18n}>{children}</I18nProvider>;

View File

@@ -1,20 +1,27 @@
import type { Messages } from "@lingui/core";
import { messages as enMessages } from "./locales/en/messages";
import { messages as jaMessages } from "./locales/ja/messages";
import { i18n } from "@lingui/core";
import type { SupportedLocale } from "./schema";
export const locales: SupportedLocale[] = ["ja", "en"];
export const i18nMessages = [
{
locale: "ja",
messages: jaMessages,
},
{
locale: "en",
messages: enMessages,
},
] as const satisfies Array<{
locale: SupportedLocale;
messages: Messages;
}>;
const importMessages = async (locale: SupportedLocale) => {
switch (locale) {
case "ja":
return import("./locales/ja/messages");
case "en":
return import("./locales/en/messages");
default:
locale satisfies never;
throw new Error(`Unsupported locale: ${locale}`);
}
};
const loadedLocales: SupportedLocale[] = [];
export const activateLocale = async (locale: SupportedLocale) => {
if (!loadedLocales.includes(locale)) {
const { messages } = await importMessages(locale);
i18n.load(locale, messages);
loadedLocales.push(locale);
}
i18n.activate(locale);
};

View File

@@ -1669,5 +1669,58 @@
"comments": [],
"origin": [["src/components/SettingsControls.tsx", 281]],
"translation": "日本語"
},
"notfound.default.title": {
"message": "Page Not Found",
"placeholders": {},
"comments": [],
"origin": [["src/components/NotFound.tsx", 19]],
"translation": "Page Not Found"
},
"notfound.default.description": {
"message": "The page you are looking for does not exist or has been moved.",
"placeholders": {},
"comments": [],
"origin": [["src/components/NotFound.tsx", 23]],
"translation": "The page you are looking for does not exist or has been moved."
},
"notfound.button.go_home": {
"message": "Go to Home",
"placeholders": {},
"comments": [],
"origin": [["src/components/NotFound.tsx", 48]],
"translation": "Go to Home"
},
"notfound.project.title": {
"message": "Project Not Found",
"placeholders": {},
"comments": [],
"origin": [["src/routes/projects/$projectId/latest/index.tsx", 15]],
"translation": "Project Not Found"
},
"notfound.project.description": {
"message": "The project you are looking for does not exist.",
"placeholders": {},
"comments": [],
"origin": [["src/routes/projects/$projectId/latest/index.tsx", 19]],
"translation": "The project you are looking for does not exist."
},
"notfound.session.title": {
"message": "Session Not Found",
"placeholders": {},
"comments": [],
"origin": [
["src/routes/projects/$projectId/sessions/$sessionId/index.tsx", 13]
],
"translation": "Session Not Found"
},
"notfound.session.description": {
"message": "The session you are looking for does not exist.",
"placeholders": {},
"comments": [],
"origin": [
["src/routes/projects/$projectId/sessions/$sessionId/index.tsx", 17]
],
"translation": "The session you are looking for does not exist."
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1669,5 +1669,58 @@
"comments": [],
"origin": [["src/components/SettingsControls.tsx", 281]],
"translation": "日本語"
},
"notfound.default.title": {
"message": "Page Not Found",
"placeholders": {},
"comments": [],
"origin": [["src/components/NotFound.tsx", 19]],
"translation": "ページが見つかりません"
},
"notfound.default.description": {
"message": "The page you are looking for does not exist or has been moved.",
"placeholders": {},
"comments": [],
"origin": [["src/components/NotFound.tsx", 23]],
"translation": "お探しのページは存在しないか、移動されました。"
},
"notfound.button.go_home": {
"message": "Go to Home",
"placeholders": {},
"comments": [],
"origin": [["src/components/NotFound.tsx", 48]],
"translation": "ホームに戻る"
},
"notfound.project.title": {
"message": "Project Not Found",
"placeholders": {},
"comments": [],
"origin": [["src/routes/projects/$projectId/latest/index.tsx", 15]],
"translation": "プロジェクトが見つかりません"
},
"notfound.project.description": {
"message": "The project you are looking for does not exist.",
"placeholders": {},
"comments": [],
"origin": [["src/routes/projects/$projectId/latest/index.tsx", 19]],
"translation": "お探しのプロジェクトは存在しません。"
},
"notfound.session.title": {
"message": "Session Not Found",
"placeholders": {},
"comments": [],
"origin": [
["src/routes/projects/$projectId/sessions/$sessionId/index.tsx", 13]
],
"translation": "セッションが見つかりません"
},
"notfound.session.description": {
"message": "The session you are looking for does not exist.",
"placeholders": {},
"comments": [],
"origin": [
["src/routes/projects/$projectId/sessions/$sessionId/index.tsx", 17]
],
"translation": "お探しのセッションは存在しません。"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,7 @@ import ReactDOM from "react-dom/client";
import { routeTree } from "./routeTree.gen";
import "./styles.css";
import { NotFound } from "./components/NotFound";
import { QueryClientProviderWrapper } from "./lib/api/QueryClientProviderWrapper";
const router = createRouter({
@@ -14,6 +15,7 @@ const router = createRouter({
scrollRestoration: true,
defaultStructuralSharing: true,
defaultPreloadStaleTime: 0,
defaultNotFoundComponent: () => <NotFound />,
});
declare module "@tanstack/react-router" {

View File

@@ -1,12 +1,27 @@
import { Trans } from "@lingui/react";
import {
createFileRoute,
useLoaderData,
useRouter,
} from "@tanstack/react-router";
import { NotFound } from "../../../../components/NotFound";
import { honoClient } from "../../../../lib/api/client";
export const Route = createFileRoute("/projects/$projectId/latest/")({
component: RouteComponent,
notFoundComponent: () => (
<NotFound
message={
<Trans id="notfound.project.title" message="Project Not Found" />
}
description={
<Trans
id="notfound.project.description"
message="The project you are looking for does not exist."
/>
}
/>
),
loader: async ({ params }) => {
const { projectId } = params;
const response = await honoClient.api.projects[":projectId"][

View File

@@ -1,10 +1,25 @@
import { Trans } from "@lingui/react";
import { createFileRoute } from "@tanstack/react-router";
import { SessionPageContent } from "../../../../../app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent";
import { NotFound } from "../../../../../components/NotFound";
export const Route = createFileRoute(
"/projects/$projectId/sessions/$sessionId/",
)({
component: RouteComponent,
notFoundComponent: () => (
<NotFound
message={
<Trans id="notfound.session.title" message="Session Not Found" />
}
description={
<Trans
id="notfound.session.description"
message="The session you are looking for does not exist."
/>
}
/>
),
});
function RouteComponent() {