feat: add 'not found' page for invalid links

This commit is contained in:
plebeius.eth
2024-02-14 11:37:07 +01:00
parent 52db615ef8
commit e8ee4963da
5 changed files with 108 additions and 25 deletions

View File

@@ -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;

View File

@@ -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) ||

View File

@@ -0,0 +1 @@
export { default, useNotFoundStore } from './not-found';

View 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;
}

View 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;