Skip to content

Commit

Permalink
Use data loaders, add global progress, fix favorite toggle loading state
Browse files Browse the repository at this point in the history
  • Loading branch information
solomonhawk committed Oct 27, 2024
1 parent 679c552 commit 5700968
Show file tree
Hide file tree
Showing 20 changed files with 443 additions and 114 deletions.
16 changes: 12 additions & 4 deletions apps/web/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@ import { SessionProvider } from "@manifold/auth/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { useState } from "react";
import { RouterProvider } from "react-router-dom";

import { router } from "~features/routing";
import { Router } from "~features/routing";
import { trpc, trpcClient } from "~utils/trpc";

export function App() {
const [queryClient] = useState(() => new QueryClient());
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5,
},
},
}),
);

return (
<SessionProvider>
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<Router />
<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
</QueryClientProvider>
</trpc.Provider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { trpc } from "~utils/trpc";
export function QuickLauncher() {
const [data, query] = trpc.table.favorites.useSuspenseQuery();

// @TODO: error state
if (query.isError) {
// @TODO: error state
console.error(query.error);
}

Expand All @@ -35,8 +35,8 @@ function QuickLaunchTile({ table }: { table: { id: string; title: string } }) {
exit={{ opacity: 0, scale: 0.9 }}
>
<Card>
<CardContent className="!p-0 flex items-center h-full">
<Button asChild className="w-full h-full p-16" variant="link">
<CardContent className="flex h-full items-center !p-0">
<Button asChild className="h-full w-full p-16" variant="link">
<Link to={`/table/${table.id}`}>
<h3>{table.title}</h3>
</Link>
Expand Down
23 changes: 11 additions & 12 deletions apps/web/src/features/dashboard/components/table-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ import { trpc } from "~utils/trpc";
const NOW = new Date();

export function TableList() {
const [data, query] = trpc.table.list.useSuspenseQuery(undefined, {
staleTime: 1000 * 60 * 5,
});
const [data, query] = trpc.table.list.useSuspenseQuery();

// @TODO: error state
if (query.isError) {
console.error(query.error);
return null;
}

return (
Expand All @@ -34,14 +33,14 @@ export function TableList() {

<CardContent
className={cn(
"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-[repeat(auto-fill,minmax(150px,200px))] gap-12 sm:gap-16 transition-opacity",
"grid grid-cols-2 gap-12 transition-opacity sm:grid-cols-3 sm:gap-16 md:grid-cols-[repeat(auto-fill,minmax(150px,200px))]",
{
"opacity-50": query.isRefetching,
},
)}
>
{data.length === 0 && (
<div className="col-span-full text-center text-gray-500 flex gap-16 items-center">
<div className="col-span-full flex items-center gap-16 text-center text-gray-500">
You haven't created any tables yet.
<Button asChild>
<Link to="/table/new">Create a table</Link>
Expand All @@ -51,32 +50,32 @@ export function TableList() {

{data.map((table) => {
return (
<div key={table.id} className="border rounded-sm">
<div className="w-full aspect-square">
<div key={table.id} className="rounded-sm border">
<div className="aspect-square w-full">
<Button
className="group w-full h-full flex flex-col items-center justify-center p-16 gap-6"
className="group flex h-full w-full flex-col items-center justify-center gap-6 p-16"
variant="secondary"
asChild
>
<Link
to={query.isRefetching ? "#" : `/table/${table.id}/edit`}
state={{ table }}
>
<div className="translate-y-14 group-hover:translate-y-0 transition-transform z-20">
<div className="z-20 translate-y-14 transition-transform group-hover:translate-y-0">
<motion.h2
layout="position"
layoutId={`table-title-${table.id}`}
className="text-lg sm:text-xl text-center whitespace-normal !leading-tight"
className="whitespace-normal text-center text-lg !leading-tight sm:text-xl"
transition={transitionBeta}
>
{table.title}
</motion.h2>
</div>

<div className="-translate-y-12 scale-95 opacity-0 group-hover:translate-y-0 group-hover:scale-100 group-hover:opacity-100 transition-all z-10">
<div className="z-10 -translate-y-12 scale-95 opacity-0 transition-all group-hover:translate-y-0 group-hover:scale-100 group-hover:opacity-100">
<motion.span
layoutId={`table-updated-at-${table.id}`}
className="text-gray-500 text-sm text-balance text-center"
className="text-balance text-center text-sm text-gray-500"
transition={transitionBeta}
>
{formatRelative(new Date(table.updatedAt), NOW)}
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/features/dashboard/pages/root/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { loaderBuilder } from "./loader";
export { DashboardRoot } from "./page";
14 changes: 14 additions & 0 deletions apps/web/src/features/dashboard/pages/root/loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type LoaderFunctionArgs } from "react-router-dom";

import type { TrpcUtils } from "~utils/trpc";

export function loaderBuilder(trpcUtils: TrpcUtils) {
return async (_args: LoaderFunctionArgs) => {
await Promise.all([
trpcUtils.table.list.prefetch(),
trpcUtils.table.favorites.prefetch(),
]);

return null;
};
}
4 changes: 1 addition & 3 deletions apps/web/src/features/dashboard/pages/root/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import { TableList } from "~features/dashboard/components/table-list";

export function DashboardRoot() {
return (
<FlexCol className="p-12 sm:p-16 space-y-12 sm:space-y-16">
<FlexCol className="space-y-12 p-12 sm:space-y-16 sm:p-16">
<DashboardHeader />
<TableList />
</FlexCol>
);
}

export const Component = DashboardRoot;
115 changes: 41 additions & 74 deletions apps/web/src/features/routing/index.tsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,43 @@
import { createBrowserRouter, Navigate } from "react-router-dom";
import { GlobalProgress } from "@manifold/ui/components/global-progress";
import { useDeferredValue, useEffect, useState } from "react";
import { RouterProvider } from "react-router-dom";

import { AuthGuard } from "~features/auth/components/auth-guard";
import { GuestGuard } from "~features/auth/components/guest-guard";
import { RootError } from "~features/routing/root/error";
import { RootLayout } from "~features/routing/root/layout";
import { rootLoader } from "~features/routing/root/loader";
import { routerBuilder } from "~features/routing/router";
import { trpc } from "~utils/trpc";

export const router = createBrowserRouter(
[
{
path: "/",
element: <RootLayout />,
loader: rootLoader,
errorElement: <RootError />,
children: [
{
element: <GuestGuard />,
children: [
{
index: true,
lazy: () => import("~features/landing/pages/root/page"),
},
{
path: "/login",
lazy: () => import("~features/auth/pages/login/page"),
},
],
},
{
element: <AuthGuard />,
children: [
{
path: "dashboard",
lazy: () => import("~features/dashboard/pages/root/page"),
},
{
path: "table",
children: [
{
path: "new",
lazy: () => import("~features/table/pages/new/page"),
},
{
path: ":id",
children: [
{
path: "edit",
lazy: () => import("~features/table/pages/edit/page"),
},
{
index: true,
element: <Navigate to="edit" replace />,
},
],
},
],
},
],
},
{
path: "*",
lazy: () => import("~features/routing/root/not-found"),
},
],
},
],
{
future: {
v7_skipActionErrorRevalidation: true,
v7_relativeSplatPath: true,
v7_fetcherPersist: true,
v7_normalizeFormMethod: true,
},
},
);
export function Router() {
const trpcUtils = trpc.useUtils();
const [routerInstance] = useState(() => routerBuilder(trpcUtils));

return (
<>
<NavigationProgress router={routerInstance} />
<RouterProvider router={routerInstance} />
</>
);
}

function NavigationProgress({
router,
}: {
router: ReturnType<typeof routerBuilder>;
}) {
const [isRouteChangePending, setIsRouteChangePending] = useState(false);

/**
* Deferring this allows us to avoid showing the progress bar for very short
* route state changes.
*/
const isPending = useDeferredValue(isRouteChangePending);

useEffect(() => {
return router.subscribe((state) => {
setIsRouteChangePending(
state.navigation.state === "loading" ||
state.navigation.state === "submitting",
);
});
}, [router]);

return <GlobalProgress isAnimating={isPending} />;
}
11 changes: 7 additions & 4 deletions apps/web/src/features/routing/root/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ export function RootLayout() {
const navigate = useNavigate();

return (
<div className="flex flex-col h-full bg-architect">
<div className="bg-architect flex h-full flex-col">
<GlobalHeader.Root>
<div className="flex items-center gap-12">
<Link to="/" className="flex items-center gap-4 group">
<Link
to={auth.status === "authenticated" ? "/dashboard" : "/"}
className="group flex items-center gap-4"
>
<GlobalHeader.LogoMark />

<Badge size="sm" variant="secondary">
Expand All @@ -31,7 +34,7 @@ export function RootLayout() {

<Separator
orientation="vertical"
className="bg-foreground/10 self-stretch h-auto"
className="bg-foreground/10 h-auto self-stretch"
/>

{auth.status === "authenticated" && (
Expand All @@ -48,7 +51,7 @@ export function RootLayout() {

{match(auth)
.with({ status: "loading" }, () => (
<Skeleton className="rounded-full size-avatar-sm sm:size-avatar" />
<Skeleton className="size-avatar-sm sm:size-avatar rounded-full" />
))
.with({ status: "unauthenticated" }, () => (
<GlobalHeader.Unauthed onSignIn={() => signIn("google")} />
Expand Down
Loading

0 comments on commit 5700968

Please sign in to comment.