Skip to content

Commit

Permalink
Refactor route loader pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
solomonhawk committed Nov 13, 2024
1 parent 4a2401c commit b483e3f
Show file tree
Hide file tree
Showing 15 changed files with 341 additions and 99 deletions.
2 changes: 2 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@trpc/react-query": "^10.45.2",
"comlink": "^4.4.1",
"date-fns": "^4.1.0",
"diff": "^7.0.0",
"fast-deep-equal": "^3.1.3",
"framer-motion": "^11.11.10",
"jotai": "^2.10.0",
Expand All @@ -52,6 +53,7 @@
"@manifold/eslint-config": "*",
"@manifold/tailwind-config": "*",
"@manifold/typescript-config": "*",
"@types/diff": "^6.0.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,14 @@ export function TableList({
title={table.title}
>
<div className="z-20 translate-y-14 transition-transform group-hover:translate-y-0">
<h2 className="line-clamp-2 whitespace-normal text-center text-sm !leading-tight sm:text-base md:text-lg">
<h2
className={cn(
"line-clamp-2 whitespace-normal text-center text-sm !leading-tight sm:text-base md:text-lg",
{
"line-through": table.deletedAt !== null,
},
)}
>
{table.title}
</h2>
</div>
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/features/dashboard/pages/root/lazy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { DashboardLoaderData } from "~features/dashboard/pages/root/loader";
import type { Handle, LazyRoute } from "~features/routing/types";
import type { TrpcUtils } from "~utils/trpc";

export function loadDashboardRoute(trpcUtils: TrpcUtils): LazyRoute {
return async () => {
const { DashboardRoot, loaderBuilder } = await import(
"~features/dashboard/pages/root"
);

return {
loader: loaderBuilder(trpcUtils),
Component: DashboardRoot,
handle: {
title: () => "Manifold | Dashboard",
description: () => "Where it all begins.",
} satisfies Handle<DashboardLoaderData>,
};
};
}
24 changes: 12 additions & 12 deletions apps/web/src/features/routing/components/prefetchable-link.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useComposedRefs } from "@radix-ui/react-compose-refs";
import { useAtomValue } from "jotai";
import { useCallback, useEffect, useRef } from "react";
import { forwardRef, useCallback, useEffect, useRef } from "react";
import { Link, type LinkProps, matchRoutes } from "react-router-dom";

import { routesAtom } from "~features/routing/state";
Expand All @@ -21,18 +22,16 @@ type PrefetchableLinkProps = LinkProps &
* @NOTE: Does not support following loader redirects and prefetching those
* routes.
*/
export function PrefetchableLink({
children,
to,
mode = "intent",
wait = 250,
...props
}: PrefetchableLinkProps) {
export const PrefetchableLink = forwardRef<
HTMLAnchorElement,
PrefetchableLinkProps
>(({ children, to, mode = "intent", wait = 250, ...props }, ref) => {
if (import.meta.env.DEV && wait < 0) {
log.warn("PrefecthableLink: `wait` must be a positive number");
}

const ref = useRef<HTMLAnchorElement | null>(null);
const linkRef = useRef<HTMLAnchorElement | null>(null);
const combinedRef = useComposedRefs(ref, linkRef);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const routes = useAtomValue(routesAtom);

Expand Down Expand Up @@ -119,7 +118,7 @@ export function PrefetchableLink({
}
});

const currentTarget = ref.current;
const currentTarget = linkRef.current;

if (currentTarget) {
observer.observe(currentTarget);
Expand All @@ -134,8 +133,8 @@ export function PrefetchableLink({

return (
<Link
ref={ref}
to={to}
ref={combinedRef}
{...props}
onMouseEnter={handleIntent}
onMouseLeave={handleUnintent}
Expand All @@ -145,4 +144,5 @@ export function PrefetchableLink({
{children}
</Link>
);
}
});
PrefetchableLink.displayName = "PrefetchableLink";
95 changes: 12 additions & 83 deletions apps/web/src/features/routing/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import {
} from "react-router-dom";

import { AuthGuard } from "~features/auth/components/auth-guard";
import { loadDashboardRoute } from "~features/dashboard/pages/root/lazy";
import { userIsOnboarded } from "~features/onboarding/helpers";
import { RootError } from "~features/routing/pages/root/error";
import { RootLayout } from "~features/routing/pages/root/layout";
import type { Handle } from "~features/routing/types";
import type { TableDetailLoaderData } from "~features/table/pages/detail";
import type { TableEditLoaderData } from "~features/table/pages/edit";
import type { TableVersionDetailLoaderData } from "~features/table-version/pages/detail";
import type { TableVersionsSearchBrowseLoaderData } from "~features/table-version/pages/search-browse";
import { loadTableDetailRoute } from "~features/table/pages/detail/lazy";
import { loadTableEditRoute } from "~features/table/pages/edit/lazy";
import { loadTableNewRoute } from "~features/table/pages/new/lazy";
import { loadTableVersionDetailRoute } from "~features/table-version/pages/detail/lazy";
import { loadTableVersionSearchBrowseRoute } from "~features/table-version/pages/search-browse/lazy";
import type { TrpcUtils } from "~utils/trpc";

export function buildAppRoutes({
Expand Down Expand Up @@ -82,20 +84,7 @@ export function buildAppRoutes({
children: [
{
index: true,
lazy: async () => {
const { DashboardRoot, loaderBuilder } = await import(
"~features/dashboard/pages/root"
);

return {
loader: loaderBuilder(trpcUtils),
Component: DashboardRoot,
};
},
handle: {
title: () => "Manifold | Dashboard",
description: () => "Where it all begins.",
} satisfies Handle,
lazy: loadDashboardRoute(trpcUtils),
},
],
},
Expand All @@ -114,57 +103,15 @@ export function buildAppRoutes({
children: [
{
index: true,
lazy: async () => {
const { TableDetail, loaderBuilder } = await import(
"~features/table/pages/detail"
);

return {
loader: loaderBuilder(trpcUtils),
Component: TableDetail,
};
},
handle: {
title: ({ data }) => `Manifold | ${data.title}`,
description: ({ data }) =>
data.description ?? `Details of ${data.title}.`,
} satisfies Handle<TableDetailLoaderData>,
lazy: loadTableDetailRoute(trpcUtils),
},
{
path: "v/:version",
lazy: async () => {
const { TableVersionDetail, loaderBuilder } = await import(
"~features/table-version/pages/detail"
);

return {
loader: loaderBuilder(trpcUtils),
Component: TableVersionDetail,
};
},
handle: {
title: ({ data }) => `Manifold | ${data.table.title}`,
description: ({ data }) =>
data.table.description ??
`Details of ${data.table.title}.`,
} satisfies Handle<TableVersionDetailLoaderData>,
lazy: loadTableVersionDetailRoute(trpcUtils),
},
{
path: "edit",
lazy: async () => {
const { TableEdit, loaderBuilder } = await import(
"~features/table/pages/edit"
);

return {
loader: loaderBuilder(trpcUtils),
Component: TableEdit,
};
},
handle: {
title: ({ data }) => `Manifold | Edit ${data.title}`,
description: ({ data }) => `Edit ${data.title} table.`,
} satisfies Handle<TableEditLoaderData>,
lazy: loadTableEditRoute(trpcUtils),
},
{
index: true,
Expand All @@ -186,29 +133,11 @@ export function buildAppRoutes({
},
{
path: "new",
lazy: () => import("~features/table/pages/new/page"),
handle: {
title: () => "Manifold | New Table",
description: () => "Create a new random table.",
} satisfies Handle,
lazy: loadTableNewRoute(),
},
{
path: "discover",
lazy: async () => {
const { TableVersionsSearchBrowse, loaderBuilder } =
await import("~features/table-version/pages/search-browse");

return {
loader: loaderBuilder(trpcUtils),
Component: TableVersionsSearchBrowse,
};
},
handle: {
title: ({ data }) =>
`Manifold | Browse ${data.pagination.totalItems} Tables`,
description: () =>
`Search and browse to discover exactly what you need.`,
} satisfies Handle<TableVersionsSearchBrowseLoaderData>,
lazy: loadTableVersionSearchBrowseRoute(trpcUtils),
},
],
},
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/features/routing/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Params } from "react-router-dom";
import type { LazyRouteFunction, Params, RouteObject } from "react-router-dom";

export type Handle<Data = unknown> = {
title?: (args: { params: Params; data: Data }) => string;
description?: (args: { params: Params; data: Data }) => string;
};

export type LazyRoute = LazyRouteFunction<RouteObject>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { useModal } from "@ebay/nice-modal-react";
import type { RouterOutput } from "@manifold/router";
import { ReactiveButton } from "@manifold/ui/components/reactive-button";
import { TableIdentifier } from "@manifold/ui/components/table-identifier";
import { Button } from "@manifold/ui/components/ui/button";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
} from "@manifold/ui/components/ui/drawer";
import { GoX } from "react-icons/go";

type Props = {
dependency: RouterOutput["table"]["findDependencies"][number];
onAddDependency: () => void;
canAddDependency: boolean;
};

export function CompareVersionsDialog({
dependency,
onAddDependency,
canAddDependency,
}: Props) {
const modal = useModal();

return (
<Drawer
open={modal.visible}
onClose={() => modal.hide()}
onAnimationEnd={() => modal.remove()}
shouldScaleBackground
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
>
<DrawerContent>
<div className="w-full flex-1 overflow-y-auto">
<div className="mx-auto w-full max-w-xl">
<DrawerHeader>
<DrawerTitle className="my-16">
<TableIdentifier tableIdentifier={dependency.tableIdentifier} />
</DrawerTitle>

<DrawerDescription className="sr-only">
{dependency.table?.description || "No table description"}
</DrawerDescription>
</DrawerHeader>

<section className="px-16">
<dl className="mb-16 grid grid-cols-[min-content,_1fr] border-l border-t text-sm">
<dt className="border-b border-r px-10 py-8 font-semibold text-muted-foreground">
Description
</dt>
<dd className="border-b border-r px-10 py-8">
{dependency.table?.description || (
<em className="text-muted-foreground">
No table description
</em>
)}
</dd>

<dt className="border-b border-r px-10 py-8 font-semibold text-muted-foreground">
Last&nbsp;updated
</dt>
<dd className="border-b border-r px-10 py-8">
{dependency.createdAt.toLocaleDateString()}
</dd>

<dt className="border-b border-r px-10 py-8 font-semibold text-muted-foreground">
Version
</dt>
<dd className="border-b border-r px-10 py-8">
{dependency.version}
</dd>

<dt className="border-b border-r px-10 py-8 font-semibold text-muted-foreground">
Release&nbsp;notes
</dt>
<dd className="border-b border-r px-10 py-8">
{dependency.releaseNotes || (
<em className="text-muted-foreground">No release notes</em>
)}
</dd>
<dt className="border-b border-r px-10 py-8 font-semibold text-muted-foreground">
Available&nbsp;Tables
</dt>
<dd className="flex flex-wrap gap-4 border-b border-r px-10 py-8">
{dependency.availableTables.map((tableId) => (
<code
key={tableId}
className="rounded bg-secondary p-3 px-6 text-xs leading-none text-accent-foreground"
>
{tableId}
</code>
))}
</dd>
</dl>

<div className="rounded border">
<pre className="max-h-256 overflow-auto px-16 py-12 text-xs leading-tight">
{dependency.definition}
</pre>
</div>
</section>

<DrawerFooter>
<ReactiveButton
onClick={() => {
modal.hide();
onAddDependency();
}}
disabled={!canAddDependency}
>
{canAddDependency
? "Add this dependency"
: "Dependency already added"}
</ReactiveButton>
</DrawerFooter>
</div>
</div>

<DrawerClose asChild>
<Button
size="icon"
variant="ghost"
className="absolute right-16 top-16"
>
<span className="sr-only">Dismiss drawer</span>
<GoX />
</Button>
</DrawerClose>
</DrawerContent>
</Drawer>
);
}
Loading

0 comments on commit b483e3f

Please sign in to comment.