diff --git a/web/src/components/AuthFooter.tsx b/web/src/components/AuthFooter.tsx index 8431aaa99..f1a115f71 100644 --- a/web/src/components/AuthFooter.tsx +++ b/web/src/components/AuthFooter.tsx @@ -1,7 +1,7 @@ import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; -import { getInitialTheme, loadTheme } from "@/utils/theme"; import { loadLocale } from "@/utils/i18n"; +import { getInitialTheme, loadTheme } from "@/utils/theme"; import LocaleSelect from "./LocaleSelect"; import ThemeSelect from "./ThemeSelect"; diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx index d0823c678..20b46215e 100644 --- a/web/src/components/PagedMemoList/PagedMemoList.tsx +++ b/web/src/components/PagedMemoList/PagedMemoList.tsx @@ -149,7 +149,7 @@ const PagedMemoList = (props: Props) => {
{/* Show skeleton loader during initial load */} {isLoading ? ( - + ) : ( <> { listMode={layout === "LIST"} /> - {/* Loading indicator for pagination - use skeleton for content consistency */} - {isFetchingNextPage && } + {/* Loading indicator for pagination */} + {isFetchingNextPage && } {/* Empty state or back-to-top button */} {!isFetchingNextPage && ( diff --git a/web/src/components/Skeleton.tsx b/web/src/components/Skeleton.tsx index 1de83ecf2..945144057 100644 --- a/web/src/components/Skeleton.tsx +++ b/web/src/components/Skeleton.tsx @@ -1,13 +1,11 @@ import { cn } from "@/lib/utils"; interface Props { - type?: "route" | "memo" | "pagination"; showCreator?: boolean; 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 }) => (
{/* Header section */} @@ -43,36 +41,17 @@ const MemoCardSkeleton = ({ showCreator = false, index = 0 }: { showCreator?: bo
); -const Skeleton = ({ type = "route", showCreator = false, count = 4, showEditor = true }: Props) => { - // Pagination type: simpler, just memos - if (type === "pagination") { - return ( -
-
- {Array.from({ length: count }).map((_, index) => ( - - ))} -
-
- ); - } - - // Route or memo type: with optional wrapper - return ( -
- {/* Editor skeleton - only for route type */} - {type === "route" && showEditor && ( -
-
-
- )} - - {/* Memo skeletons */} - {Array.from({ length: count }).map((_, index) => ( - - ))} -
- ); -}; +/** + * Skeleton loading state for memo lists. + * Use this for initial memo list loading and pagination. + * For generic page/route loading, use Spinner instead. + */ +const Skeleton = ({ showCreator = false, count = 4 }: Props) => ( +
+ {Array.from({ length: count }).map((_, index) => ( + + ))} +
+); export default Skeleton; diff --git a/web/src/layouts/RootLayout.tsx b/web/src/layouts/RootLayout.tsx index 69c2a172e..7a296ff37 100644 --- a/web/src/layouts/RootLayout.tsx +++ b/web/src/layouts/RootLayout.tsx @@ -2,7 +2,7 @@ import { Suspense, useEffect, useMemo } from "react"; import { Outlet, useLocation, useSearchParams } from "react-router-dom"; import usePrevious from "react-use/lib/usePrevious"; import Navigation from "@/components/Navigation"; -import Skeleton from "@/components/Skeleton"; +import Spinner from "@/components/Spinner"; import { useInstance } from "@/contexts/InstanceContext"; import { useMemoFilterContext } from "@/contexts/MemoFilterContext"; import useCurrentUser from "@/hooks/useCurrentUser"; @@ -47,7 +47,13 @@ const RootLayout = () => {
)}
- }> + + +
+ } + > diff --git a/web/src/main.tsx b/web/src/main.tsx index 9bc61fc4d..39365f23c 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -8,6 +8,7 @@ import { RouterProvider } from "react-router-dom"; import "./i18n"; import "./index.css"; import { ErrorBoundary } from "@/components/ErrorBoundary"; +import Spinner from "@/components/Spinner"; import { AuthProvider, useAuth } from "@/contexts/AuthContext"; import { InstanceProvider, useInstance } from "@/contexts/InstanceContext"; import { ViewProvider } from "@/contexts/ViewContext"; @@ -39,7 +40,11 @@ function AppInitializer({ children }: { children: React.ReactNode }) { }, [initAuth, initInstance]); if (!authInitialized || !instanceInitialized) { - return undefined; + return ( +
+ +
+ ); } return <>{children}; diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx index 5bf27269f..fc4fdd26f 100644 --- a/web/src/router/index.tsx +++ b/web/src/router/index.tsx @@ -1,7 +1,8 @@ +import type { ComponentType } from "react"; import { lazy, Suspense } from "react"; import { createBrowserRouter } from "react-router-dom"; import App from "@/App"; -import Skeleton from "@/components/Skeleton"; +import Spinner from "@/components/Spinner"; import MainLayout from "@/layouts/MainLayout"; import RootLayout from "@/layouts/RootLayout"; import Home from "@/pages/Home"; @@ -27,6 +28,19 @@ import { ROUTES } from "./routes"; export const Routes = ROUTES; export { ROUTES }; +// Helper component to reduce Suspense boilerplate for lazy routes +const LazyRoute = ({ component: Component }: { component: ComponentType }) => ( + + + + } + > + + +); + const router = createBrowserRouter([ { path: "/", @@ -35,38 +49,10 @@ const router = createBrowserRouter([ { path: Routes.AUTH, children: [ - { - path: "", - element: ( - }> - - - ), - }, - { - path: "admin", - element: ( - }> - - - ), - }, - { - path: "signup", - element: ( - }> - - - ), - }, - { - path: "callback", - element: ( - }> - - - ), - }, + { path: "", element: }, + { path: "admin", element: }, + { path: "signup", element: }, + { path: "callback", element: }, ], }, { @@ -76,101 +62,21 @@ const router = createBrowserRouter([ { element: , children: [ - { - path: "", - element: , - }, - { - path: Routes.EXPLORE, - element: ( - }> - - - ), - }, - { - path: Routes.ARCHIVED, - element: ( - }> - - - ), - }, - { - path: "u/:username", - element: ( - }> - - - ), - }, + { path: "", element: }, + { path: Routes.EXPLORE, element: }, + { path: Routes.ARCHIVED, element: }, + { path: "u/:username", element: }, ], }, - { - path: Routes.ATTACHMENTS, - element: ( - }> - - - ), - }, - { - path: Routes.INBOX, - element: ( - }> - - - ), - }, - { - path: Routes.SETTING, - element: ( - }> - - - ), - }, - { - path: "memos/:uid", - element: ( - }> - - - ), - }, - // Redirect old path to new path. - { - path: "m/:uid", - element: ( - }> - - - ), - }, - { - path: "403", - element: ( - }> - - - ), - }, - { - path: "404", - element: ( - }> - - - ), - }, - { - path: "*", - element: ( - }> - - - ), - }, + { path: Routes.ATTACHMENTS, element: }, + { path: Routes.INBOX, element: }, + { path: Routes.SETTING, element: }, + { path: "memos/:uid", element: }, + // Redirect old path to new path + { path: "m/:uid", element: }, + { path: "403", element: }, + { path: "404", element: }, + { path: "*", element: }, ], }, ],