Skip to content

Conversation

@Clement-coder
Copy link
Contributor

@Clement-coder Clement-coder commented Oct 4, 2025

🔥 Title

feat: Implement Dashboard Selector


📝 Summary

Added a dashboard selector at /dashboard to route users based on their role or show a selector for dual-role users.


🔄 Changes

  • Created page.tsx for dashboard redirection logic
  • Added DashboardSelector.tsx for dual-role selection UI
  • Updated useUserRole.ts to fetch actual user role and properties
  • Added getPropertiesByOwner to api.ts
  • Ran lint and format checks

🧪 Testing

  • /dashboard redirects correctly based on role
  • Dual users see the selector screen
  • All checks passed and code builds successfully

⚙️ Related Issue

Closes #167

Summary by CodeRabbit

  • New Features
    • Role-based dashboard access with automatic routing to host or tenant views.
    • Dashboard selector to choose Host or Guest dashboard with responsive cards and loading visuals.
    • Revamped Tenant Dashboard: tabbed navigation (Bookings, Wallet, Profile, Analytics) with stats, wallet details, transactions, and analytics.
    • Personalized greetings and conditional loading states.
    • Host-specific CTA to create a property when access is unavailable.
  • Improvements
    • Consistent guarded layouts and redirects, including login when unauthenticated.
  • UI/UX
    • Updated copy, icons, and streamlined layout; removed outdated help text.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 4, 2025

Walkthrough

Implements role-aware dashboard routing and access control using a new useUserRole hook and a RoleGuard component. The dashboard entry page conditionally redirects or renders a selector. Host and tenant dashboards are wrapped with RoleGuard. Adds a DashboardSelector component and restructures tenant UI into tabbed sections.

Changes

Cohort / File(s) Summary
Dashboard entry routing
apps/web/src/app/dashboard/page.tsx
Converts to default export; integrates useUserRole and useAuth; adds conditional redirects to host/tenant dashboards; renders DashboardSelector for dual role; introduces loading and unauthorized-host states with CTA.
Dashboard selector component
apps/web/src/components/dashboard/DashboardSelector.tsx
New client component rendering two selectable cards (Guest/Host) with loading overlays; uses lucide-react icons, Next Link, and internal loading key.
Role derivation hook
apps/web/src/hooks/useUserRole.ts
New hook deriving role, host access, and property presence from auth plus mock profile API; exposes role, canAccessHostDashboard, isLoading, hasProperties.
Route guard component
apps/web/src/hooks/auth/RoleGuard.tsx
New client RoleGuard enforcing requiredRole ('guest'
Host dashboard gating
apps/web/src/app/dashboard/host-dashboard/page.tsx
Wraps content in RoleGuard (required host); minor state refactor (setter renamed); adjusts top-level layout accordingly.
Tenant dashboard restructuring
apps/web/src/app/dashboard/tenant-dashboard/page.tsx
Wraps content in RoleGuard (required guest); introduces tabbed navigation (Bookings, Wallet, Profile, Analytics); adds headers, stats, wallet sections, transaction table placeholders, and analytics sections; updates avatar source.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant DP as /dashboard (DashboardPage)
  participant UR as useUserRole
  participant R as Next Router
  participant DS as DashboardSelector

  U->>DP: Navigate
  DP->>UR: Resolve role, access, loading
  UR-->>DP: { role, canAccessHostDashboard, isLoading }

  alt isLoading
    DP-->>U: Show Loading
  else role == null
    DP->>R: replace('/login' or '/dashboard')
  else role == 'dual'
    DP-->>U: Render DashboardSelector
    U->>DS: Choose Guest/Host
    DS->>R: push('/dashboard/tenant-dashboard' or '/dashboard/host-dashboard')
  else role == 'host' and canAccessHostDashboard
    DP->>R: replace('/dashboard/host-dashboard')
  else
    DP->>R: replace('/dashboard/tenant-dashboard')
  end
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant HD as /dashboard/host-dashboard
  participant TD as /dashboard/tenant-dashboard
  participant RG as RoleGuard
  participant UR as useUserRole
  participant R as Next Router

  U->>HD: Navigate
  HD->>RG: Wrap children (requiredRole='host')
  RG->>UR: Get role/access/loading
  UR-->>RG: role, canAccessHostDashboard, isLoading
  alt Loading
    RG-->>U: Full-screen loading
  else Authorized host (role in ['host','dual'] and canAccessHostDashboard)
    RG-->>U: Render Host Dashboard
  else
    RG->>R: replace('/dashboard')
  end

  U->>TD: Navigate
  TD->>RG: Wrap children (requiredRole='guest')
  RG->>UR: Get role/access/loading
  UR-->>RG: role, canAccessHostDashboard, isLoading
  alt Loading
    RG-->>U: Full-screen loading
  else Authorized guest (role in ['guest','dual'] or host without access)
    RG-->>U: Render Tenant Dashboard
  else
    RG->>R: replace('/dashboard')
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

I hop to the dashboards, two doors in sight,
A guest-blue burrow, a host-green light.
RoleGuard stands watch with a gentle stare,
“Are you verified? Do you belong there?”
With a twitch of my whiskers, I choose my way—
Dual paths, one bunny, delighted to stay. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The implementation does not fully satisfy issue #167 because redirect targets are using incorrect paths, authentication gating is missing, and hosts without properties are left in an infinite loading state, so the feature does not reliably route users based on their role. Update the /dashboard logic to use the correct /dashboard/host-dashboard and /dashboard/tenant-dashboard routes, integrate isAuthenticated checks, and add handling for hosts without properties to avoid infinite loading and enforce permission checks before navigation.
Out of Scope Changes Check ⚠️ Warning The PR includes modifications to host-dashboard and tenant-dashboard pages, introduces a RoleGuard component, and updates the useUserRole hook, all of which extend beyond the two files specified in issue #167 and are unrelated to the dashboard selector feature. Separate the out-of-scope changes into their own PR or feature branch and limit this PR to implementing only the /dashboard routing page and the DashboardSelector component as defined by the linked issue.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “feat: Implement dashboard selector” is concise and accurately highlights the primary change of introducing a dashboard selector feature, allowing teammates to understand the main purpose of the pull request at a glance.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

🧹 Nitpick comments (1)
apps/web/src/components/dashboard/DashboardSelector.tsx (1)

5-37: Consider adding a color class to the Home icon for consistency.

The User icon at line 16 has text-blue-600, but the Home icon at line 26 lacks a color class. For visual consistency, consider adding a color class to the Home icon.

Apply this diff to add a color class:

-              <Home className="w-12 h-12 mb-4" />
+              <Home className="w-12 h-12 text-green-600 mb-4" />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fa7fc1 and d176605.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • apps/backend/bun.config.ts (0 hunks)
  • apps/backend/src/config/supabase.ts (7 hunks)
  • apps/backend/src/controllers/booking.controller.ts (4 hunks)
  • apps/backend/src/services/sync.service.ts (1 hunks)
  • apps/backend/tests/integration/booking-integration.test.ts (3 hunks)
  • apps/backend/tests/utils/booking-test.utils.ts (1 hunks)
  • apps/web/src/app/dashboard/page.tsx (1 hunks)
  • apps/web/src/components/dashboard/DashboardSelector.tsx (1 hunks)
  • apps/web/src/hooks/useUserRole.ts (1 hunks)
  • apps/web/test-results/.last-run.json (1 hunks)
  • apps/web/tests/e2e/auth-guards.spec.ts (4 hunks)
  • apps/web/tests/e2e/auth.spec.ts (1 hunks)
  • apps/web/tests/e2e/dashboards.spec.ts (2 hunks)
  • apps/web/tests/e2e/role-separation.spec.ts (1 hunks)
  • apps/web/tsconfig.json (3 hunks)
💤 Files with no reviewable changes (1)
  • apps/backend/bun.config.ts
🧰 Additional context used
🧬 Code graph analysis (3)
apps/web/src/app/dashboard/page.tsx (2)
apps/web/src/hooks/useUserRole.ts (1)
  • useUserRole (9-53)
apps/web/src/components/dashboard/DashboardSelector.tsx (1)
  • DashboardSelector (5-37)
apps/web/src/hooks/useUserRole.ts (2)
apps/web/src/hooks/auth/use-auth.tsx (1)
  • useAuth (251-251)
apps/web/src/services/authService.ts (1)
  • isAuthenticated (86-105)
apps/web/src/components/dashboard/DashboardSelector.tsx (1)
apps/web/src/app/page.tsx (1)
  • Home (8-39)
🪛 ast-grep (0.39.5)
apps/web/tests/e2e/auth-guards.spec.ts

[warning] 28-28: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(PUBLIC_ROUTE)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (8)
apps/web/tests/e2e/role-separation.spec.ts (1)

59-59: LGTM!

Formatting improvement adds a trailing newline for consistency with project standards.

apps/web/tests/e2e/dashboards.spec.ts (1)

1-1: LGTM!

Formatting improvements: standardized import order and added trailing newline for consistency.

Also applies to: 130-130

apps/web/tests/e2e/auth.spec.ts (1)

64-64: LGTM!

Formatting improvement adds a trailing newline for consistency.

apps/web/tsconfig.json (1)

4-4: LGTM!

Formatting improvements collapse arrays to single lines for better readability without affecting TypeScript configuration behavior.

Also applies to: 26-27, 45-45

apps/web/tests/e2e/auth-guards.spec.ts (2)

1-1: LGTM!

Formatting improvements standardize import order and improve readability of localStorage setup blocks by expanding them to multi-line format.

Also applies to: 36-42, 55-61, 76-82, 98-104


110-119: LGTM!

The logout test flow addition enhances test coverage by verifying session clearing and redirection behavior.

apps/web/src/app/dashboard/page.tsx (1)

9-26: LGTM!

The refactored dashboard page correctly implements role-based routing:

  • Redirects host users with verified status to /dashboard/host
  • Redirects guest users to /dashboard/guest
  • Shows DashboardSelector for dual-role users
  • Includes appropriate dependency array in useEffect
apps/web/src/hooks/useUserRole.ts (1)

9-53: LGTM with note about mock implementation.

The hook correctly implements role derivation logic with appropriate error handling and state management. The mock profile data (lines 25-29) is clearly documented for demonstration purposes.

Remember to replace the mock implementation with real API calls when the profileAPI is implemented, as noted in the comments.

Based on the PR objectives and issue #167, ensure that the real API implementation will fetch:

  • User role ('guest', 'host', or 'dual')
  • Host status ('verified', 'unverified', or 'pending')
  • Property ownership data to validate host dashboard access

Copy link
Contributor

@respp respp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation has several critical issues that need to be addressed: First, the route redirects are incorrect - they point to /dashboard/host and /dashboard/guest which don't exist, when they should point to /dashboard/host-dashboard and /dashboard/tenant-dashboard (update both page.tsx and DashboardSelector.tsx). Second, there's an infinite loading state bug when a user has role === 'host' but canAccessHostDashboard === false (verified host without properties) - this condition is never handled in the redirect logic, leaving users stuck on "Loading...". Third, the implementation lacks authentication checks (isAuthenticated is not being used) and proper loading/error states. Fourth, the DashboardSelector component has a security vulnerability as it allows any user to navigate to any dashboard without permission verification - consider integrating the existing RoleGuard component for route protection. Finally, there are minor UI issues: the Home icon is missing color styling (should be text-green-600 to match the User icon pattern), and the component could benefit from loading spinners and transition animations to match the rest of the application's UX. Please fix the route paths and the role logic first as these are blocking issues, then address the authentication and security concerns.

@Clement-coder
Copy link
Contributor Author

The implementation has several critical issues that need to be addressed: First, the route redirects are incorrect - they point to /dashboard/host and /dashboard/guest which don't exist, when they should point to /dashboard/host-dashboard and /dashboard/tenant-dashboard (update both page.tsx and DashboardSelector.tsx). Second, there's an infinite loading state bug when a user has role === 'host' but canAccessHostDashboard === false (verified host without properties) - this condition is never handled in the redirect logic, leaving users stuck on "Loading...". Third, the implementation lacks authentication checks (isAuthenticated is not being used) and proper loading/error states. Fourth, the DashboardSelector component has a security vulnerability as it allows any user to navigate to any dashboard without permission verification - consider integrating the existing RoleGuard component for route protection. Finally, there are minor UI issues: the Home icon is missing color styling (should be text-green-600 to match the User icon pattern), and the component could benefit from loading spinners and transition animations to match the rest of the application's UX. Please fix the route paths and the role logic first as these are blocking issues, then address the authentication and security concerns.

Thanks for the detailed feedback @respp! I’ll fix the route paths and role logic first, then address the authentication and security issues next.

This commit addresses several critical issues in the user dashboard

functionality.

It resolves an infinite loading bug for hosts without properties by

implementing a dedicated UI state that prompts them to create a property

instead of causing a redirect loop.

The RoleGuard component has been refactored to provide stricter and more

centralized route protection, ensuring users can only access dashboards

appropriate for their role. This closes a security vulnerability where

users could bypass role checks.

Additionally, the dashboard selector UI has been enhanced with loading

spinners and transition animations to improve user experience.
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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d176605 and fa25344.

📒 Files selected for processing (5)
  • apps/web/src/app/dashboard/host-dashboard/page.tsx (3 hunks)
  • apps/web/src/app/dashboard/page.tsx (1 hunks)
  • apps/web/src/app/dashboard/tenant-dashboard/page.tsx (3 hunks)
  • apps/web/src/components/dashboard/DashboardSelector.tsx (1 hunks)
  • apps/web/src/hooks/auth/RoleGuard.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/web/src/app/dashboard/host-dashboard/page.tsx (2)
apps/web/src/hooks/auth/RoleGuard.tsx (1)
  • RoleGuard (13-38)
apps/web/src/app/dashboard/host-dashboard/components/CalendarModal.tsx (1)
  • CalendarModal (50-142)
apps/web/src/app/dashboard/page.tsx (3)
apps/web/src/hooks/useUserRole.ts (1)
  • useUserRole (9-53)
apps/web/src/hooks/auth/use-auth.tsx (1)
  • useAuth (251-251)
apps/web/src/components/dashboard/DashboardSelector.tsx (1)
  • DashboardSelector (7-85)
apps/web/src/app/dashboard/tenant-dashboard/page.tsx (1)
apps/web/src/hooks/auth/RoleGuard.tsx (1)
  • RoleGuard (13-38)
apps/web/src/hooks/auth/RoleGuard.tsx (1)
apps/web/src/hooks/useUserRole.ts (1)
  • useUserRole (9-53)

This commit addresses two issues identified in code review:

- The RoleGuard now correctly allows users with the 'dual' role to

  access the host dashboard, preventing them from being redirected.

- Keyboard navigation on the dashboard selector is fixed. The onClick

  handler has been moved to the Link component, allowing keyboard

  users to navigate to the dashboards correctly.
This commit refactors the condition for the host guard to more

explicitly handle dual-role users.

The previous implementation was logically equivalent, but the new

condition `!(role === "host" || role === "dual")` is clearer and

more directly reflects the requirement to allow both "host" and

"dual" roles.
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: 2

🧹 Nitpick comments (4)
apps/web/src/hooks/auth/RoleGuard.tsx (3)

26-29: Add a comment to clarify the guest role logic.

The condition role !== 'guest' && role !== 'dual' && !(role === 'host' && !canAccessHostDashboard) is correct but uses a triple-negative structure that is difficult to parse. Consider adding a brief comment explaining that the guest dashboard allows: guest users, dual users, and host users who cannot access the host dashboard.

Example:

     } else if (requiredRole === 'guest') {
+      // Allow: guest, dual, or host without host-dashboard access
       if (role !== 'guest' && role !== 'dual' && !(role === 'host' && !canAccessHostDashboard)) {
         router.replace('/dashboard');

33-35: Consider enhancing the loading state.

The current loading indicator is functional but basic. Per the PR feedback, consider adding a loading spinner and transition animations to match the app's UX patterns. This would provide a more polished experience during role verification.

Example with a spinner:

   if (isLoading) {
-    return <div className="flex items-center justify-center min-h-screen">Loading...</div>;
+    return (
+      <div className="flex items-center justify-center min-h-screen">
+        <div className="flex flex-col items-center gap-2">
+          <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900" />
+          <p className="text-sm text-gray-600">Loading...</p>
+        </div>
+      </div>
+    );
   }

17-31: Consider adding explicit unauthenticated user handling.

While the current logic implicitly handles unauthenticated users (when role is null) by redirecting them to /dashboard, adding an explicit check would make the intent clearer and address the "missing auth handling" concern from the PR feedback.

Example:

   useEffect(() => {
     if (isLoading) {
       return;
     }
+
+    // Redirect unauthenticated users to main dashboard
+    if (!role) {
+      router.replace('/dashboard');
+      return;
+    }

     if (requiredRole === 'host') {
apps/web/src/components/dashboard/DashboardSelector.tsx (1)

35-39: Enhance accessibility for loading states.

The loading overlays lack screen reader announcements. Users relying on assistive technology won't know when navigation is in progress.

Add aria-live regions to announce loading states:

 {loading === 'guest' && (
-  <div className="absolute inset-0 bg-white dark:bg-gray-800 bg-opacity-50 flex items-center justify-center rounded-xl">
+  <div 
+    className="absolute inset-0 bg-white dark:bg-gray-800 bg-opacity-50 flex items-center justify-center rounded-xl"
+    role="status"
+    aria-live="polite"
+  >
     <Loader2 className="w-8 h-8 animate-spin text-blue-600" />
+    <span className="sr-only">Loading Guest Dashboard...</span>
   </div>
 )}

Apply the same pattern to the host loading overlay (lines 56-60).

Also applies to: 56-60

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa25344 and 5c3317f.

📒 Files selected for processing (2)
  • apps/web/src/components/dashboard/DashboardSelector.tsx (1 hunks)
  • apps/web/src/hooks/auth/RoleGuard.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/hooks/auth/RoleGuard.tsx (1)
apps/web/src/hooks/useUserRole.ts (1)
  • useUserRole (9-53)
🔇 Additional comments (2)
apps/web/src/hooks/auth/RoleGuard.tsx (1)

22-25: Past review concern addressed.

The condition now correctly allows dual-role users to access the host dashboard by checking !(role === 'host' || role === 'dual'), which resolves the blocker identified in the previous review.

apps/web/src/components/dashboard/DashboardSelector.tsx (1)

22-41: RoleGuard protection verified. The tenant-dashboard page at apps/web/src/app/dashboard/tenant-dashboard/page.tsx is wrapped in <RoleGuard requiredRole="guest">.

This commit addresses several points of feedback from code review to

improve the dashboard functionality.

In `RoleGuard.tsx`:

- Adds a comment to clarify the guest role validation logic.

- Enhances the loading state with a spinner for a better user experience.

- Adds an explicit check to handle unauthenticated users.

In `DashboardSelector.tsx`:

- Improves accessibility of the loading state by adding `aria-live`

  regions for screen readers.

- Implements a more robust navigation handler that clears the loading

  state on navigation success, failure, or timeout.
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: 2

🧹 Nitpick comments (1)
apps/web/src/hooks/auth/RoleGuard.tsx (1)

33-38: Clarify the guest dashboard access logic.

The condition on line 35 is logically correct but complex. It redirects users who are neither guest, dual, nor host without host access—effectively sending dual users and hosts with access back to /dashboard (the selector). This enforces that those users should choose their dashboard explicitly rather than directly accessing the guest route.

Add a comment explaining the intent:

 } else if (requiredRole === 'guest') {
+  // Guest dashboard: allow guest, dual, or host without host access.
+  // Redirect dual users and hosts with access to the selector.
   if (role !== 'guest' && role !== 'dual' && !(role === 'host' && !canAccessHostDashboard)) {

This improves maintainability for future developers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c3317f and e190a4a.

📒 Files selected for processing (2)
  • apps/web/src/components/dashboard/DashboardSelector.tsx (1 hunks)
  • apps/web/src/hooks/auth/RoleGuard.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/hooks/auth/RoleGuard.tsx (1)
apps/web/src/hooks/useUserRole.ts (1)
  • useUserRole (9-53)
🔇 Additional comments (1)
apps/web/src/components/dashboard/DashboardSelector.tsx (1)

13-32: LGTM: Navigation and loading state are well handled.

The implementation correctly:

  • Prevents default link behavior to manage loading state.
  • Sets an 8-second fallback timeout to clear loading if navigation stalls.
  • Cleans up the timeout on unmount and after navigation completes.

This addresses the previous review feedback about loading state persistence.

Comment on lines +77 to +102
<Link
href="/dashboard/host-dashboard"
onClick={(e) => handleNavigation(e, 'host', '/dashboard/host-dashboard')}
className="group relative block focus:outline-none"
>
<div className="p-8 bg-white dark:bg-gray-800 rounded-xl shadow-lg hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1 border-2 border-transparent hover:border-green-500">
<Home className="w-12 h-12 text-green-600 mb-4" />
<h2 className="text-2xl font-bold mb-2 text-gray-900 dark:text-white">
Host Dashboard
</h2>
<p className="text-gray-600 dark:text-gray-300">
Manage properties, bookings, and earnings
</p>
{loading === 'host' && (
<div
className="absolute inset-0 bg-white dark:bg-gray-800 bg-opacity-50 flex items-center justify-center rounded-xl"
// biome-ignore lint/a11y/useSemanticElements: Using a div with role='status' for the loading overlay.
role="status"
aria-live="polite"
>
<Loader2 className="w-8 h-8 animate-spin text-green-600" />
<span className="sr-only">Loading Host Dashboard...</span>
</div>
)}
</div>
</Link>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Poor UX: selector allows navigation to inaccessible dashboards.

The host dashboard card is always enabled, so users lacking host access can click it, see a loading spinner, navigate to /dashboard/host-dashboard, then get immediately redirected back by RoleGuard. This creates a confusing loop.

Integrate useUserRole to check canAccessHostDashboard and conditionally disable or hide the host card for users without access:

+'use client';
+import { useUserRole } from '@/hooks/useUserRole';
 
 export function DashboardSelector() {
+  const { canAccessHostDashboard } = useUserRole();
   const [loading, setLoading] = useState<string | null>(null);
   ...
 
   return (
     ...
-          <Link
+          <Link
             href="/dashboard/host-dashboard"
             onClick={(e) => handleNavigation(e, 'host', '/dashboard/host-dashboard')}
-            className="group relative block focus:outline-none"
+            className={`group relative block focus:outline-none ${!canAccessHostDashboard ? 'opacity-50 pointer-events-none' : ''}`}
+            aria-disabled={!canAccessHostDashboard}
           >

This prevents navigation attempts and clarifies to users which dashboards they can access.

Based on reviewer feedback and PR objectives.

📝 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.

Suggested change
<Link
href="/dashboard/host-dashboard"
onClick={(e) => handleNavigation(e, 'host', '/dashboard/host-dashboard')}
className="group relative block focus:outline-none"
>
<div className="p-8 bg-white dark:bg-gray-800 rounded-xl shadow-lg hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1 border-2 border-transparent hover:border-green-500">
<Home className="w-12 h-12 text-green-600 mb-4" />
<h2 className="text-2xl font-bold mb-2 text-gray-900 dark:text-white">
Host Dashboard
</h2>
<p className="text-gray-600 dark:text-gray-300">
Manage properties, bookings, and earnings
</p>
{loading === 'host' && (
<div
className="absolute inset-0 bg-white dark:bg-gray-800 bg-opacity-50 flex items-center justify-center rounded-xl"
// biome-ignore lint/a11y/useSemanticElements: Using a div with role='status' for the loading overlay.
role="status"
aria-live="polite"
>
<Loader2 className="w-8 h-8 animate-spin text-green-600" />
<span className="sr-only">Loading Host Dashboard...</span>
</div>
)}
</div>
</Link>
'use client';
import { useState } from 'react';
import Link from 'next/link';
import { useUserRole } from '@/hooks/useUserRole';
import { Loader2 } from 'lucide-react';
import { Home } from 'lucide-react';
export function DashboardSelector() {
const { canAccessHostDashboard } = useUserRole();
const [loading, setLoading] = useState<string | null>(null);
const handleNavigation = (e: React.MouseEvent, dashboard: string, path: string) => {
e.preventDefault();
setLoading(dashboard);
// existing navigation logic, e.g. router.push(path).finally(() => setLoading(null))
};
return (
<>
{/* … other cards … */}
<Link
href="/dashboard/host-dashboard"
onClick={(e) => handleNavigation(e, 'host', '/dashboard/host-dashboard')}
className={`group relative block focus:outline-none ${
!canAccessHostDashboard ? 'opacity-50 pointer-events-none' : ''
}`}
aria-disabled={!canAccessHostDashboard}
>
<div className="p-8 bg-white dark:bg-gray-800 rounded-xl shadow-lg hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1 border-2 border-transparent hover:border-green-500">
<Home className="w-12 h-12 text-green-600 mb-4" />
<h2 className="text-2xl font-bold mb-2 text-gray-900 dark:text-white">
Host Dashboard
</h2>
<p className="text-gray-600 dark:text-gray-300">
Manage properties, bookings, and earnings
</p>
{loading === 'host' && (
<div
className="absolute inset-0 bg-white dark:bg-gray-800 bg-opacity-50 flex items-center justify-center rounded-xl"
role="status"
aria-live="polite"
>
<Loader2 className="w-8 h-8 animate-spin text-green-600" />
<span className="sr-only">Loading Host Dashboard...</span>
</div>
)}
</div>
</Link>
{/* … other cards … */}
</>
);
}

}

if (requiredRole === 'host') {
// TODO: Add check for hasProperties when available in useUserRole
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Implement the missing properties check.

The TODO flags a critical gap: per the PR objectives, "Host dashboard accessible only if hostStatus === 'verified' and the user has properties." Currently, canAccessHostDashboard from useUserRole only verifies hostStatus === 'verified', omitting the properties check. This allows verified hosts without properties to access the host dashboard, violating the requirement.

Update useUserRole to include a properties check in canAccessHostDashboard, or add an explicit property-count verification here before allowing host access.

Based on PR objectives and reviewer feedback.

🤖 Prompt for AI Agents
In apps/web/src/hooks/auth/RoleGuard.tsx around line 29, the TODO notes a
missing properties check: ensure that host dashboard access requires both
hostStatus === 'verified' and that the user actually has properties; either
update useUserRole to make canAccessHostDashboard return hostStatus ===
'verified' && hasProperties (or properties?.length > 0) or add an explicit check
here before granting host access (validate properties array/flag is present and
non-empty), and use that combined boolean in the access decision.

The host dashboard should only be accessible to verified hosts who have
at least one property.

This commit updates the `useUserRole` hook to include a `hasProperties`
check in the `canAccessHostDashboard` logic. The `RoleGuard` component
now correctly prevents access to the host dashboard if the user has no
properties, resolving the `TODO`.
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: 1

🧹 Nitpick comments (1)
apps/web/src/hooks/auth/RoleGuard.tsx (1)

32-37: Simplify the guest guard logic for readability.

The current condition correctly allows guests, dual users, and hosts without host access, but the triple-negative logic is hard to parse. Consider simplifying to:

     } else if (requiredRole === 'guest') {
-      // Allow: guest, dual, or host without host-dashboard access
-      if (role !== 'guest' && role !== 'dual' && !(role === 'host' && !canAccessHostDashboard)) {
+      // Redirect only hosts with host dashboard access
+      if (role === 'host' && canAccessHostDashboard) {
         router.replace('/dashboard');
       }

Both implementations produce the same behavior, but the simplified version directly expresses the intent: "redirect hosts who can access the host dashboard."

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e190a4a and 28f53fa.

📒 Files selected for processing (2)
  • apps/web/src/hooks/auth/RoleGuard.tsx (1 hunks)
  • apps/web/src/hooks/useUserRole.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/src/hooks/auth/RoleGuard.tsx (1)
apps/web/src/hooks/useUserRole.ts (1)
  • useUserRole (15-66)
apps/web/src/hooks/useUserRole.ts (2)
apps/web/src/hooks/auth/use-auth.tsx (1)
  • useAuth (251-251)
apps/web/src/services/authService.ts (1)
  • isAuthenticated (86-105)

Clement-coder and others added 2 commits October 8, 2025 13:06
The `useUserRole` hook is updated to fetch user profiles from a mock API
instead of using a hardcoded mock profile.

This change introduces a `profileAPI` mock that simulates fetching user
data, with different profiles for different user IDs in a development
environment. This makes the hook more realistic and easier to test with
different user roles.
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

🧹 Nitpick comments (2)
apps/web/src/app/dashboard/page.tsx (2)

17-32: Verify the redirect logic handles all edge cases.

The redirect logic correctly addresses the previously reported issues:

  • Routes to correct paths (/dashboard/host-dashboard and /dashboard/tenant-dashboard)
  • Handles host without dashboard access by not redirecting (allowing the CTA UI to show)

However, consider explicitly handling the role === null case after loading completes. Currently, if isLoading === false and isAuthenticated === true but role === null, no redirect occurs, and the user sees the default dashboard selection UI. While unlikely if useUserRole works correctly, adding an explicit check would make the intent clearer:

if (role === null) {
  // Log error or show error state
  return;
}

59-61: Pass canAccessHostDashboard to DashboardSelector for better UX.

Currently, DashboardSelector shows both dashboard options to all dual-role users, even if they cannot access the host dashboard (canAccessHostDashboard === false). While the dashboard pages themselves should be protected (as mentioned in the PR objectives via RoleGuard), this creates a poor user experience where users click the host option only to be blocked.

Consider passing canAccessHostDashboard as a prop to DashboardSelector so it can conditionally hide, disable, or show a message for the host dashboard option when access is not available:

-  if (role === 'dual') {
-    return <DashboardSelector />;
-  }
+  if (role === 'dual') {
+    return <DashboardSelector canAccessHostDashboard={canAccessHostDashboard} />;
+  }

Then update DashboardSelector to accept this prop and adjust its UI accordingly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb33503 and 7715e33.

📒 Files selected for processing (1)
  • apps/web/src/app/dashboard/page.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/app/dashboard/page.tsx (3)
apps/web/src/hooks/useUserRole.ts (1)
  • useUserRole (58-102)
apps/web/src/hooks/auth/use-auth.tsx (1)
  • useAuth (251-251)
apps/web/src/components/dashboard/DashboardSelector.tsx (1)
  • DashboardSelector (8-107)
🔇 Additional comments (2)
apps/web/src/app/dashboard/page.tsx (2)

64-149: RoleGuard protection verified on both dashboards. Both host and tenant dashboard pages wrap their content in <RoleGuard requiredRole="host"> and <RoleGuard requiredRole="guest"> respectively, preventing unauthorized access.


42-57: Confirm /add-property route exists
I couldn’t find an add-property page under apps/web/src/app; please verify (or add) the /add-property route implementation.

@respp respp merged commit 7715e33 into Stellar-Rent:main Oct 10, 2025
1 check passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 30, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants