Skip to content

feat(dashboard): enhance organization dropdown and loading states for self-hosted users#9808

Merged
merrcury merged 4 commits intonextfrom
fix/selfhosted-organization-picker
Jan 8, 2026
Merged

feat(dashboard): enhance organization dropdown and loading states for self-hosted users#9808
merrcury merged 4 commits intonextfrom
fix/selfhosted-organization-picker

Conversation

@merrcury
Copy link
Member

@merrcury merrcury commented Jan 8, 2026

What changed? Why was the change needed?

  • Updated OrganizationDropdown to conditionally render based on self-hosted type.
  • Improved loading state handling in OrganizationSwitcherComponent.
  • Adjusted OrganizationContextProvider to manage organization loading state more effectively.
  • Modified user resource to handle null user state and updated user creation logic from JWT.

Screenshots

Expand for optional sections

Related enterprise PR

Special notes for your reviewer

… self-hosted users

- Updated OrganizationDropdown to conditionally render based on self-hosted type.
- Improved loading state handling in OrganizationSwitcherComponent.
- Adjusted OrganizationContextProvider to manage organization loading state more effectively.
- Modified user resource to handle null user state and updated user creation logic from JWT.
@netlify
Copy link

netlify bot commented Jan 8, 2026

Deploy preview added

Name Link
🔨 Latest commit f8aee1c
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/695f8555d767cb0008ee2d10
😎 Deploy Preview https://deploy-preview-9808.dashboard-v2.novu-staging.co
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2026

Hey there and thank you for opening this pull request! 👋

We require pull request titles to follow specific formatting rules and it looks like your proposed title needs to be adjusted.

Your PR title is: feat(dashboard): enhance organization dropdown and loading states for self-hosted users

Requirements:

  1. Follow the Conventional Commits specification
  2. As a team member, include Linear ticket ID at the end: fixes TICKET-ID or include it in your branch name

Expected format: feat(scope): Add fancy new feature fixes NOV-123

Details:

PR title must end with 'fixes TICKET-ID' (e.g., 'fixes NOV-123') or include ticket ID in branch name

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

Walkthrough

This change refactors the self-hosted dashboard utilities to improve loading state handling and null safety. The organization switcher component now displays a loading skeleton when data is not yet loaded. The user context has been updated to accept null user values instead of initialized defaults. The createUserFromJwt function now explicitly returns null when decoding fails and adds organizationMemberships and passwordEnabled fields. Organization resource loading logic is revised to check for token presence. Additionally, the Vite configuration makes the @ alias universally available while keeping the self-hosted region alias conditionally gated.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: enhancing organization dropdown and improving loading states for self-hosted users, which directly corresponds to the changeset modifications.
Description check ✅ Passed The description is directly related to the changeset, detailing updates to OrganizationDropdown, loading state handling, OrganizationContextProvider, and user resource modifications that align with the actual changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0b7110 and e583295.

📒 Files selected for processing (1)
  • apps/dashboard/vite.config.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use descriptive variable names with auxiliary verbs (isLoading, hasError)
Add blank lines before return statements
Import motion components from 'motion/react' instead of 'motion-react'

**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use functional and declarative programming patterns; avoid classes
Prefer iteration and modularization over code duplication, minimize code duplication as possible
Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
Structure files: exported component, subcomponents, helpers, static content, types
Don't leave comments in code, unless they explain something complex and not trivial
Don't use nested ternaries
Favor named exports for components
Use TypeScript for all code; prefer interfaces over types
In front end code, use types over interfaces
Use functional components with TypeScript types
Use the "function" keyword for pure functions
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
Add blank lines before return statements
When importing "motion-react" package, import it from "motion/react"

Files:

  • apps/dashboard/vite.config.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use functional and declarative programming patterns; avoid classes

Files:

  • apps/dashboard/vite.config.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Favor named exports for components

Files:

  • apps/dashboard/vite.config.ts
apps/{dashboard,web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer types over interfaces in frontend code

Files:

  • apps/dashboard/vite.config.ts
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use lowercase with dashes for directories and files in dashboard (e.g., components/auth-wizard)

Files:

  • apps/dashboard/vite.config.ts
apps/dashboard/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Favor named exports for components in dashboard

Files:

  • apps/dashboard/vite.config.ts
apps/dashboard/**/*

📄 CodeRabbit inference engine (.cursor/rules/figma.mdc)

apps/dashboard/**/*: Use the assets endpoint from Figma Dev Mode MCP Server to serve image and SVG assets
If the Figma Dev Mode MCP Server returns a localhost source for an image or SVG, use that image or SVG source directly without modification
Do not import or add new icon packages; all assets should come from the Figma payload
Do not use or create placeholders if a localhost source is provided by the Figma Dev Mode MCP Server

Use lowercase with dashes for directory and file names (e.g., components/auth-wizard)

Files:

  • apps/dashboard/vite.config.ts
**

📄 CodeRabbit inference engine (.cursor/rules/novu.mdc)

Use lowercase with dashes for directories and files (e.g., components/auth-wizard)

Files:

  • apps/dashboard/vite.config.ts
🧠 Learnings (1)
📚 Learning: 2026-01-07T13:09:45.895Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/novu.mdc:0-0
Timestamp: 2026-01-07T13:09:45.895Z
Learning: Applies to **/*.{ts,tsx} : Structure files: exported component, subcomponents, helpers, static content, types

Applied to files:

  • apps/dashboard/vite.config.ts
🧬 Code graph analysis (1)
apps/dashboard/vite.config.ts (2)
scripts/setup-env-files.js (1)
  • path (2-2)
scripts/dotenvcreate.mjs (1)
  • __dirname (82-82)
⏰ 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). (6)
  • GitHub Check: E2E test API / Test E2E
  • GitHub Check: Redirect rules - dashboard-v2-novu-staging
  • GitHub Check: Header rules - dashboard-v2-novu-staging
  • GitHub Check: Pages changed - dashboard-v2-novu-staging
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (typescript)
🔇 Additional comments (1)
apps/dashboard/vite.config.ts (1)

98-98: LGTM! Good fix to make the '@' alias universally available.

Restoring the '@' alias outside the conditional block ensures consistent import path resolution across all build modes (self-hosted and cloud). This is the standard practice for src directory aliasing and aligns with the broader PR changes that rely on predictable import paths.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/dashboard/src/utils/self-hosted/user.types.ts (1)

32-33: Address hardcoded property values in self-hosted user initialization.

The organizationMemberships array with a single empty object [{}] is problematic—it's neither an empty array nor populated with data, which could cause logic errors in organizational membership checks (e.g., organizationMemberships.length > 0 would incorrectly evaluate to true). For self-hosted deployments without organization data, this should likely be [], or it should be populated from the JWT if organization data is available.

Similarly, passwordEnabled: true is unconditionally hardcoded without considering authentication method variations. In self-hosted scenarios with SAML-only or OAuth-only authentication, this value may be incorrect. Verify whether this should be extracted from the JWT or configured based on the self-hosted deployment's authentication method.

🧹 Nitpick comments (1)
apps/dashboard/src/utils/self-hosted/organization-switcher.tsx (1)

6-9: Consider improving type safety by properly typing the context.

The type assertion as { organization: { name: string } | undefined; isLoaded: boolean; } bypasses TypeScript's type checking. Since useOrganization is created via createContextHook(OrganizationContext), the return type should be properly inferred from the context value.

♻️ Suggested approach

Define a proper type for the organization context value in organization.resource.tsx and export it:

// In organization.resource.tsx
export type OrganizationContextValue = {
  organization: {
    name: string;
    // ... other fields
  } | undefined;
  isLoaded: boolean;
};

Then use it without assertion:

-  const { organization, isLoaded } = useOrganization() as {
-    organization: { name: string } | undefined;
-    isLoaded: boolean;
-  };
+  const { organization, isLoaded } = useOrganization();
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82f01ad and e0b7110.

📒 Files selected for processing (5)
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.types.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use descriptive variable names with auxiliary verbs (isLoading, hasError)
Add blank lines before return statements
Import motion components from 'motion/react' instead of 'motion-react'

**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use functional and declarative programming patterns; avoid classes
Prefer iteration and modularization over code duplication, minimize code duplication as possible
Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
Structure files: exported component, subcomponents, helpers, static content, types
Don't leave comments in code, unless they explain something complex and not trivial
Don't use nested ternaries
Favor named exports for components
Use TypeScript for all code; prefer interfaces over types
In front end code, use types over interfaces
Use functional components with TypeScript types
Use the "function" keyword for pure functions
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
Add blank lines before return statements
When importing "motion-react" package, import it from "motion/react"

Files:

  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.types.ts
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use functional and declarative programming patterns; avoid classes

Files:

  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.types.ts
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Favor named exports for components

Files:

  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.types.ts
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
apps/{dashboard,web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer types over interfaces in frontend code

Files:

  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.types.ts
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use lowercase with dashes for directories and files in dashboard (e.g., components/auth-wizard)

Files:

  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.types.ts
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
apps/dashboard/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Favor named exports for components in dashboard

Files:

  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.types.ts
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
apps/dashboard/**/*

📄 CodeRabbit inference engine (.cursor/rules/figma.mdc)

apps/dashboard/**/*: Use the assets endpoint from Figma Dev Mode MCP Server to serve image and SVG assets
If the Figma Dev Mode MCP Server returns a localhost source for an image or SVG, use that image or SVG source directly without modification
Do not import or add new icon packages; all assets should come from the Figma payload
Do not use or create placeholders if a localhost source is provided by the Figma Dev Mode MCP Server

Use lowercase with dashes for directory and file names (e.g., components/auth-wizard)

Files:

  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.types.ts
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
**

📄 CodeRabbit inference engine (.cursor/rules/novu.mdc)

Use lowercase with dashes for directories and files (e.g., components/auth-wizard)

Files:

  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/user.types.ts
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/novu.mdc)

**/*.tsx: Use declarative JSX
Wrap client components in Suspense with fallback
Use dynamic loading for non-critical components

Files:

  • apps/dashboard/src/utils/self-hosted/user.resource.tsx
  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization.resource.tsx
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
apps/dashboard/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

Favor named exports for components

Files:

  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-25T11:29:52.304Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:29:52.304Z
Learning: Applies to apps/dashboard/**/*.{tsx,ts} : Favor named exports for components in dashboard

Applied to files:

  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
📚 Learning: 2025-12-22T14:14:49.363Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-12-22T14:14:49.363Z
Learning: Applies to apps/dashboard/**/components/**/*.{ts,tsx} : Favor named exports for components

Applied to files:

  • apps/dashboard/src/components/side-navigation/organization-dropdown.tsx
  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
📚 Learning: 2026-01-07T13:09:45.895Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/novu.mdc:0-0
Timestamp: 2026-01-07T13:09:45.895Z
Learning: Applies to **/*.tsx : Use dynamic loading for non-critical components

Applied to files:

  • apps/dashboard/src/utils/self-hosted/organization-switcher.tsx
🧬 Code graph analysis (5)
apps/dashboard/src/utils/self-hosted/user.resource.tsx (1)
apps/dashboard/src/utils/self-hosted/user.types.ts (1)
  • SelfHostedUser (3-15)
apps/dashboard/src/components/side-navigation/organization-dropdown.tsx (2)
apps/dashboard/src/config/index.ts (1)
  • IS_ENTERPRISE (39-39)
apps/dashboard/src/components/side-navigation/organization-dropdown-clerk.tsx (1)
  • OrganizationDropdown (96-252)
apps/dashboard/src/utils/self-hosted/organization.resource.tsx (1)
apps/dashboard/src/utils/self-hosted/jwt-manager.tsx (1)
  • getJwtToken (3-5)
apps/dashboard/src/utils/self-hosted/user.types.ts (1)
apps/dashboard/src/utils/self-hosted/index.tsx (1)
  • DecodedJwt (83-94)
apps/dashboard/src/utils/self-hosted/organization-switcher.tsx (2)
apps/dashboard/src/utils/self-hosted/organization.resource.tsx (1)
  • useOrganization (45-45)
apps/dashboard/src/utils/self-hosted/index.tsx (1)
  • useOrganization (28-28)
⏰ 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). (6)
  • GitHub Check: E2E test API / Test E2E
  • GitHub Check: Redirect rules - dashboard-v2-novu-staging
  • GitHub Check: Header rules - dashboard-v2-novu-staging
  • GitHub Check: Pages changed - dashboard-v2-novu-staging
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (typescript)
🔇 Additional comments (10)
apps/dashboard/src/utils/self-hosted/user.resource.tsx (1)

6-12: LGTM! Type consistency improved.

The nullable user type aligns with the updated createUserFromJwt signature, and simplifying the default value from createUserFromJwt(null) to null is cleaner since the function would return null anyway.

apps/dashboard/src/utils/self-hosted/user.types.ts (2)

17-20: LGTM! Nullable return type properly implemented.

The early null check and nullable return type improve type safety and align with the context updates in user.resource.tsx.


25-28: LGTM! Appropriate use of direct property access.

After the null check on line 18, direct property access is safe and more appropriate than optional chaining, especially since DecodedJwt fields are non-optional.

apps/dashboard/src/utils/self-hosted/organization.resource.tsx (3)

19-19: LGTM! Token-based query gating improves efficiency.

The hasToken flag provides clear intent and prevents unnecessary API calls when no authentication token is present.

Also applies to: 23-23


26-38: LGTM! More explicit handling of missing organization.

Returning undefined instead of a default organization object is more semantically correct and aligns with how consumers like organization-switcher.tsx handle the missing organization case.


39-39: Verify loading state behavior when no token is present.

The isLoaded logic returns true immediately when hasToken is false. This means consumers won't see a loading state even on initial render when there's no token. Confirm this is the intended UX:

  • Should users see a loading skeleton before we determine there's no token?
  • Does this align with how organization-switcher.tsx (line 11) expects the loading state?

The current implementation assumes "no token = fully loaded state with no data," which may be correct but should be verified against the intended user experience.

apps/dashboard/src/components/side-navigation/organization-dropdown.tsx (1)

1-9: LGTM! Clean conditional export pattern.

The feature-flag-based component selection is well-structured with clear naming and helpful comments. The const export for the component is an acceptable pattern here since it's aliasing one of two concrete component implementations.

As per coding guidelines, named exports are properly used.

apps/dashboard/src/utils/self-hosted/organization-switcher.tsx (3)

11-18: LGTM! Loading state enhances user experience.

The loading skeleton provides appropriate visual feedback and matches the pattern used in the Clerk variant for consistency.


23-28: LGTM! Simplified UI appropriate for non-interactive display.

The change from Button to a plain div structure aligns with the non-interactive nature of the community self-hosted organization display. Setting shining={false} is also appropriate for a static display component.


32-32: LGTM! Dual export aliases provide flexibility.

Exporting the component as both OrganizationDropdown and OrganizationSwitcher allows consumers to import using the name that best fits their context, which aligns with the conditional import pattern in organization-dropdown.tsx.

- Removed conditional rendering logic for OrganizationDropdown.
- Directly exported OrganizationDropdown from the Clerk component for clarity and maintainability.
- Reintroduced the alias mapping for '@' to point to the './src' directory.
- Removed unnecessary conditional alias mappings for self-hosted components.
@merrcury merrcury merged commit 7f9d6ed into next Jan 8, 2026
27 of 28 checks passed
@merrcury merrcury deleted the fix/selfhosted-organization-picker branch January 8, 2026 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant