mirror of
https://github.com/plebbit/seedit.git
synced 2026-02-22 02:14:10 -05:00
feat: add 'not found' page for invalid links
This commit is contained in:
65
src/app.tsx
65
src/app.tsx
@@ -1,14 +1,16 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Outlet, Route, Routes } from 'react-router-dom';
|
||||
import { Outlet, Route, Routes, useParams } from 'react-router-dom';
|
||||
import useTheme from './hooks/use-theme';
|
||||
import { timeFilterNames } from './hooks/use-time-filter';
|
||||
import styles from './app.module.css';
|
||||
import All from './views/all';
|
||||
import Inbox from './views/inbox';
|
||||
import About from './views/about';
|
||||
import Author from './views/author';
|
||||
import Home from './views/home';
|
||||
import Inbox from './views/inbox';
|
||||
import NotFound from './views/not-found';
|
||||
import PendingPost from './views/pending-post';
|
||||
import PostPage from './views/post-page';
|
||||
import About from './views/about/about';
|
||||
import Author from './views/author';
|
||||
import Profile from './views/profile';
|
||||
import Settings from './views/settings';
|
||||
import Submit from './views/submit';
|
||||
@@ -21,7 +23,23 @@ import Header from './components/header';
|
||||
import StickyHeader from './components/sticky-header';
|
||||
import TopBar from './components/topbar';
|
||||
|
||||
function App() {
|
||||
export const sortTypes = ['hot', 'new', 'active', 'controversialAll', 'topAll'];
|
||||
|
||||
const CheckRouteParams = () => {
|
||||
let { sortType, timeFilterName, accountCommentIndex } = useParams<{ sortType?: string; timeFilterName?: string; accountCommentIndex?: string }>();
|
||||
|
||||
const isSortTypeValid = !sortType || sortTypes.includes(sortType);
|
||||
const isTimeFilterNameValid = !timeFilterName || timeFilterNames.includes(timeFilterName as any);
|
||||
const isAccountCommentIndexValid = !accountCommentIndex || !isNaN(parseInt(accountCommentIndex));
|
||||
|
||||
if (!isSortTypeValid || !isTimeFilterNameValid || !isAccountCommentIndexValid) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const [theme] = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -58,25 +76,27 @@ function App() {
|
||||
<Routes>
|
||||
<Route element={globalLayout}>
|
||||
<Route element={feedLayout}>
|
||||
<Route path='/:sortType?' element={<Home />} />
|
||||
<Route path='/:sortType?/:timeFilterName?' element={<Home />} />
|
||||
<Route element={<CheckRouteParams />}>
|
||||
<Route path='/:sortType?' element={<Home />} />
|
||||
<Route path='/:sortType?/:timeFilterName?' element={<Home />} />
|
||||
|
||||
<Route path='/p/all/:sortType?' element={<All />} />
|
||||
<Route path='/p/all/:sortType?/:timeFilterName?' element={<All />} />
|
||||
<Route path='/p/all/:sortType?' element={<All />} />
|
||||
<Route path='/p/all/:sortType?/:timeFilterName?' element={<All />} />
|
||||
|
||||
<Route path='/p/:subplebbitAddress/:sortType?' element={<Subplebbit />} />
|
||||
<Route path='/p/:subplebbitAddress/:sortType?/:timeFilterName?' element={<Subplebbit />} />
|
||||
<Route path='/p/:subplebbitAddress/:sortType?' element={<Subplebbit />} />
|
||||
<Route path='/p/:subplebbitAddress/:sortType?/:timeFilterName?' element={<Subplebbit />} />
|
||||
|
||||
<Route path='/profile/:accountCommentIndex' element={<PostPage />} />
|
||||
<Route path='/profile/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
<Route path='/profile/upvoted/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
<Route path='/profile/downvoted/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
<Route path='/profile/comments/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
<Route path='/profile/submitted/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
<Route path='/profile/:accountCommentIndex' element={<PendingPost />} />
|
||||
<Route path='/profile/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
<Route path='/profile/upvoted/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
<Route path='/profile/downvoted/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
<Route path='/profile/comments/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
<Route path='/profile/submitted/:sortType?/:timeFilterName?' element={<Profile />} />
|
||||
|
||||
<Route path='/u/:authorAddress/c/:commentCid?/:sortType?/:timeFilterName?' element={<Author />} />
|
||||
<Route path='/u/:authorAddress/c/:commentCid?/comments/:sortType?/:timeFilterName?' element={<Author />} />
|
||||
<Route path='/u/:authorAddress/c/:commentCid?/submitted/:sortType?/:timeFilterName?' element={<Author />} />
|
||||
<Route path='/u/:authorAddress/c/:commentCid?/:sortType?/:timeFilterName?' element={<Author />} />
|
||||
<Route path='/u/:authorAddress/c/:commentCid?/comments/:sortType?/:timeFilterName?' element={<Author />} />
|
||||
<Route path='/u/:authorAddress/c/:commentCid?/submitted/:sortType?/:timeFilterName?' element={<Author />} />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route element={pagesLayout}>
|
||||
<Route path='/submit' element={<Submit />} />
|
||||
@@ -94,7 +114,6 @@ function App() {
|
||||
<Route path='/settings' element={<Settings />} />
|
||||
<Route path='/p/:subplebbitAddress/settings' element={<SubplebbitSettings />} />
|
||||
|
||||
<Route path='/profile/:accountCommentIndex' element={<PendingPost />} />
|
||||
<Route path='/profile/about' element={<About />} />
|
||||
|
||||
<Route path='/u/:authorAddress/c/:commentCid/about' element={<About />} />
|
||||
@@ -114,11 +133,13 @@ function App() {
|
||||
<Route path='/communities/vote/rejecting' element={<Subplebbits />} />
|
||||
|
||||
<Route path='/communities/create' element={<SubplebbitSettings />} />
|
||||
|
||||
<Route path='*' element={<NotFound />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getShortAddress } from '@plebbit/plebbit-js';
|
||||
import { useAccount, useAuthorAvatar, useComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import { sortTypes } from '../../app';
|
||||
import {
|
||||
getAboutLink,
|
||||
isAboutView,
|
||||
@@ -35,9 +36,10 @@ import {
|
||||
isProfileUpvotedView,
|
||||
} from '../../lib/utils/view-utils';
|
||||
import useTheme from '../../hooks/use-theme';
|
||||
import { TimeFilterKey } from '../../hooks/use-time-filter';
|
||||
import { useNotFoundStore } from '../../views/not-found';
|
||||
import styles from './header.module.css';
|
||||
import SubscribeButton from '../subscribe-button';
|
||||
import { TimeFilterKey } from '../../hooks/use-time-filter';
|
||||
|
||||
const AboutButton = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -72,8 +74,6 @@ const CommentsButton = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const sortTypes = ['hot', 'new', 'active', 'controversialAll', 'topAll'];
|
||||
|
||||
const SortItems = () => {
|
||||
const { t } = useTranslation();
|
||||
const params = useParams();
|
||||
@@ -291,6 +291,7 @@ const HeaderTitle = ({ title, shortAddress }: { title: string; shortAddress: str
|
||||
const isInSubplebbitSettingsView = isSubplebbitSettingsView(location.pathname, params);
|
||||
const isInSubplebbitsView = isSubplebbitsView(location.pathname);
|
||||
const isInCreateSubplebbitView = isCreateSubplebbitView(location.pathname);
|
||||
const isInNotFoundView = useNotFoundStore((state) => state.isNotFound);
|
||||
|
||||
const subplebbitTitle = <Link to={`/p/${params.subplebbitAddress}`}>{title || shortAddress}</Link>;
|
||||
const submitTitle = <span className={styles.submitTitle}>{t('submit')}</span>;
|
||||
@@ -325,6 +326,8 @@ const HeaderTitle = ({ title, shortAddress }: { title: string; shortAddress: str
|
||||
return <span className={styles.lowercase}>{t('create_community')}</span>;
|
||||
} else if (isInSubplebbitsView) {
|
||||
return t('communities');
|
||||
} else if (isInNotFoundView) {
|
||||
return <span className={styles.lowercase}>{t('page_not_found')}</span>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -350,6 +353,7 @@ const Header = () => {
|
||||
const isInSubmitView = isSubmitView(location.pathname);
|
||||
const isInSubplebbitSubmitView = isSubplebbitSubmitView(location.pathname, params);
|
||||
const isInSubplebbitSettingsView = isSubplebbitSettingsView(location.pathname, params);
|
||||
const isInNotFoundView = useNotFoundStore((state) => state.isNotFound);
|
||||
|
||||
const account = useAccount();
|
||||
const authorComment = useComment({ commentCid: params?.commentCid });
|
||||
@@ -359,6 +363,7 @@ const Header = () => {
|
||||
const hasFewTabs = isInPostView || isInSubmitView || isInSubplebbitSubmitView || isInSubplebbitSettingsView || isInSettingsView || isInInboxView;
|
||||
const hasStickyHeader =
|
||||
isInHomeView ||
|
||||
isInNotFoundView ||
|
||||
(isInSubplebbitView && !isInSubplebbitSubmitView && !isInSubplebbitSettingsView && !isInPostView && !isInAboutView) ||
|
||||
(isInProfileView && !isInAboutView) ||
|
||||
(isInAllView && !isInAllAboutView) ||
|
||||
|
||||
1
src/views/not-found/index.ts
Normal file
1
src/views/not-found/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default, useNotFoundStore } from './not-found';
|
||||
21
src/views/not-found/not-found.module.css
Normal file
21
src/views/not-found/not-found.module.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.content {
|
||||
margin: 7px 5px 0px 5px;
|
||||
}
|
||||
|
||||
.notFound {
|
||||
text-align: center;
|
||||
text-transform: lowercase;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.notFound h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.notFound p {
|
||||
margin: 1em auto;
|
||||
width: 500px;
|
||||
font-size: 13px;
|
||||
}
|
||||
35
src/views/not-found/not-found.tsx
Normal file
35
src/views/not-found/not-found.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { create } from 'zustand';
|
||||
import styles from './not-found.module.css';
|
||||
|
||||
interface NotFoundState {
|
||||
isNotFound: boolean;
|
||||
setNotFound: (isNotFound: boolean) => void;
|
||||
}
|
||||
|
||||
export const useNotFoundStore = create<NotFoundState>((set) => ({
|
||||
isNotFound: false,
|
||||
setNotFound: (isNotFound: boolean) => set({ isNotFound }),
|
||||
}));
|
||||
|
||||
const NotFound = () => {
|
||||
const { t } = useTranslation();
|
||||
const setNotFound = useNotFoundStore((state) => state.setNotFound);
|
||||
|
||||
useEffect(() => {
|
||||
setNotFound(true);
|
||||
return () => setNotFound(false); // Reset on unmount
|
||||
}, [setNotFound]);
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.notFound}>
|
||||
<h1>{t('page_not_found')}</h1>
|
||||
<p>{t('not_found_description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFound;
|
||||
Reference in New Issue
Block a user