Commit d55af9b5 authored by Johnny's avatar Johnny

refactor: standardize loading indicators by using Spinner for route fallbacks...

refactor: standardize loading indicators by using Spinner for route fallbacks and specializing Skeleton for memo lists
parent 61e94e8b
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { getInitialTheme, loadTheme } from "@/utils/theme";
import { loadLocale } from "@/utils/i18n"; import { loadLocale } from "@/utils/i18n";
import { getInitialTheme, loadTheme } from "@/utils/theme";
import LocaleSelect from "./LocaleSelect"; import LocaleSelect from "./LocaleSelect";
import ThemeSelect from "./ThemeSelect"; import ThemeSelect from "./ThemeSelect";
......
...@@ -149,7 +149,7 @@ const PagedMemoList = (props: Props) => { ...@@ -149,7 +149,7 @@ const PagedMemoList = (props: Props) => {
<div className="flex flex-col justify-start items-start w-full max-w-full"> <div className="flex flex-col justify-start items-start w-full max-w-full">
{/* Show skeleton loader during initial load */} {/* Show skeleton loader during initial load */}
{isLoading ? ( {isLoading ? (
<Skeleton type="memo" showCreator={props.showCreator} count={4} /> <Skeleton showCreator={props.showCreator} count={4} />
) : ( ) : (
<> <>
<MasonryView <MasonryView
...@@ -166,8 +166,8 @@ const PagedMemoList = (props: Props) => { ...@@ -166,8 +166,8 @@ const PagedMemoList = (props: Props) => {
listMode={layout === "LIST"} listMode={layout === "LIST"}
/> />
{/* Loading indicator for pagination - use skeleton for content consistency */} {/* Loading indicator for pagination */}
{isFetchingNextPage && <Skeleton type="pagination" showCreator={props.showCreator} count={2} />} {isFetchingNextPage && <Skeleton showCreator={props.showCreator} count={2} />}
{/* Empty state or back-to-top button */} {/* Empty state or back-to-top button */}
{!isFetchingNextPage && ( {!isFetchingNextPage && (
......
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface Props { interface Props {
type?: "route" | "memo" | "pagination";
showCreator?: boolean; showCreator?: boolean;
count?: number; count?: number;
showEditor?: boolean;
} }
// Memo card skeleton component // Memo card skeleton component for list loading states
const MemoCardSkeleton = ({ showCreator = false, index = 0 }: { showCreator?: boolean; index?: number }) => ( const MemoCardSkeleton = ({ showCreator = false, index = 0 }: { showCreator?: boolean; index?: number }) => (
<div className="relative flex flex-col justify-start items-start bg-card w-full px-4 py-3 mb-2 gap-2 rounded-lg border border-border animate-pulse"> <div className="relative flex flex-col justify-start items-start bg-card w-full px-4 py-3 mb-2 gap-2 rounded-lg border border-border animate-pulse">
{/* Header section */} {/* Header section */}
...@@ -43,36 +41,17 @@ const MemoCardSkeleton = ({ showCreator = false, index = 0 }: { showCreator?: bo ...@@ -43,36 +41,17 @@ const MemoCardSkeleton = ({ showCreator = false, index = 0 }: { showCreator?: bo
</div> </div>
); );
const Skeleton = ({ type = "route", showCreator = false, count = 4, showEditor = true }: Props) => { /**
// Pagination type: simpler, just memos * Skeleton loading state for memo lists.
if (type === "pagination") { * Use this for initial memo list loading and pagination.
return ( * For generic page/route loading, use Spinner instead.
<div className="w-full flex flex-col justify-center items-center my-4"> */
<div className="w-full max-w-2xl mx-auto"> const Skeleton = ({ showCreator = false, count = 4 }: Props) => (
{Array.from({ length: count }).map((_, index) => ( <div className="w-full max-w-2xl mx-auto">
<MemoCardSkeleton key={index} showCreator={showCreator} index={index} /> {Array.from({ length: count }).map((_, index) => (
))} <MemoCardSkeleton key={index} showCreator={showCreator} index={index} />
</div> ))}
</div> </div>
); );
}
// Route or memo type: with optional wrapper
return (
<div className="w-full max-w-2xl mx-auto">
{/* Editor skeleton - only for route type */}
{type === "route" && showEditor && (
<div className="relative flex flex-col justify-start items-start bg-card w-full px-4 py-3 mb-4 gap-2 rounded-lg border border-border animate-pulse">
<div className="w-full h-12 bg-muted rounded" />
</div>
)}
{/* Memo skeletons */}
{Array.from({ length: count }).map((_, index) => (
<MemoCardSkeleton key={index} showCreator={showCreator} index={index} />
))}
</div>
);
};
export default Skeleton; export default Skeleton;
...@@ -2,7 +2,7 @@ import { Suspense, useEffect, useMemo } from "react"; ...@@ -2,7 +2,7 @@ import { Suspense, useEffect, useMemo } from "react";
import { Outlet, useLocation, useSearchParams } from "react-router-dom"; import { Outlet, useLocation, useSearchParams } from "react-router-dom";
import usePrevious from "react-use/lib/usePrevious"; import usePrevious from "react-use/lib/usePrevious";
import Navigation from "@/components/Navigation"; import Navigation from "@/components/Navigation";
import Skeleton from "@/components/Skeleton"; import Spinner from "@/components/Spinner";
import { useInstance } from "@/contexts/InstanceContext"; import { useInstance } from "@/contexts/InstanceContext";
import { useMemoFilterContext } from "@/contexts/MemoFilterContext"; import { useMemoFilterContext } from "@/contexts/MemoFilterContext";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
...@@ -47,7 +47,13 @@ const RootLayout = () => { ...@@ -47,7 +47,13 @@ const RootLayout = () => {
</div> </div>
)} )}
<main className="w-full h-auto grow shrink flex flex-col justify-start items-center"> <main className="w-full h-auto grow shrink flex flex-col justify-start items-center">
<Suspense fallback={<Skeleton type="route" />}> <Suspense
fallback={
<div className="w-full h-64 flex items-center justify-center">
<Spinner size="lg" />
</div>
}
>
<Outlet /> <Outlet />
</Suspense> </Suspense>
</main> </main>
......
...@@ -8,6 +8,7 @@ import { RouterProvider } from "react-router-dom"; ...@@ -8,6 +8,7 @@ import { RouterProvider } from "react-router-dom";
import "./i18n"; import "./i18n";
import "./index.css"; import "./index.css";
import { ErrorBoundary } from "@/components/ErrorBoundary"; import { ErrorBoundary } from "@/components/ErrorBoundary";
import Spinner from "@/components/Spinner";
import { AuthProvider, useAuth } from "@/contexts/AuthContext"; import { AuthProvider, useAuth } from "@/contexts/AuthContext";
import { InstanceProvider, useInstance } from "@/contexts/InstanceContext"; import { InstanceProvider, useInstance } from "@/contexts/InstanceContext";
import { ViewProvider } from "@/contexts/ViewContext"; import { ViewProvider } from "@/contexts/ViewContext";
...@@ -39,7 +40,11 @@ function AppInitializer({ children }: { children: React.ReactNode }) { ...@@ -39,7 +40,11 @@ function AppInitializer({ children }: { children: React.ReactNode }) {
}, [initAuth, initInstance]); }, [initAuth, initInstance]);
if (!authInitialized || !instanceInitialized) { if (!authInitialized || !instanceInitialized) {
return undefined; return (
<div className="w-full h-screen flex items-center justify-center">
<Spinner size="lg" />
</div>
);
} }
return <>{children}</>; return <>{children}</>;
......
import type { ComponentType } from "react";
import { lazy, Suspense } from "react"; import { lazy, Suspense } from "react";
import { createBrowserRouter } from "react-router-dom"; import { createBrowserRouter } from "react-router-dom";
import App from "@/App"; import App from "@/App";
import Skeleton from "@/components/Skeleton"; import Spinner from "@/components/Spinner";
import MainLayout from "@/layouts/MainLayout"; import MainLayout from "@/layouts/MainLayout";
import RootLayout from "@/layouts/RootLayout"; import RootLayout from "@/layouts/RootLayout";
import Home from "@/pages/Home"; import Home from "@/pages/Home";
...@@ -27,6 +28,19 @@ import { ROUTES } from "./routes"; ...@@ -27,6 +28,19 @@ import { ROUTES } from "./routes";
export const Routes = ROUTES; export const Routes = ROUTES;
export { ROUTES }; export { ROUTES };
// Helper component to reduce Suspense boilerplate for lazy routes
const LazyRoute = ({ component: Component }: { component: ComponentType }) => (
<Suspense
fallback={
<div className="w-full h-64 flex items-center justify-center">
<Spinner size="lg" />
</div>
}
>
<Component />
</Suspense>
);
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
path: "/", path: "/",
...@@ -35,38 +49,10 @@ const router = createBrowserRouter([ ...@@ -35,38 +49,10 @@ const router = createBrowserRouter([
{ {
path: Routes.AUTH, path: Routes.AUTH,
children: [ children: [
{ { path: "", element: <LazyRoute component={SignIn} /> },
path: "", { path: "admin", element: <LazyRoute component={AdminSignIn} /> },
element: ( { path: "signup", element: <LazyRoute component={SignUp} /> },
<Suspense fallback={<Skeleton type="route" showEditor={false} />}> { path: "callback", element: <LazyRoute component={AuthCallback} /> },
<SignIn />
</Suspense>
),
},
{
path: "admin",
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<AdminSignIn />
</Suspense>
),
},
{
path: "signup",
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<SignUp />
</Suspense>
),
},
{
path: "callback",
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<AuthCallback />
</Suspense>
),
},
], ],
}, },
{ {
...@@ -76,101 +62,21 @@ const router = createBrowserRouter([ ...@@ -76,101 +62,21 @@ const router = createBrowserRouter([
{ {
element: <MainLayout />, element: <MainLayout />,
children: [ children: [
{ { path: "", element: <Home /> },
path: "", { path: Routes.EXPLORE, element: <LazyRoute component={Explore} /> },
element: <Home />, { path: Routes.ARCHIVED, element: <LazyRoute component={Archived} /> },
}, { path: "u/:username", element: <LazyRoute component={UserProfile} /> },
{
path: Routes.EXPLORE,
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<Explore />
</Suspense>
),
},
{
path: Routes.ARCHIVED,
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<Archived />
</Suspense>
),
},
{
path: "u/:username",
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<UserProfile />
</Suspense>
),
},
], ],
}, },
{ { path: Routes.ATTACHMENTS, element: <LazyRoute component={Attachments} /> },
path: Routes.ATTACHMENTS, { path: Routes.INBOX, element: <LazyRoute component={Inboxes} /> },
element: ( { path: Routes.SETTING, element: <LazyRoute component={Setting} /> },
<Suspense fallback={<Skeleton type="route" showEditor={false} />}> { path: "memos/:uid", element: <LazyRoute component={MemoDetail} /> },
<Attachments /> // Redirect old path to new path
</Suspense> { path: "m/:uid", element: <LazyRoute component={MemoDetailRedirect} /> },
), { path: "403", element: <LazyRoute component={PermissionDenied} /> },
}, { path: "404", element: <LazyRoute component={NotFound} /> },
{ { path: "*", element: <LazyRoute component={NotFound} /> },
path: Routes.INBOX,
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<Inboxes />
</Suspense>
),
},
{
path: Routes.SETTING,
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<Setting />
</Suspense>
),
},
{
path: "memos/:uid",
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<MemoDetail />
</Suspense>
),
},
// Redirect old path to new path.
{
path: "m/:uid",
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<MemoDetailRedirect />
</Suspense>
),
},
{
path: "403",
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<PermissionDenied />
</Suspense>
),
},
{
path: "404",
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<NotFound />
</Suspense>
),
},
{
path: "*",
element: (
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
<NotFound />
</Suspense>
),
},
], ],
}, },
], ],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment