Skip to content

Commit d55af9b

Browse files
committed
refactor: standardize loading indicators by using Spinner for route fallbacks and specializing Skeleton for memo lists
1 parent 61e94e8 commit d55af9b

File tree

6 files changed

+63
-167
lines changed

6 files changed

+63
-167
lines changed

web/src/components/AuthFooter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useTranslation } from "react-i18next";
22
import { cn } from "@/lib/utils";
3-
import { getInitialTheme, loadTheme } from "@/utils/theme";
43
import { loadLocale } from "@/utils/i18n";
4+
import { getInitialTheme, loadTheme } from "@/utils/theme";
55
import LocaleSelect from "./LocaleSelect";
66
import ThemeSelect from "./ThemeSelect";
77

web/src/components/PagedMemoList/PagedMemoList.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ const PagedMemoList = (props: Props) => {
149149
<div className="flex flex-col justify-start items-start w-full max-w-full">
150150
{/* Show skeleton loader during initial load */}
151151
{isLoading ? (
152-
<Skeleton type="memo" showCreator={props.showCreator} count={4} />
152+
<Skeleton showCreator={props.showCreator} count={4} />
153153
) : (
154154
<>
155155
<MasonryView
@@ -166,8 +166,8 @@ const PagedMemoList = (props: Props) => {
166166
listMode={layout === "LIST"}
167167
/>
168168

169-
{/* Loading indicator for pagination - use skeleton for content consistency */}
170-
{isFetchingNextPage && <Skeleton type="pagination" showCreator={props.showCreator} count={2} />}
169+
{/* Loading indicator for pagination */}
170+
{isFetchingNextPage && <Skeleton showCreator={props.showCreator} count={2} />}
171171

172172
{/* Empty state or back-to-top button */}
173173
{!isFetchingNextPage && (

web/src/components/Skeleton.tsx

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import { cn } from "@/lib/utils";
22

33
interface Props {
4-
type?: "route" | "memo" | "pagination";
54
showCreator?: boolean;
65
count?: number;
7-
showEditor?: boolean;
86
}
97

10-
// Memo card skeleton component
8+
// Memo card skeleton component for list loading states
119
const MemoCardSkeleton = ({ showCreator = false, index = 0 }: { showCreator?: boolean; index?: number }) => (
1210
<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">
1311
{/* Header section */}
@@ -43,36 +41,17 @@ const MemoCardSkeleton = ({ showCreator = false, index = 0 }: { showCreator?: bo
4341
</div>
4442
);
4543

46-
const Skeleton = ({ type = "route", showCreator = false, count = 4, showEditor = true }: Props) => {
47-
// Pagination type: simpler, just memos
48-
if (type === "pagination") {
49-
return (
50-
<div className="w-full flex flex-col justify-center items-center my-4">
51-
<div className="w-full max-w-2xl mx-auto">
52-
{Array.from({ length: count }).map((_, index) => (
53-
<MemoCardSkeleton key={index} showCreator={showCreator} index={index} />
54-
))}
55-
</div>
56-
</div>
57-
);
58-
}
59-
60-
// Route or memo type: with optional wrapper
61-
return (
62-
<div className="w-full max-w-2xl mx-auto">
63-
{/* Editor skeleton - only for route type */}
64-
{type === "route" && showEditor && (
65-
<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">
66-
<div className="w-full h-12 bg-muted rounded" />
67-
</div>
68-
)}
69-
70-
{/* Memo skeletons */}
71-
{Array.from({ length: count }).map((_, index) => (
72-
<MemoCardSkeleton key={index} showCreator={showCreator} index={index} />
73-
))}
74-
</div>
75-
);
76-
};
44+
/**
45+
* Skeleton loading state for memo lists.
46+
* Use this for initial memo list loading and pagination.
47+
* For generic page/route loading, use Spinner instead.
48+
*/
49+
const Skeleton = ({ showCreator = false, count = 4 }: Props) => (
50+
<div className="w-full max-w-2xl mx-auto">
51+
{Array.from({ length: count }).map((_, index) => (
52+
<MemoCardSkeleton key={index} showCreator={showCreator} index={index} />
53+
))}
54+
</div>
55+
);
7756

7857
export default Skeleton;

web/src/layouts/RootLayout.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Suspense, useEffect, useMemo } from "react";
22
import { Outlet, useLocation, useSearchParams } from "react-router-dom";
33
import usePrevious from "react-use/lib/usePrevious";
44
import Navigation from "@/components/Navigation";
5-
import Skeleton from "@/components/Skeleton";
5+
import Spinner from "@/components/Spinner";
66
import { useInstance } from "@/contexts/InstanceContext";
77
import { useMemoFilterContext } from "@/contexts/MemoFilterContext";
88
import useCurrentUser from "@/hooks/useCurrentUser";
@@ -47,7 +47,13 @@ const RootLayout = () => {
4747
</div>
4848
)}
4949
<main className="w-full h-auto grow shrink flex flex-col justify-start items-center">
50-
<Suspense fallback={<Skeleton type="route" />}>
50+
<Suspense
51+
fallback={
52+
<div className="w-full h-64 flex items-center justify-center">
53+
<Spinner size="lg" />
54+
</div>
55+
}
56+
>
5157
<Outlet />
5258
</Suspense>
5359
</main>

web/src/main.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { RouterProvider } from "react-router-dom";
88
import "./i18n";
99
import "./index.css";
1010
import { ErrorBoundary } from "@/components/ErrorBoundary";
11+
import Spinner from "@/components/Spinner";
1112
import { AuthProvider, useAuth } from "@/contexts/AuthContext";
1213
import { InstanceProvider, useInstance } from "@/contexts/InstanceContext";
1314
import { ViewProvider } from "@/contexts/ViewContext";
@@ -39,7 +40,11 @@ function AppInitializer({ children }: { children: React.ReactNode }) {
3940
}, [initAuth, initInstance]);
4041

4142
if (!authInitialized || !instanceInitialized) {
42-
return undefined;
43+
return (
44+
<div className="w-full h-screen flex items-center justify-center">
45+
<Spinner size="lg" />
46+
</div>
47+
);
4348
}
4449

4550
return <>{children}</>;

web/src/router/index.tsx

Lines changed: 32 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import type { ComponentType } from "react";
12
import { lazy, Suspense } from "react";
23
import { createBrowserRouter } from "react-router-dom";
34
import App from "@/App";
4-
import Skeleton from "@/components/Skeleton";
5+
import Spinner from "@/components/Spinner";
56
import MainLayout from "@/layouts/MainLayout";
67
import RootLayout from "@/layouts/RootLayout";
78
import Home from "@/pages/Home";
@@ -27,6 +28,19 @@ import { ROUTES } from "./routes";
2728
export const Routes = ROUTES;
2829
export { ROUTES };
2930

31+
// Helper component to reduce Suspense boilerplate for lazy routes
32+
const LazyRoute = ({ component: Component }: { component: ComponentType }) => (
33+
<Suspense
34+
fallback={
35+
<div className="w-full h-64 flex items-center justify-center">
36+
<Spinner size="lg" />
37+
</div>
38+
}
39+
>
40+
<Component />
41+
</Suspense>
42+
);
43+
3044
const router = createBrowserRouter([
3145
{
3246
path: "/",
@@ -35,38 +49,10 @@ const router = createBrowserRouter([
3549
{
3650
path: Routes.AUTH,
3751
children: [
38-
{
39-
path: "",
40-
element: (
41-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
42-
<SignIn />
43-
</Suspense>
44-
),
45-
},
46-
{
47-
path: "admin",
48-
element: (
49-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
50-
<AdminSignIn />
51-
</Suspense>
52-
),
53-
},
54-
{
55-
path: "signup",
56-
element: (
57-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
58-
<SignUp />
59-
</Suspense>
60-
),
61-
},
62-
{
63-
path: "callback",
64-
element: (
65-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
66-
<AuthCallback />
67-
</Suspense>
68-
),
69-
},
52+
{ path: "", element: <LazyRoute component={SignIn} /> },
53+
{ path: "admin", element: <LazyRoute component={AdminSignIn} /> },
54+
{ path: "signup", element: <LazyRoute component={SignUp} /> },
55+
{ path: "callback", element: <LazyRoute component={AuthCallback} /> },
7056
],
7157
},
7258
{
@@ -76,101 +62,21 @@ const router = createBrowserRouter([
7662
{
7763
element: <MainLayout />,
7864
children: [
79-
{
80-
path: "",
81-
element: <Home />,
82-
},
83-
{
84-
path: Routes.EXPLORE,
85-
element: (
86-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
87-
<Explore />
88-
</Suspense>
89-
),
90-
},
91-
{
92-
path: Routes.ARCHIVED,
93-
element: (
94-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
95-
<Archived />
96-
</Suspense>
97-
),
98-
},
99-
{
100-
path: "u/:username",
101-
element: (
102-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
103-
<UserProfile />
104-
</Suspense>
105-
),
106-
},
65+
{ path: "", element: <Home /> },
66+
{ path: Routes.EXPLORE, element: <LazyRoute component={Explore} /> },
67+
{ path: Routes.ARCHIVED, element: <LazyRoute component={Archived} /> },
68+
{ path: "u/:username", element: <LazyRoute component={UserProfile} /> },
10769
],
10870
},
109-
{
110-
path: Routes.ATTACHMENTS,
111-
element: (
112-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
113-
<Attachments />
114-
</Suspense>
115-
),
116-
},
117-
{
118-
path: Routes.INBOX,
119-
element: (
120-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
121-
<Inboxes />
122-
</Suspense>
123-
),
124-
},
125-
{
126-
path: Routes.SETTING,
127-
element: (
128-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
129-
<Setting />
130-
</Suspense>
131-
),
132-
},
133-
{
134-
path: "memos/:uid",
135-
element: (
136-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
137-
<MemoDetail />
138-
</Suspense>
139-
),
140-
},
141-
// Redirect old path to new path.
142-
{
143-
path: "m/:uid",
144-
element: (
145-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
146-
<MemoDetailRedirect />
147-
</Suspense>
148-
),
149-
},
150-
{
151-
path: "403",
152-
element: (
153-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
154-
<PermissionDenied />
155-
</Suspense>
156-
),
157-
},
158-
{
159-
path: "404",
160-
element: (
161-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
162-
<NotFound />
163-
</Suspense>
164-
),
165-
},
166-
{
167-
path: "*",
168-
element: (
169-
<Suspense fallback={<Skeleton type="route" showEditor={false} />}>
170-
<NotFound />
171-
</Suspense>
172-
),
173-
},
71+
{ path: Routes.ATTACHMENTS, element: <LazyRoute component={Attachments} /> },
72+
{ path: Routes.INBOX, element: <LazyRoute component={Inboxes} /> },
73+
{ path: Routes.SETTING, element: <LazyRoute component={Setting} /> },
74+
{ path: "memos/:uid", element: <LazyRoute component={MemoDetail} /> },
75+
// Redirect old path to new path
76+
{ path: "m/:uid", element: <LazyRoute component={MemoDetailRedirect} /> },
77+
{ path: "403", element: <LazyRoute component={PermissionDenied} /> },
78+
{ path: "404", element: <LazyRoute component={NotFound} /> },
79+
{ path: "*", element: <LazyRoute component={NotFound} /> },
17480
],
17581
},
17682
],

0 commit comments

Comments
 (0)