-
Notifications
You must be signed in to change notification settings - Fork 71
fix: billing usage page guard flicker #1826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughGuard and user/wallet hooks now return loading-aware objects ({ canVisit, isLoading }); Guard short-circuits to a Loading component when any guard is loading. useUser aggregates isLoading; related tests and fixtures updated to the new shapes. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant P as Protected Page
participant G as Guard(Component, useCheck)
participant H as useCheck (hook)
participant L as Loading
participant F as Fallback (404)
participant C as Component
U->>P: Navigate
P->>G: Render
G->>H: Invoke useCheck()
H-->>G: { canVisit, isLoading }
alt isLoading === true
G->>L: Render Loading
note right of G: Short-circuit until loading resolves
else canVisit === true
G->>C: Render Component
else
G->>F: Render Fallback
end
sequenceDiagram
autonumber
participant P as Protected Page
participant CG as composeGuards(g1, g2, ...)
participant g1 as Guard Hook A
participant g2 as Guard Hook B
P->>CG: composedUseCheck()
CG->>g1: call()
g1-->>CG: { canVisit: a, isLoading: la }
CG->>g2: call()
g2-->>CG: { canVisit: b, isLoading: lb }
CG-->>P: { canVisit: a && b, isLoading: la || lb }
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: CodeRabbit UI 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
apps/deploy-web/src/hooks/useUser.ts (1)
7-17
: Return type is too strong; prefer Partial to match actual runtime shape
useUser
promises a fullCustomUserProfile
, but can return{}
when no user is present. This is type-unsafe and can mislead call sites into treating fields as always defined.Apply this diff to align the type contract with behavior:
-export const useUser = (): CustomUserProfile & { isLoading: boolean } => { +export const useUser = (): Partial<CustomUserProfile> & { isLoading: boolean } => {This keeps your current usage pattern (optional chaining) sound, and avoids leaking unsound “always-present” fields.
apps/deploy-web/src/hoc/guard/guard.hoc.tsx (1)
4-8
: Export the UseCheck type for consumersExporting
UseCheck
allows guard hooks to explicitly annotate their return type and get compile-time alignment withGuard
/composeGuards
.-type UseCheck = () => { +export type UseCheck = () => { canVisit: boolean; isLoading: boolean; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx
(1 hunks)apps/deploy-web/src/hoc/guard/guard.hoc.tsx
(2 hunks)apps/deploy-web/src/hooks/useUser.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)
Never use type any or cast to type any. Always define the proper TypeScript types.
Files:
apps/deploy-web/src/hooks/useUser.ts
apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx
apps/deploy-web/src/hoc/guard/guard.hoc.tsx
**/*.{js,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)
**/*.{js,ts,tsx}
: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code
Files:
apps/deploy-web/src/hooks/useUser.ts
apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx
apps/deploy-web/src/hoc/guard/guard.hoc.tsx
🧬 Code Graph Analysis (2)
apps/deploy-web/src/hooks/useUser.ts (3)
apps/deploy-web/src/types/user.ts (1)
CustomUserProfile
(19-19)apps/deploy-web/src/hooks/useCustomUser.ts (1)
useCustomUser
(16-26)apps/deploy-web/src/hooks/useStoredAnonymousUser.ts (1)
useStoredAnonymousUser
(18-49)
apps/deploy-web/src/hoc/guard/guard.hoc.tsx (1)
apps/deploy-web/src/components/layout/Layout.tsx (1)
Loading
(151-162)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: validate / validate-app
- GitHub Check: test-build
🔇 Additional comments (4)
apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx (1)
360-363
: LGTM: guard-ready hook shape is correct
useIsManagedWalletUser
cleanly maps to the new guard contract and will prevent the 404 flicker by surfacingisLoading
. A repository-wide search across.ts
/.tsx
files confirms there are no remaining boolean-style usages ofuseIsManagedWalletUser
that need adapting.apps/deploy-web/src/hooks/useUser.ts (1)
19-26
: Normalize registered-user detection to consider bothid
anduserId
To prevent false negatives when one of these fields is populated and the other isn’t, update
useIsRegisteredUser
to accept either:– export const useIsRegisteredUser = () => { – const { isLoading, userId } = useUser(); – – return { – isLoading, – canVisit: !!userId – }; – }; + export const useIsRegisteredUser = () => { + const { isLoading, userId, id } = useUser(); + return { + isLoading, + canVisit: Boolean(userId ?? id) + }; + };– This aligns with checks elsewhere (e.g.
user?.id
inWalletProvider.tsx
).
– Once the user shape is consolidated, you can remove one of these branches.apps/deploy-web/src/hoc/guard/guard.hoc.tsx (2)
9-16
: LGTM: loading gate removes 404 flickerRendering
<Loading>
duringisLoading
neatly fixes the transient 404 without violating hook rules.
31-44
: ComposeGuards verification complete – no boolean-only guards found• Searched for all
composeGuards(…)
usages and confirmed only the intended calls in
- apps/deploy-web/src/pages/usage/index.tsx
- apps/deploy-web/src/pages/billing/index.tsx
• Searched across all.ts
/.tsx
files for any guard hooks still returning a bareboolean
– none foundComposition logic is sound, hooks correctly return
{ canVisit, isLoading }
, and there are no remaining boolean-based guards to migrate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
apps/deploy-web/src/types/user.ts (1)
19-22
: Consider keeping loading state out of the domain/User typeAdding isLoading onto CustomUserProfile blurs concerns (data vs. UI state) and can accidentally leak into analytics/persistence. Optional, but consider returning a wrapper result from hooks instead (e.g., { user?: CustomUserProfile; isLoading: boolean }) and keep CustomUserProfile purely user data.
Example type (outside this hunk) if you decide to refactor hooks later:
export type UseUserResult = { user?: CustomUserProfile; isLoading: boolean; };apps/deploy-web/src/components/user/UserProviders/UserProviders.spec.tsx (1)
21-21
: Follow repo test guideline: prefer queryBy over getBy in expectationsIn apps//.spec.tsx we use queryBy methods. Replace getByRole with queryByRole here.
Apply:
- expect(screen.getByRole("status")).toBeInTheDocument(); + expect(screen.queryByRole("status")).toBeInTheDocument();apps/deploy-web/src/hooks/useUser.ts (2)
7-16
: useMemo here is optional and adds noiseThese computations are cheap and derived from stable hook outputs. You can simplify by inlining them without useMemo to reduce indirection and dependency maintenance.
Example:
- const user = useMemo(() => registeredUser ?? anonymousUser, [registeredUser, anonymousUser]); - const isLoading = useMemo(() => isLoadingRegisteredUser || isLoadingAnonymousUser, [isLoadingRegisteredUser, isLoadingAnonymousUser]); + const user = registeredUser ?? anonymousUser; + const isLoading = isLoadingRegisteredUser || isLoadingAnonymousUser;
8-12
: Potential double invocation of useCustomUser via nested hook usageuseUser calls useCustomUser, and useStoredAnonymousUser also calls useCustomUser internally. This duplicates subscription to Auth0’s user state and can cause extra renders. Optional: thread registered user/loading into useStoredAnonymousUser, or have useStoredAnonymousUser accept a flag/param to avoid its internal useCustomUser call when already provided.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
apps/deploy-web/src/components/user/UserProviders/UserProviders.spec.tsx
(1 hunks)apps/deploy-web/src/hoc/guard/guard.hoc.tsx
(2 hunks)apps/deploy-web/src/hooks/useUser.spec.tsx
(1 hunks)apps/deploy-web/src/hooks/useUser.ts
(1 hunks)apps/deploy-web/src/types/user.ts
(1 hunks)apps/deploy-web/tests/seeders/user.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/deploy-web/src/hoc/guard/guard.hoc.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)
Never use type any or cast to type any. Always define the proper TypeScript types.
Files:
apps/deploy-web/tests/seeders/user.ts
apps/deploy-web/src/types/user.ts
apps/deploy-web/src/hooks/useUser.spec.tsx
apps/deploy-web/src/components/user/UserProviders/UserProviders.spec.tsx
apps/deploy-web/src/hooks/useUser.ts
**/*.{js,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)
**/*.{js,ts,tsx}
: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code
Files:
apps/deploy-web/tests/seeders/user.ts
apps/deploy-web/src/types/user.ts
apps/deploy-web/src/hooks/useUser.spec.tsx
apps/deploy-web/src/components/user/UserProviders/UserProviders.spec.tsx
apps/deploy-web/src/hooks/useUser.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)
Don't use
jest.mock()
to mock dependencies in test files. Instead, usejest-mock-extended
to create mocks and pass mocks as dependencies to the service under test.
**/*.spec.{ts,tsx}
: Usesetup
function instead ofbeforeEach
in test files
setup
function must be at the bottom of the rootdescribe
block in test files
setup
function creates an object under test and returns it
setup
function should accept a single parameter with inline type definition
Don't use shared state insetup
function
Don't specify return type ofsetup
function
Files:
apps/deploy-web/src/hooks/useUser.spec.tsx
apps/deploy-web/src/components/user/UserProviders/UserProviders.spec.tsx
apps/{deploy-web,provider-console}/**/*.spec.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/query-by-in-tests.mdc)
Use
queryBy
methods instead ofgetBy
methods in test expectations in.spec.tsx
files
Files:
apps/deploy-web/src/hooks/useUser.spec.tsx
apps/deploy-web/src/components/user/UserProviders/UserProviders.spec.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-21T08:24:27.953Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-07-21T08:24:27.953Z
Learning: Applies to apps/{deploy-web,provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations in `.spec.tsx` files
Applied to files:
apps/deploy-web/src/hooks/useUser.spec.tsx
apps/deploy-web/src/components/user/UserProviders/UserProviders.spec.tsx
🧬 Code Graph Analysis (3)
apps/deploy-web/src/types/user.ts (1)
apps/deploy-web/src/components/user/UserProfile.tsx (1)
UserProfile
(20-71)
apps/deploy-web/src/hooks/useUser.spec.tsx (1)
apps/deploy-web/tests/seeders/user.ts (1)
buildUser
(7-26)
apps/deploy-web/src/hooks/useUser.ts (2)
apps/deploy-web/src/hooks/useCustomUser.ts (1)
useCustomUser
(16-26)apps/deploy-web/src/hooks/useStoredAnonymousUser.ts (1)
useStoredAnonymousUser
(18-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: validate / validate-app
- GitHub Check: test-build
🔇 Additional comments (7)
apps/deploy-web/tests/seeders/user.ts (1)
23-23
: LGTM: sensible default for testsDefaulting isLoading to false in the user seeder is consistent with the updated typing and enables overriding per test.
apps/deploy-web/src/components/user/UserProviders/UserProviders.spec.tsx (1)
56-62
: LGTM: updated tracking payloads match the new loading-aware shapeThe test now asserts the transient loading payload and the final anonymous user payload with isLoading. This aligns with the new contract and should prevent the flicker regressions from slipping.
apps/deploy-web/src/hooks/useUser.spec.tsx (4)
15-15
: LGTM: test name reflects the new canVisit contractClearer intent with “can visit” wording.
20-23
: LGTM: expectation updated to the new return shapeAsserting { isLoading: false, canVisit: true } matches the updated API.
26-26
: LGTM: negative case coveredCovers the falsy userId path appropriately.
31-34
: LGTM: expectation mirrors the object result for the negative pathMatches { isLoading: false, canVisit: false } as designed.
apps/deploy-web/src/hooks/useUser.ts (1)
20-25
: No boolean-style usages of useIsRegisteredUser remain – all call sites are up to dateA repository-wide search for
useIsRegisteredUser(
in *.ts/tsx only returned its use withinapps/deploy-web/src/hooks/useUser.spec.tsx
(no production code treating the hook call as a bare boolean). There are no downstreamif (useIsRegisteredUser())
orconst foo = useIsRegisteredUser()
patterns in the app.Since no code outside tests was broken by the change, no further updates are required.
export const useUser = (): CustomUserProfile => { | ||
const { user: registeredUser } = useCustomUser(); | ||
const { user: anonymousUser } = useStoredAnonymousUser(); | ||
const user = useMemo(() => registeredUser || anonymousUser, [registeredUser, anonymousUser]); | ||
const { user: registeredUser, isLoading: isLoadingRegisteredUser } = useCustomUser(); | ||
const { user: anonymousUser, isLoading: isLoadingAnonymousUser } = useStoredAnonymousUser(); | ||
const user = useMemo(() => registeredUser || anonymousUser || {}, [registeredUser, anonymousUser]); | ||
const isLoading = useMemo(() => isLoadingRegisteredUser || isLoadingAnonymousUser, [isLoadingRegisteredUser, isLoadingAnonymousUser]); | ||
|
||
return user; | ||
return { | ||
...user, | ||
isLoading | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Tighten return typing and avoid {} as a user fallback
Returning {} as the user can be type-unsound against CustomUserProfile (depending on required fields of Auth0’s UserProfile) and spreads UI state into a domain object. Also, nullish coalescing communicates intent better than || here.
Minimal, local change to improve type-safety without altering callers:
-export const useUser = (): CustomUserProfile => {
+export const useUser = (): Partial<CustomUserProfile> & { isLoading: boolean } => {
const { user: registeredUser, isLoading: isLoadingRegisteredUser } = useCustomUser();
const { user: anonymousUser, isLoading: isLoadingAnonymousUser } = useStoredAnonymousUser();
- const user = useMemo(() => registeredUser || anonymousUser || {}, [registeredUser, anonymousUser]);
+ const user = useMemo(() => registeredUser ?? anonymousUser, [registeredUser, anonymousUser]);
const isLoading = useMemo(() => isLoadingRegisteredUser || isLoadingAnonymousUser, [isLoadingRegisteredUser, isLoadingAnonymousUser]);
return {
- ...user,
+ ...(user ?? {}),
isLoading
};
};
Notes:
- Return type is now Partial with a guaranteed isLoading flag.
- Avoids returning {} as a “user” while keeping the existing spread shape for consumers.
- Uses ?? over || to avoid treating falsy-but-valid values as absent.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const useUser = (): CustomUserProfile => { | |
const { user: registeredUser } = useCustomUser(); | |
const { user: anonymousUser } = useStoredAnonymousUser(); | |
const user = useMemo(() => registeredUser || anonymousUser, [registeredUser, anonymousUser]); | |
const { user: registeredUser, isLoading: isLoadingRegisteredUser } = useCustomUser(); | |
const { user: anonymousUser, isLoading: isLoadingAnonymousUser } = useStoredAnonymousUser(); | |
const user = useMemo(() => registeredUser || anonymousUser || {}, [registeredUser, anonymousUser]); | |
const isLoading = useMemo(() => isLoadingRegisteredUser || isLoadingAnonymousUser, [isLoadingRegisteredUser, isLoadingAnonymousUser]); | |
return user; | |
return { | |
...user, | |
isLoading | |
}; | |
export const useUser = (): Partial<CustomUserProfile> & { isLoading: boolean } => { | |
const { user: registeredUser, isLoading: isLoadingRegisteredUser } = useCustomUser(); | |
const { user: anonymousUser, isLoading: isLoadingAnonymousUser } = useStoredAnonymousUser(); | |
const user = useMemo(() => registeredUser ?? anonymousUser, [registeredUser, anonymousUser]); | |
const isLoading = useMemo(() => isLoadingRegisteredUser || isLoadingAnonymousUser, [isLoadingRegisteredUser, isLoadingAnonymousUser]); | |
return { | |
...(user ?? {}), | |
isLoading | |
}; | |
}; |
🤖 Prompt for AI Agents
In apps/deploy-web/src/hooks/useUser.ts around lines 7 to 16, the function
returns {} as a user and uses || which is type-unsafe and can mix UI state into
the domain object; change the function return type to Partial<CustomUserProfile>
& { isLoading: boolean }, replace the user fallback {} with undefined (i.e.
compute user = useMemo(() => registeredUser ?? anonymousUser, [...])), use ??
instead of || for nullish coalescing, and when returning only spread the
resolved user if present (e.g. ...(user ? user : {})) alongside a guaranteed
isLoading boolean so callers keep the same shape without unsafe empty-object
fallbacks.
const { canVisit, isLoading } = useCheck(); | ||
|
||
if (isLoading) { | ||
return <Loading text="Please wait..." />; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question(non-blocking): Is there a way we can customize the text in different pages?
Fixes #1822
Summary by CodeRabbit
New Features
Bug Fixes
Tests