fix: add 404 not found page (#349)
* fix: add 404 not found page Closes #320 - Add custom 404 page matching the app's design system - Include PatternedBackground and proper dark mode support - Provide navigation links to homepage and boards - Add i18n support with @lingui/react * fix: add 404 redirects for non-existent boards and cards Add useEffect hooks to redirect to /404 page when: - Board does not exist in board view - Card does not exist in card view This addresses the cases mentioned in maintainer feedback where the static 404 page wasn't being used for dynamic routes. Addresses review feedback on PR #349 * feat: redirect to 404 for non-existent public workspaces and cards Per maintainer feedback: - Add redirect logic to public boards view when workspace doesn't exist - Add redirect logic to public card modal when card doesn't exist - Uses error?.data?.code === 'NOT_FOUND' check as suggested - Closes modal before redirecting for card modal
This commit is contained in:
55
apps/web/src/pages/404.tsx
Normal file
55
apps/web/src/pages/404.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import Link from "next/link";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
|
||||
import { PageHead } from "~/components/PageHead";
|
||||
import PatternedBackground from "~/components/PatternedBackground";
|
||||
|
||||
export default function NotFoundPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHead title={t`404 - Page Not Found | kan.bn`} />
|
||||
<main className="h-screen bg-light-100 pt-20 dark:bg-dark-50 sm:pt-0">
|
||||
<div className="justify-top flex h-full flex-col items-center px-4 sm:justify-center">
|
||||
<div className="z-10 flex w-full flex-col items-center">
|
||||
<Link href="/">
|
||||
<h1 className="mb-6 text-lg font-bold tracking-tight text-light-1000 dark:text-dark-1000">
|
||||
kan.bn
|
||||
</h1>
|
||||
</Link>
|
||||
<p className="mb-4 text-8xl font-bold tracking-tight text-light-1000 dark:text-dark-1000">
|
||||
404
|
||||
</p>
|
||||
<p className="mb-10 text-3xl font-bold tracking-tight text-light-1000 dark:text-dark-1000">
|
||||
<Trans>Page not found</Trans>
|
||||
</p>
|
||||
<div className="w-full rounded-lg border border-light-500 bg-light-300 px-4 py-10 dark:border-dark-400 dark:bg-dark-200 sm:max-w-md lg:px-10">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<p className="mb-6 text-center text-light-900 dark:text-dark-900">
|
||||
<Trans>
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</Trans>
|
||||
</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex w-full justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-100 shadow-sm hover:bg-light-900 dark:bg-dark-1000 dark:text-dark-100 dark:hover:bg-dark-900"
|
||||
>
|
||||
<Trans>Go to homepage</Trans>
|
||||
</Link>
|
||||
<Link
|
||||
href="/boards"
|
||||
className="flex w-full justify-center rounded-md border border-light-500 bg-light-100 px-3 py-2 text-sm font-semibold text-light-1000 shadow-sm hover:bg-light-200 dark:border-dark-400 dark:bg-dark-100 dark:text-dark-1000 dark:hover:bg-dark-300"
|
||||
>
|
||||
<Trans>Go to boards</Trans>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PatternedBackground />
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -125,11 +125,21 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
data: boardData,
|
||||
isSuccess,
|
||||
isLoading: isQueryLoading,
|
||||
error,
|
||||
} = api.board.byId.useQuery(queryParams, {
|
||||
enabled: !!boardId,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
// Redirect to 404 if board doesn't exist
|
||||
useEffect(() => {
|
||||
if (router.isReady && boardId && !isQueryLoading) {
|
||||
if (error?.data?.code === "NOT_FOUND" || (!boardData && !isQueryLoading)) {
|
||||
router.replace("/404");
|
||||
}
|
||||
}
|
||||
}, [router, boardId, isQueryLoading, error, boardData]);
|
||||
|
||||
const refetchBoard = async () => {
|
||||
if (boardId) await utils.board.byId.refetch({ boardPublicId: boardId });
|
||||
};
|
||||
|
||||
@@ -185,11 +185,20 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
||||
? router.query.cardId[0]
|
||||
: router.query.cardId;
|
||||
|
||||
const { data: card, isLoading } = api.card.byId.useQuery(
|
||||
const { data: card, isLoading, error } = api.card.byId.useQuery(
|
||||
{ cardPublicId: cardId ?? "" },
|
||||
{ enabled: !!cardId && cardId.length >= 12 },
|
||||
);
|
||||
|
||||
// Redirect to 404 if card doesn't exist
|
||||
useEffect(() => {
|
||||
if (router.isReady && cardId && !isLoading) {
|
||||
if (error?.data?.code === "NOT_FOUND" || (!card && !isLoading)) {
|
||||
router.replace("/404");
|
||||
}
|
||||
}
|
||||
}, [router, cardId, isLoading, error, card]);
|
||||
|
||||
const isCreator = card?.createdBy && session?.user.id === card.createdBy;
|
||||
const canEdit = canEditCard || isCreator;
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export function CardModal({
|
||||
}
|
||||
};
|
||||
|
||||
const { data, isLoading } = api.card.byId.useQuery(
|
||||
const { data, isLoading, error } = api.card.byId.useQuery(
|
||||
{
|
||||
cardPublicId: cardPublicId ?? "",
|
||||
},
|
||||
@@ -56,6 +56,17 @@ export function CardModal({
|
||||
},
|
||||
);
|
||||
|
||||
// Redirect to 404 if card doesn't exist
|
||||
useEffect(() => {
|
||||
if (isOpen && cardPublicId && !isLoading) {
|
||||
if (error?.data?.code === "NOT_FOUND" || (!data && !isLoading && error)) {
|
||||
// Close modal first, then redirect
|
||||
closeModal();
|
||||
router.replace("/404");
|
||||
}
|
||||
}
|
||||
}, [isOpen, cardPublicId, isLoading, error, data, closeModal, router]);
|
||||
|
||||
const labels = data?.labels ?? [];
|
||||
|
||||
const handleScroll = () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { PageHead } from "~/components/PageHead";
|
||||
import PatternedBackground from "~/components/PatternedBackground";
|
||||
@@ -12,13 +13,22 @@ export default function PublicBoardsView() {
|
||||
? router.query.workspaceSlug[0]
|
||||
: router.query.workspaceSlug;
|
||||
|
||||
const { data, isLoading } = api.workspace.bySlug.useQuery(
|
||||
const { data, isLoading, error } = api.workspace.bySlug.useQuery(
|
||||
{
|
||||
workspaceSlug: workspaceSlug ?? "",
|
||||
},
|
||||
{ enabled: !!workspaceSlug },
|
||||
);
|
||||
|
||||
// Redirect to 404 if workspace doesn't exist
|
||||
useEffect(() => {
|
||||
if (router.isReady && workspaceSlug && !isLoading) {
|
||||
if (error?.data?.code === "NOT_FOUND" || (!data && !isLoading)) {
|
||||
router.replace("/404");
|
||||
}
|
||||
}
|
||||
}, [router, workspaceSlug, isLoading, error, data]);
|
||||
|
||||
const BoardsList = ({
|
||||
isLoading,
|
||||
boards,
|
||||
|
||||
Reference in New Issue
Block a user