From 315d511f4178a5c8c8a8524557d7683b2667e3b3 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Thu, 17 Jul 2025 04:51:57 +0900 Subject: [PATCH] #24 --- frontend/package-lock.json | 22 ++++++---- frontend/package.json | 4 +- frontend/src/App.tsx | 34 +++++++++++---- frontend/src/components/ErrorScreen.tsx | 48 +++++++++++++++++++++ frontend/src/pages/Forbidden.tsx | 4 ++ frontend/src/pages/NotFound.tsx | 26 +---------- frontend/src/pages/ServiceUnavailable.tsx | 4 ++ frontend/src/pages/posts/PostDetailPage.tsx | 18 +++++--- frontend/src/pages/posts/PostNewPage.tsx | 11 ++++- frontend/src/pages/wiki/WikiEditPage.tsx | 10 ++++- frontend/src/pages/wiki/WikiNewPage.tsx | 10 ++++- 11 files changed, 136 insertions(+), 55 deletions(-) create mode 100644 frontend/src/components/ErrorScreen.tsx create mode 100644 frontend/src/pages/Forbidden.tsx create mode 100644 frontend/src/pages/ServiceUnavailable.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 79ed7ca..417dc54 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,7 +12,7 @@ "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-toast": "^1.2.14", - "axios": "^1.9.0", + "axios": "^1.10.0", "camelcase-keys": "^9.1.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -29,7 +29,7 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", - "@types/axios": "^0.9.36", + "@types/axios": "^0.14.4", "@types/markdown-it": "^14.1.2", "@types/node": "^24.0.13", "@types/react": "^19.1.2", @@ -1897,11 +1897,15 @@ ] }, "node_modules/@types/axios": { - "version": "0.9.36", - "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.9.36.tgz", - "integrity": "sha512-NLOpedx9o+rxo/X5ChbdiX6mS1atE4WHmEEIcR9NLenRVa5HoVjAvjafwU3FPTqnZEstpoqCaW7fagqSoTDNeg==", + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.4.tgz", + "integrity": "sha512-9JgOaunvQdsQ/qW2OPmE5+hCeUB52lQSolecrFrthct55QekhmXEwT203s20RL+UHtCQc15y3VXpby9E7Kkh/g==", + "deprecated": "This is a stub types definition. axios provides its own type definitions, so you do not need this installed.", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "axios": "*" + } }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -2509,9 +2513,9 @@ } }, "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", diff --git a/frontend/package.json b/frontend/package.json index 1945bd2..ddc80b1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,7 @@ "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-toast": "^1.2.14", - "axios": "^1.9.0", + "axios": "^1.10.0", "camelcase-keys": "^9.1.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -31,7 +31,7 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", - "@types/axios": "^0.9.36", + "@types/axios": "^0.14.4", "@types/markdown-it": "^14.1.2", "@types/node": "^24.0.13", "@types/react": "^19.1.2", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0e8f93f..ea1e1d7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,6 +11,7 @@ import NotFound from '@/pages/NotFound' import PostDetailPage from '@/pages/posts/PostDetailPage' import PostListPage from '@/pages/posts/PostListPage' import PostNewPage from '@/pages/posts/PostNewPage' +import ServiceUnavailable from '@/pages/ServiceUnavailable' import SettingPage from '@/pages/users/SettingPage' import WikiDetailPage from '@/pages/wiki/WikiDetailPage' import WikiDiffPage from '@/pages/wiki/WikiDiffPage' @@ -24,6 +25,7 @@ import type { User } from '@/types' export default () => { const [user, setUser] = useState (null) + const [status, setStatus] = useState (200) useEffect (() => { const createUser = async () => { @@ -40,18 +42,32 @@ export default () => { if (code) { void (async () => { - const res = await axios.post (`${ API_BASE_URL }/users/verify`, { code }) - const data = res.data as { valid: boolean, user: any } - if (data.valid) - setUser (toCamel (data.user, { deep: true })) - else - await createUser () + try + { + const res = await axios.post (`${ API_BASE_URL }/users/verify`, { code }) + const data = res.data as { valid: boolean, user: any } + if (data.valid) + setUser (toCamel (data.user, { deep: true })) + else + await createUser () + } + catch (err) + { + if (axios.isAxiosError (err)) + setStatus (err.status ?? 200) + } }) () } else createUser () }, []) + switch (status) + { + case 503: + return + } + return ( <> @@ -61,13 +77,13 @@ export default () => { } /> } /> - } /> + } /> } /> } /> } /> } /> - } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/frontend/src/components/ErrorScreen.tsx b/frontend/src/components/ErrorScreen.tsx new file mode 100644 index 0000000..bc77813 --- /dev/null +++ b/frontend/src/components/ErrorScreen.tsx @@ -0,0 +1,48 @@ +import { Helmet } from 'react-helmet-async' + +import errorImg from '@/assets/images/not-found.gif' +import MainArea from '@/components/layout/MainArea' +import { SITE_TITLE } from '@/config' + +type Props = { status: number } + + +export default ({ status }: Props) => { + const [message, rightMsg, leftMsg]: [string, string, string] = (() => { + switch (status) + { + case 403: + return ['権限ないよ(笑)', '帰れ!', '帰れ!'] + case 404: + return ['ページないよ(笑)', '帰れ!', '帰れ!'] + case 500: + return ['鯖でエラー出たって(嘲笑)', '管理人に', '聯絡を!'] + case 503: + return ['鯖死んでるよ(泣)', '管理人に', '聯絡を!'] + default: + throw new Error + } + }) () + + const title = message.replaceAll ('(', ' (').replaceAll (')', ')') + + return ( + + + + {title} | {SITE_TITLE} + +
+

{status}

+
+

{leftMsg}

+ 逃げたギター +

{rightMsg}

+
+

{message}

+
+
) +} diff --git a/frontend/src/pages/Forbidden.tsx b/frontend/src/pages/Forbidden.tsx new file mode 100644 index 0000000..fea1604 --- /dev/null +++ b/frontend/src/pages/Forbidden.tsx @@ -0,0 +1,4 @@ +import ErrorScreen from '@/components/ErrorScreen' + + +export default () => diff --git a/frontend/src/pages/NotFound.tsx b/frontend/src/pages/NotFound.tsx index ea681fd..8552471 100644 --- a/frontend/src/pages/NotFound.tsx +++ b/frontend/src/pages/NotFound.tsx @@ -1,26 +1,4 @@ -import { Helmet } from 'react-helmet-async' +import ErrorScreen from '@/components/ErrorScreen' -import notFoundImg from '@/assets/images/not-found.gif' -import MainArea from '@/components/layout/MainArea' -import { SITE_TITLE } from '@/config' - -export default () => ( - - - - ページないよ (笑) | {SITE_TITLE} - -
-

404

-
-

帰れ!

- 404 -

帰れ!

-
-

ページないよ(笑)

-
-
) +export default () => diff --git a/frontend/src/pages/ServiceUnavailable.tsx b/frontend/src/pages/ServiceUnavailable.tsx new file mode 100644 index 0000000..113e8f5 --- /dev/null +++ b/frontend/src/pages/ServiceUnavailable.tsx @@ -0,0 +1,4 @@ +import ErrorScreen from '@/components/ErrorScreen' + + +export default () => diff --git a/frontend/src/pages/posts/PostDetailPage.tsx b/frontend/src/pages/posts/PostDetailPage.tsx index d937084..501a77a 100644 --- a/frontend/src/pages/posts/PostDetailPage.tsx +++ b/frontend/src/pages/posts/PostDetailPage.tsx @@ -14,6 +14,7 @@ import { toast } from '@/components/ui/use-toast' import { API_BASE_URL, SITE_TITLE } from '@/config' import { cn } from '@/lib/utils' import NotFound from '@/pages/NotFound' +import ServiceUnavailable from '@/pages/ServiceUnavailable' import type { Post, User } from '@/types' @@ -25,7 +26,7 @@ export default ({ user }: Props) => { const [post, setPost] = useState (null) const [editing, setEditing] = useState (true) - const [found, setFound] = useState (true) + const [status, setStatus] = useState (200) const changeViewedFlg = async () => { const url = `${ API_BASE_URL }/posts/${ id }/viewed` @@ -59,8 +60,8 @@ export default ({ user }: Props) => { } catch (err) { - if (err.status === 404) - setFound (false) + if (axios.isAxiosError (err)) + setStatus (err.status ?? 200) } }) () }, [id]) @@ -74,8 +75,13 @@ export default ({ user }: Props) => { setEditing (true) }, [editing]) - if (!(found)) + switch (status) + { + case 404: return + case 503: + return + } const url = post ? new URL (post.url) : null const nicoFlg = url?.hostname.split ('.').slice (-2).join ('.') === 'nicovideo.jp' @@ -88,8 +94,8 @@ export default ({ user }: Props) => { return ( <> - {(post?.thumbnail || post?.thumbnailBase) && - } + {(post?.thumbnail || post?.thumbnailBase) && ( + )} {post && {`${ post.title || post.url } | ${ SITE_TITLE }`}} diff --git a/frontend/src/pages/posts/PostNewPage.tsx b/frontend/src/pages/posts/PostNewPage.tsx index 349ce99..be8d216 100644 --- a/frontend/src/pages/posts/PostNewPage.tsx +++ b/frontend/src/pages/posts/PostNewPage.tsx @@ -11,8 +11,17 @@ import MainArea from '@/components/layout/MainArea' import { Button } from '@/components/ui/button' import { toast } from '@/components/ui/use-toast' import { API_BASE_URL, SITE_TITLE } from '@/config' +import Forbidden from '@/pages/Forbidden' + +import type { User } from '@/types' + +type Props = { user: User | null } + + +export default ({ user }: Props) => { + if (!(['admin', 'member'].some (r => user?.role === r))) + return -export default () => { const navigate = useNavigate () const [title, setTitle] = useState ('') diff --git a/frontend/src/pages/wiki/WikiEditPage.tsx b/frontend/src/pages/wiki/WikiEditPage.tsx index 01dd0ea..064fa1b 100644 --- a/frontend/src/pages/wiki/WikiEditPage.tsx +++ b/frontend/src/pages/wiki/WikiEditPage.tsx @@ -8,15 +8,21 @@ import { useParams, useNavigate } from 'react-router-dom' import MainArea from '@/components/layout/MainArea' import { toast } from '@/components/ui/use-toast' import { API_BASE_URL, SITE_TITLE } from '@/config' +import Forbidden from '@/pages/Forbidden' import 'react-markdown-editor-lite/lib/index.css' -import type { WikiPage } from '@/types' +import type { User, WikiPage } from '@/types' const mdParser = new MarkdownIt +type Props = { user: User | null } + + +export default ({ user }: Props) => { + if (!(['admin', 'member'].some (r => user?.role === r))) + return -export default () => { const { id } = useParams () const navigate = useNavigate () diff --git a/frontend/src/pages/wiki/WikiNewPage.tsx b/frontend/src/pages/wiki/WikiNewPage.tsx index 798beb4..5402162 100644 --- a/frontend/src/pages/wiki/WikiNewPage.tsx +++ b/frontend/src/pages/wiki/WikiNewPage.tsx @@ -8,15 +8,21 @@ import { useLocation, useNavigate } from 'react-router-dom' import MainArea from '@/components/layout/MainArea' import { toast } from '@/components/ui/use-toast' import { API_BASE_URL, SITE_TITLE } from '@/config' +import Forbidden from '@/pages/Forbidden' import 'react-markdown-editor-lite/lib/index.css' -import type { WikiPage } from '@/types' +import type { User, WikiPage } from '@/types' const mdParser = new MarkdownIt +type Props = { user: User | null } + + +export default ({ user }: Props) => { + if (!(['admin', 'member'].some (r => user?.role === r))) + return -export default () => { const location = useLocation () const navigate = useNavigate ()