Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
81022123
Commit
81022123
authored
Jan 27, 2026
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: simplify page loading logic
parent
c5d9770f
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
47 additions
and
122 deletions
+47
-122
Skeleton.tsx
web/src/components/Skeleton.tsx
+25
-31
Spinner.tsx
web/src/components/Spinner.tsx
+0
-19
UserMemoMap.tsx
web/src/components/UserMemoMap/UserMemoMap.tsx
+1
-8
RootLayout.tsx
web/src/layouts/RootLayout.tsx
+2
-11
main.tsx
web/src/main.tsx
+1
-6
AuthCallback.tsx
web/src/pages/AuthCallback.tsx
+3
-6
MemoDetailRedirect.tsx
web/src/router/MemoDetailRedirect.tsx
+0
-8
index.tsx
web/src/router/index.tsx
+15
-33
No files found.
web/src/components/Skeleton.tsx
View file @
81022123
import
{
cn
}
from
"@/lib/utils"
;
import
{
cn
}
from
"@/lib/utils"
;
interface
Props
{
interface
Skeleton
Props
{
showCreator
?:
boolean
;
showCreator
?:
boolean
;
count
?:
number
;
count
?:
number
;
}
}
// Memo card skeleton component for list loading states
const
skeletonBase
=
"bg-muted/70 rounded animate-pulse"
;
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"
>
const
MemoCardSkeleton
=
({
showCreator
,
index
}:
{
showCreator
?:
boolean
;
index
:
number
})
=>
(
{
/* Header section */
}
<
div
className=
"relative flex flex-col bg-card w-full px-4 py-3 mb-2 gap-2 rounded-lg border border-border"
>
<
div
className=
"w-full flex
flex-row
justify-between items-center gap-2"
>
<
div
className=
"w-full flex justify-between items-center gap-2"
>
<
div
className=
"
w-auto max-w-[calc(100%-8rem)] grow flex flex-row justify-start items-center
"
>
<
div
className=
"
grow flex items-center max-w-[calc(100%-8rem)]
"
>
{
showCreator
?
(
{
showCreator
?
(
<
div
className=
"w-full flex
flex-row justify-start
items-center gap-2"
>
<
div
className=
"w-full flex items-center gap-2"
>
<
div
className=
"w-8 h-8 rounded-full bg-muted shrink-0"
/>
<
div
className=
{
cn
(
"w-8 h-8 rounded-full shrink-0"
,
skeletonBase
)
}
/>
<
div
className=
"
w-full flex flex-col justify-center items-start
gap-1"
>
<
div
className=
"
flex flex-col
gap-1"
>
<
div
className=
"h-4 w-24 bg-muted rounded"
/>
<
div
className=
{
cn
(
"h-4 w-24"
,
skeletonBase
)
}
/>
<
div
className=
"h-3 w-16 bg-muted rounded"
/>
<
div
className=
{
cn
(
"h-3 w-16"
,
skeletonBase
)
}
/>
</
div
>
</
div
>
</
div
>
</
div
>
)
:
(
)
:
(
<
div
className=
"h-4 w-32 bg-muted rounded"
/>
<
div
className=
{
cn
(
"h-4 w-32"
,
skeletonBase
)
}
/>
)
}
)
}
</
div
>
</
div
>
{
/* Action buttons skeleton */
}
<
div
className=
"flex gap-2"
>
<
div
className=
"flex flex-row gap-2"
>
<
div
className=
{
cn
(
"w-4 h-4"
,
skeletonBase
)
}
/>
<
div
className=
"w-4 h-4 bg-muted rounded"
/>
<
div
className=
{
cn
(
"w-4 h-4"
,
skeletonBase
)
}
/>
<
div
className=
"w-4 h-4 bg-muted rounded"
/>
</
div
>
</
div
>
</
div
>
</
div
>
{
/* Content section */
}
<
div
className=
"w-full flex flex-col gap-2"
>
<
div
className=
"space-y-2"
>
<
div
className=
"space-y-2"
>
<
div
className=
{
cn
(
"h-4 bg-muted rounded"
,
index
%
3
===
0
?
"w-full"
:
index
%
3
===
1
?
"w-4/5"
:
"w-5/6"
)
}
/>
<
div
className=
{
cn
(
"h-4"
,
skeletonBase
,
index
%
3
===
0
?
"w-full"
:
index
%
3
===
1
?
"w-4/5"
:
"w-5/6"
)
}
/>
<
div
className=
{
cn
(
"h-4 bg-muted rounded"
,
index
%
2
===
0
?
"w-3/4"
:
"w-4/5"
)
}
/>
<
div
className=
{
cn
(
"h-4"
,
skeletonBase
,
index
%
2
===
0
?
"w-3/4"
:
"w-4/5"
)
}
/>
{
index
%
2
===
0
&&
<
div
className=
"h-4 w-2/3 bg-muted rounded"
/>
}
{
index
%
2
===
0
&&
<
div
className=
{
cn
(
"h-4 w-2/3"
,
skeletonBase
)
}
/>
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
);
);
/**
/**
* Skeleton loading state for memo lists.
* Memo list loading skeleton - shows card structure while loading.
* Use this for initial memo list loading and pagination.
* Only use for memo lists in PagedMemoList component.
* For generic page/route loading, use Spinner instead.
*/
*/
const
Skeleton
=
({
showCreator
=
false
,
count
=
4
}:
Props
)
=>
(
const
Skeleton
=
({
showCreator
=
false
,
count
=
4
}:
Skeleton
Props
)
=>
(
<
div
className=
"w-full max-w-2xl mx-auto"
>
<
div
className=
"w-full max-w-2xl mx-auto"
>
{
Array
.
from
({
length
:
count
}
).
map
((
_
,
index
)
=>
(
{
Array
.
from
({
length
:
count
}
,
(
_
,
i
)
=>
(
<
MemoCardSkeleton
key=
{
i
ndex
}
showCreator=
{
showCreator
}
index=
{
index
}
/>
<
MemoCardSkeleton
key=
{
i
}
showCreator=
{
showCreator
}
index=
{
i
}
/>
))
}
))
}
</
div
>
</
div
>
);
);
...
...
web/src/components/Spinner.tsx
deleted
100644 → 0
View file @
c5d9770f
import
{
LoaderIcon
}
from
"lucide-react"
;
import
{
cn
}
from
"@/lib/utils"
;
interface
Props
{
className
?:
string
;
size
?:
"sm"
|
"md"
|
"lg"
;
}
const
Spinner
=
({
className
,
size
=
"md"
}:
Props
)
=>
{
const
sizeClasses
=
{
sm
:
"w-4 h-4"
,
md
:
"w-6 h-6"
,
lg
:
"w-8 h-8"
,
};
return
<
LoaderIcon
className=
{
cn
(
"animate-spin"
,
sizeClasses
[
size
],
className
)
}
/>;
};
export
default
Spinner
;
web/src/components/UserMemoMap/UserMemoMap.tsx
View file @
81022123
...
@@ -8,7 +8,6 @@ import { MapContainer, Marker, Popup, useMap } from "react-leaflet";
...
@@ -8,7 +8,6 @@ import { MapContainer, Marker, Popup, useMap } from "react-leaflet";
import
MarkerClusterGroup
from
"react-leaflet-cluster"
;
import
MarkerClusterGroup
from
"react-leaflet-cluster"
;
import
{
Link
}
from
"react-router-dom"
;
import
{
Link
}
from
"react-router-dom"
;
import
{
defaultMarkerIcon
,
ThemedTileLayer
}
from
"@/components/map/map-utils"
;
import
{
defaultMarkerIcon
,
ThemedTileLayer
}
from
"@/components/map/map-utils"
;
import
Spinner
from
"@/components/Spinner"
;
import
{
useInfiniteMemos
}
from
"@/hooks/useMemoQueries"
;
import
{
useInfiniteMemos
}
from
"@/hooks/useMemoQueries"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
State
}
from
"@/types/proto/api/v1/common_pb"
;
import
{
State
}
from
"@/types/proto/api/v1/common_pb"
;
...
@@ -64,13 +63,7 @@ const UserMemoMap = ({ creator, className }: Props) => {
...
@@ -64,13 +63,7 @@ const UserMemoMap = ({ creator, className }: Props) => {
const
memosWithLocation
=
useMemo
(()
=>
data
?.
pages
.
flatMap
((
page
)
=>
page
.
memos
).
filter
((
memo
)
=>
memo
.
location
)
||
[],
[
data
]);
const
memosWithLocation
=
useMemo
(()
=>
data
?.
pages
.
flatMap
((
page
)
=>
page
.
memos
).
filter
((
memo
)
=>
memo
.
location
)
||
[],
[
data
]);
if
(
isLoading
)
{
if
(
isLoading
)
return
null
;
return
(
<
div
className=
"w-full h-[380px] flex items-center justify-center rounded-xl border border-border bg-muted/30"
>
<
Spinner
className=
"w-8 h-8"
/>
</
div
>
);
}
const
defaultCenter
=
{
lat
:
48.8566
,
lng
:
2.3522
};
const
defaultCenter
=
{
lat
:
48.8566
,
lng
:
2.3522
};
...
...
web/src/layouts/RootLayout.tsx
View file @
81022123
import
{
Suspense
,
useEffect
,
useMemo
}
from
"react"
;
import
{
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
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,15 +46,7 @@ const RootLayout = () => {
...
@@ -47,15 +46,7 @@ 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=
{
<
div
className=
"w-full h-64 flex items-center justify-center"
>
<
Spinner
size=
"lg"
/>
</
div
>
}
>
<
Outlet
/>
<
Outlet
/>
</
Suspense
>
</
main
>
</
main
>
</
div
>
</
div
>
);
);
...
...
web/src/main.tsx
View file @
81022123
...
@@ -8,7 +8,6 @@ import { RouterProvider } from "react-router-dom";
...
@@ -8,7 +8,6 @@ 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"
;
...
@@ -41,11 +40,7 @@ function AppInitializer({ children }: { children: React.ReactNode }) {
...
@@ -41,11 +40,7 @@ function AppInitializer({ children }: { children: React.ReactNode }) {
},
[
initAuth
,
initInstance
]);
},
[
initAuth
,
initInstance
]);
if
(
!
authInitialized
||
!
instanceInitialized
)
{
if
(
!
authInitialized
||
!
instanceInitialized
)
{
return
(
return
null
;
<
div
className=
"w-full h-screen flex items-center justify-center"
>
<
Spinner
size=
"lg"
/>
</
div
>
);
}
}
return
<>
{
children
}
</>;
return
<>
{
children
}
</>;
...
...
web/src/pages/AuthCallback.tsx
View file @
81022123
...
@@ -2,7 +2,6 @@ import { timestampDate } from "@bufbuild/protobuf/wkt";
...
@@ -2,7 +2,6 @@ import { timestampDate } from "@bufbuild/protobuf/wkt";
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useSearchParams
}
from
"react-router-dom"
;
import
{
useSearchParams
}
from
"react-router-dom"
;
import
{
setAccessToken
}
from
"@/auth-state"
;
import
{
setAccessToken
}
from
"@/auth-state"
;
import
Spinner
from
"@/components/Spinner"
;
import
{
authServiceClient
}
from
"@/connect"
;
import
{
authServiceClient
}
from
"@/connect"
;
import
{
useAuth
}
from
"@/contexts/AuthContext"
;
import
{
useAuth
}
from
"@/contexts/AuthContext"
;
import
{
absolutifyLink
}
from
"@/helpers/utils"
;
import
{
absolutifyLink
}
from
"@/helpers/utils"
;
...
@@ -110,13 +109,11 @@ const AuthCallback = () => {
...
@@ -110,13 +109,11 @@ const AuthCallback = () => {
})();
})();
},
[
searchParams
,
navigateTo
]);
},
[
searchParams
,
navigateTo
]);
if
(
state
.
loading
)
return
null
;
return
(
return
(
<
div
className=
"p-4 py-24 w-full h-full flex justify-center items-center"
>
<
div
className=
"p-4 py-24 w-full h-full flex justify-center items-center"
>
{
state
.
loading
?
(
<
Spinner
size=
"lg"
/>
)
:
(
<
div
className=
"max-w-lg font-mono whitespace-pre-wrap opacity-80"
>
{
state
.
errorMessage
}
</
div
>
<
div
className=
"max-w-lg font-mono whitespace-pre-wrap opacity-80"
>
{
state
.
errorMessage
}
</
div
>
)
}
</
div
>
</
div
>
);
);
};
};
...
...
web/src/router/MemoDetailRedirect.tsx
deleted
100644 → 0
View file @
c5d9770f
import
{
Navigate
,
useParams
}
from
"react-router-dom"
;
const
MemoDetailRedirect
=
()
=>
{
const
{
uid
}
=
useParams
();
return
<
Navigate
to=
{
`/memos/${uid}`
}
replace
/>;
};
export
default
MemoDetailRedirect
;
web/src/router/index.tsx
View file @
81022123
import
type
{
ComponentType
}
from
"react"
;
import
{
lazy
}
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
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"
;
...
@@ -20,7 +18,6 @@ const Setting = lazy(() => import("@/pages/Setting"));
...
@@ -20,7 +18,6 @@ const Setting = lazy(() => import("@/pages/Setting"));
const
SignIn
=
lazy
(()
=>
import
(
"@/pages/SignIn"
));
const
SignIn
=
lazy
(()
=>
import
(
"@/pages/SignIn"
));
const
SignUp
=
lazy
(()
=>
import
(
"@/pages/SignUp"
));
const
SignUp
=
lazy
(()
=>
import
(
"@/pages/SignUp"
));
const
UserProfile
=
lazy
(()
=>
import
(
"@/pages/UserProfile"
));
const
UserProfile
=
lazy
(()
=>
import
(
"@/pages/UserProfile"
));
const
MemoDetailRedirect
=
lazy
(()
=>
import
(
"./MemoDetailRedirect"
));
import
{
ROUTES
}
from
"./routes"
;
import
{
ROUTES
}
from
"./routes"
;
...
@@ -28,19 +25,6 @@ import { ROUTES } from "./routes";
...
@@ -28,19 +25,6 @@ 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
:
"/"
,
...
@@ -49,10 +33,10 @@ const router = createBrowserRouter([
...
@@ -49,10 +33,10 @@ const router = createBrowserRouter([
{
{
path
:
Routes
.
AUTH
,
path
:
Routes
.
AUTH
,
children
:
[
children
:
[
{
path
:
""
,
element
:
<
LazyRoute
component=
{
SignIn
}
/>
},
{
path
:
""
,
element
:
<
SignIn
/>
},
{
path
:
"admin"
,
element
:
<
LazyRoute
component=
{
AdminSignIn
}
/>
},
{
path
:
"admin"
,
element
:
<
AdminSignIn
/>
},
{
path
:
"signup"
,
element
:
<
LazyRoute
component=
{
SignUp
}
/>
},
{
path
:
"signup"
,
element
:
<
SignUp
/>
},
{
path
:
"callback"
,
element
:
<
LazyRoute
component=
{
AuthCallback
}
/>
},
{
path
:
"callback"
,
element
:
<
AuthCallback
/>
},
],
],
},
},
{
{
...
@@ -63,20 +47,18 @@ const router = createBrowserRouter([
...
@@ -63,20 +47,18 @@ const router = createBrowserRouter([
element
:
<
MainLayout
/>,
element
:
<
MainLayout
/>,
children
:
[
children
:
[
{
path
:
""
,
element
:
<
Home
/>
},
{
path
:
""
,
element
:
<
Home
/>
},
{
path
:
Routes
.
EXPLORE
,
element
:
<
LazyRoute
component=
{
Explore
}
/>
},
{
path
:
Routes
.
EXPLORE
,
element
:
<
Explore
/>
},
{
path
:
Routes
.
ARCHIVED
,
element
:
<
LazyRoute
component=
{
Archived
}
/>
},
{
path
:
Routes
.
ARCHIVED
,
element
:
<
Archived
/>
},
{
path
:
"u/:username"
,
element
:
<
LazyRoute
component=
{
UserProfile
}
/>
},
{
path
:
"u/:username"
,
element
:
<
UserProfile
/>
},
],
],
},
},
{
path
:
Routes
.
ATTACHMENTS
,
element
:
<
LazyRoute
component=
{
Attachments
}
/>
},
{
path
:
Routes
.
ATTACHMENTS
,
element
:
<
Attachments
/>
},
{
path
:
Routes
.
INBOX
,
element
:
<
LazyRoute
component=
{
Inboxes
}
/>
},
{
path
:
Routes
.
INBOX
,
element
:
<
Inboxes
/>
},
{
path
:
Routes
.
SETTING
,
element
:
<
LazyRoute
component=
{
Setting
}
/>
},
{
path
:
Routes
.
SETTING
,
element
:
<
Setting
/>
},
{
path
:
"memos/:uid"
,
element
:
<
LazyRoute
component=
{
MemoDetail
}
/>
},
{
path
:
"memos/:uid"
,
element
:
<
MemoDetail
/>
},
// Redirect old path to new path
{
path
:
"403"
,
element
:
<
PermissionDenied
/>
},
{
path
:
"m/:uid"
,
element
:
<
LazyRoute
component=
{
MemoDetailRedirect
}
/>
},
{
path
:
"404"
,
element
:
<
NotFound
/>
},
{
path
:
"403"
,
element
:
<
LazyRoute
component=
{
PermissionDenied
}
/>
},
{
path
:
"*"
,
element
:
<
NotFound
/>
},
{
path
:
"404"
,
element
:
<
LazyRoute
component=
{
NotFound
}
/>
},
{
path
:
"*"
,
element
:
<
LazyRoute
component=
{
NotFound
}
/>
},
],
],
},
},
],
],
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment