Skip to content

Conversation

@Macnelson9
Copy link

@Macnelson9 Macnelson9 commented Jan 22, 2026

feat(web): implement invitations page with search, filters (closes #191)
feat(web): implement roleguard component (closes #201)

Add search and status filtering with created/property sorting. Add pagination for mobile/desktop and a responsive mobile menu drawer. Resolve useUserRole merge conflicts and add loading state.

StellarRent Logo

Pull Request | StellarRent

📝 Summary

Implement invitations page with search, filters, and pagination.

Add comprehensive functionality to the invitations page, including search by property, owner, status, or invitation, status filtering (all, pending, accepted, declined, expired), sorting by created date and property name, pagination for mobile and desktop views, and a responsive mobile menu drawer. Also, resolve merge conflicts in useUserRole hook by consolidating code and adding loading state.

🔗 Related Issues

Closes #191 (Replace with the actual issue number).

🔄 Changes Made

Provide a general description of the changes. Include any relevant background information or context to help reviewers understand the purpose of this PR.
Added the invitations/page.tsx placeholder page based on spec provided in the figma file.

🖼️ Current Output

Provide visual evidence of the changes:

  • For small changes: Screenshots.
Screenshot from 2026-01-22 13-52-25 Screenshot from 2026-01-22 13-00-19 Screenshot from 2026-01-22 13-00-29 Screenshot from 2026-01-22 13-52-25
  • For large changes: Video or Loom link.

🧪 Testing

If applicable, describe the tests performed. Include screenshots, test outputs, or any resources that help reviewers understand how the changes were tested.

✅ Testing Checklist

  • Unit tests added/modified
  • Integration tests performed
  • Manual tests executed
  • All tests pass in CI/CD

⚠️ Potential Risks

List any possible issues that might arise with this change.

🚀 Next Steps & Improvements

This change lays a solid foundation for further optimizations. Some areas that could benefit from future improvements include:

  • 🔹 Performance optimization
  • 🔹 Increased test coverage
  • 🔹 Potential user experience enhancements

💬 Comments

Any additional context, questions, or considerations for reviewers.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced invitations page with search, filtering, and sorting capabilities
    • Added pagination controls for managing invitations across views
    • Improved mobile experience with responsive drawer navigation and streamlined layout
  • Bug Fixes

    • Refined authentication and access control logic for better reliability
    • Enhanced loading state indicators during system checks

✏️ Tip: You can customize this high-level summary in your review settings.

…tellar-Rent#191)

Add search and status filtering with created/property sorting.
Add pagination for mobile/desktop and a responsive mobile menu drawer.
Resolve useUserRole merge conflicts and add loading state.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

The pull request relocates RoleGuard from hooks to a dedicated component directory and refactors it with unified loading state and computed access logic. The invitations page is completely rewritten to include mobile-responsive navigation, real-time search/filtering, sorting, and pagination. Corresponding imports are updated across dashboards, and obsolete hooks are removed.

Changes

Cohort / File(s) Change Summary
RoleGuard Component Refactoring
apps/web/src/components/guards/RoleGuard.tsx
Replaces per-branch loading checks with unified loading state; introduces useMemo-based access computation considering role, authentication status, and host-dashboard capability. Adds useEffect-based redirect on access denial. Updates insufficient-access UI messaging and button labels. Simplifies loading indicator. Updates children prop type signature from React.ReactNode to ReactNode.
Import Path Migration
apps/web/src/app/dashboard/host-dashboard/page.tsx, apps/web/src/app/dashboard/tenant-dashboard/page.tsx
Updates RoleGuard import from default export at @/hooks/auth/RoleGuard to named export from @/components/guards/RoleGuard. Removes unused Breadcrumb import in host-dashboard.
Invitations Page Rewrite
apps/web/src/app/invitations/page.tsx
Transforms placeholder into full-featured interactive page with role-based navigation, mobile-responsive drawer, comprehensive header, search/filter/sort controls, and dual rendering paths (mobile card list and desktop table). Introduces client-side state management for search, status filtering, sort orders, and per-view pagination. Integrates multiple new UI components: SearchBar, FilterDropdown, DesktopInvitationsTable, MobileInvitationsList, PaginationControls, MobileMenuDrawer.
Hook Removal
apps/web/src/hooks/auth/RoleGuard.tsx, apps/web/src/hooks/useUserRole.ts
Deletes old RoleGuard client component (52 lines) and entire useUserRole hook implementation (102 lines) including mock profile API, role state management, and host-dashboard access computation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #178: Modifies the same role-based auth code (RoleGuard and useUserRole implementations and their import paths), indicating direct architectural overlap.
  • PR #189: Adds initial InvitationsPage placeholder component; this PR completely rewrites the same file into a fully interactive, auth-aware implementation.
  • PR #181: Creates/modifies useUserRole hook (same file being deleted in this PR), suggesting coordinated changes to the role management system.

Suggested reviewers

  • respp

Poem

🐰 The guard now stands in components so grand,
With logic refined and access well-planned,
Invitations bloom with search and with sort,
Mobile and desktop, a responsive fort! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing the Invitations page with search and filters functionality as specified in issue #191.
Linked Issues check ✅ Passed The PR successfully implements issue #191 (Invitations page with search, filters, pagination, responsive mobile drawer) and issue #201 (RoleGuard component with role-based access control and proper loading/redirect states).
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue objectives: Invitations page enhancement, RoleGuard implementation, and necessary file reorganization (moving RoleGuard to components/guards and updating imports across pages).

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

✨ Finishing touches
  • 📝 Generate docstrings

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: 2

🤖 Fix all issues with AI agents
In `@apps/web/src/app/invitations/page.tsx`:
- Around line 300-305: The JSX references an undefined symbol mobileInvitations
causing build/runtime errors; fix by declaring it before use (e.g., inside the
component scope, add a line like const mobileInvitations = invitations ?? []; or
derive it from an existing invitations array (const mobileInvitations =
(invitations || []).filter(...) if you need mobile-specific filtering) so the
mobile empty-state check and rendering in page.tsx can safely reference
mobileInvitations.
- Around line 45-53: Reset pagination when filters/search/sort change: after you
compute totalPages and desktopTotalPages (the values derived from the useMemo
filtered list), add a useEffect that watches searchQuery, statusFilter,
createdSort, propertySort (and optionally totalPages/desktopTotalPages) and call
setCurrentPage(1) and setDesktopPage(1) or clamp current values to
Math.min(currentPage, totalPages) / Math.min(desktopPage, desktopTotalPages).
This ensures currentPage/desktopPage (and their setters
setCurrentPage/setDesktopPage) are reset or clamped whenever the
filter/search/sort state changes so the pagination slice never points past
available pages.
🧹 Nitpick comments (1)
apps/web/src/app/invitations/page.tsx (1)

67-122: Extract a shared filter/sort helper to keep mobile & desktop logic in sync.
The two useMemo blocks are identical, which risks divergence over time. Consider a single helper to reduce duplication.

♻️ Suggested refactor
+  const filterAndSortInvitations = (invitations: Invitation[]) => {
+    const normalizedQuery = searchQuery.trim().toLowerCase();
+    let filtered = invitations.filter((invite) => {
+      if (statusFilter !== 'all' && invite.status.toLowerCase() !== statusFilter) {
+        return false;
+      }
+      if (!normalizedQuery) return true;
+      return (
+        invite.property.toLowerCase().includes(normalizedQuery) ||
+        invite.owner.toLowerCase().includes(normalizedQuery) ||
+        invite.status.toLowerCase().includes(normalizedQuery) ||
+        invite.invitation.toLowerCase().includes(normalizedQuery)
+      );
+    });
+
+    filtered = [...filtered].sort((left, right) => {
+      const propertyCompare = left.property.localeCompare(right.property);
+      if (propertyCompare !== 0) {
+        return propertySort === 'a-z' ? propertyCompare : -propertyCompare;
+      }
+
+      const leftDate = new Date(left.created).getTime();
+      const rightDate = new Date(right.created).getTime();
+      return createdSort === 'newest' ? rightDate - leftDate : leftDate - rightDate;
+    });
+
+    return filtered;
+  };
+
-  const filteredDesktopInvitations = useMemo(() => {
-    const normalizedQuery = searchQuery.trim().toLowerCase();
-    let filtered = DESKTOP_INVITATIONS.filter((invite) => {
-      if (statusFilter !== 'all' && invite.status.toLowerCase() !== statusFilter) {
-        return false;
-      }
-      if (!normalizedQuery) return true;
-      return (
-        invite.property.toLowerCase().includes(normalizedQuery) ||
-        invite.owner.toLowerCase().includes(normalizedQuery) ||
-        invite.status.toLowerCase().includes(normalizedQuery) ||
-        invite.invitation.toLowerCase().includes(normalizedQuery)
-      );
-    });
-
-    filtered = [...filtered].sort((left, right) => {
-      const propertyCompare = left.property.localeCompare(right.property);
-      if (propertyCompare !== 0) {
-        return propertySort === 'a-z' ? propertyCompare : -propertyCompare;
-      }
-
-      const leftDate = new Date(left.created).getTime();
-      const rightDate = new Date(right.created).getTime();
-      return createdSort === 'newest' ? rightDate - leftDate : leftDate - rightDate;
-    });
-
-    return filtered;
-  }, [createdSort, propertySort, searchQuery, statusFilter]);
+  const filteredDesktopInvitations = useMemo(
+    () => filterAndSortInvitations(DESKTOP_INVITATIONS),
+    [createdSort, propertySort, searchQuery, statusFilter]
+  );

-  const filteredMobileInvitations = useMemo(() => {
-    const normalizedQuery = searchQuery.trim().toLowerCase();
-    let filtered = MOBILE_INVITATIONS.filter((invite) => {
-      if (statusFilter !== 'all' && invite.status.toLowerCase() !== statusFilter) {
-        return false;
-      }
-      if (!normalizedQuery) return true;
-      return (
-        invite.property.toLowerCase().includes(normalizedQuery) ||
-        invite.owner.toLowerCase().includes(normalizedQuery) ||
-        invite.status.toLowerCase().includes(normalizedQuery) ||
-        invite.invitation.toLowerCase().includes(normalizedQuery)
-      );
-    });
-
-    filtered = [...filtered].sort((left, right) => {
-      const propertyCompare = left.property.localeCompare(right.property);
-      if (propertyCompare !== 0) {
-        return propertySort === 'a-z' ? propertyCompare : -propertyCompare;
-      }
-
-      const leftDate = new Date(left.created).getTime();
-      const rightDate = new Date(right.created).getTime();
-      return createdSort === 'newest' ? rightDate - leftDate : leftDate - rightDate;
-    });
-
-    return filtered;
-  }, [createdSort, propertySort, searchQuery, statusFilter]);
+  const filteredMobileInvitations = useMemo(
+    () => filterAndSortInvitations(MOBILE_INVITATIONS),
+    [createdSort, propertySort, searchQuery, statusFilter]
+  );

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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

🤖 Fix all issues with AI agents
In `@apps/web/src/app/invitations/page.tsx`:
- Around line 300-304: The Tailwind arbitrary color class in the mobile
empty-state div uses invalid backticks (bg-[`#071429`]) so the background color
doesn't apply; update the class to use valid syntax (bg-[`#071429`]) in the JSX
element that renders the "No invitations sent yet" container (the div inside
filteredMobileInvitations.length === 0 branch) so Tailwind recognizes the color,
or alternatively replace it with an equivalent utility or inline style if
preferred.
♻️ Duplicate comments (1)
apps/web/src/app/invitations/page.tsx (1)

45-135: Reset pagination when filters/search/sort change.
Page indices can drift out of range after filtering/sorting, producing empty screens. Please reset or clamp on dependency changes.

✅ Suggested fix
   const [createdSort, setCreatedSort] = useState('newest');
   const [propertySort, setPropertySort] = useState('a-z');
   const mobileRowsPerPage = 5;
   const desktopRowsPerPage = 10;
+
+  useEffect(() => {
+    setCurrentPage(1);
+    setDesktopPage(1);
+  }, [searchQuery, statusFilter, createdSort, propertySort]);
🧹 Nitpick comments (1)
apps/web/src/app/invitations/page.tsx (1)

67-122: Consider extracting shared filter/sort logic.
The mobile/desktop filtering blocks are identical—this is a good candidate for a helper to avoid divergence.

Example sketch:

const filterAndSortInvitations = (items: Invitation[]) => {
  const normalizedQuery = searchQuery.trim().toLowerCase();
  let filtered = items.filter((invite) => { /* same logic */ });
  return [...filtered].sort((left, right) => { /* same sort */ });
};

const filteredDesktopInvitations = useMemo(
  () => filterAndSortInvitations(DESKTOP_INVITATIONS),
  [createdSort, propertySort, searchQuery, statusFilter]
);

const filteredMobileInvitations = useMemo(
  () => filterAndSortInvitations(MOBILE_INVITATIONS),
  [createdSort, propertySort, searchQuery, statusFilter]
);

@Macnelson9 Macnelson9 force-pushed the feature/placeholder-guest-invitations-page branch from 885cb35 to 47a2097 Compare January 22, 2026 21:33
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.

While I appreciate your work on both Issue #191 and #201, please avoid combining multiple issues into a single PR in the future to keep the review process cleaner.

Currently, there is an integration conflict with RoleGuard: the existing code still references the old version in hooks/auth/, meaning your new component in components/guards/ isn't actually being used; please migrate all references to the new one and delete the legacy file.

Additionally, the invitations page needs refactoring to address a syntax error on line 303 of page.tsx, the removal of unnecessary empty constants (MOBILE_INVITATIONS/DESKTOP_INVITATIONS), and the cleanup of duplicated filtering logic between mobile and desktop.

Finally, please break down the 475-line component into smaller sub-components and delete the legacy useUserRole.ts mock to ensure we are using the new .tsx implementation consistently.

@Macnelson9
Copy link
Author

Hey there, you can take a look at the latest commit. I have fixed all you have pointed out. Thanks for your time.

@Macnelson9 Macnelson9 requested a review from respp January 22, 2026 23:03
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

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/app/dashboard/host-dashboard/page.tsx (1)

2-8: Import Breadcrumb to avoid a build error.
<Breadcrumb /> is used (Line 204) but not imported, which will fail compilation.

🐛 Suggested fix
 import NetworkStatus from '@/components/NetworkStatus';
 import BookingHistory from '@/components/dashboard/BookingHistory';
 import NotificationSystem from '@/components/dashboard/NotificationSystem';
 import ProfileManagement from '@/components/dashboard/ProfileManagement';
 import PropertyManagement from '@/components/dashboard/PropertyManagement';
+import Breadcrumb from '@/components/ui/breadcrumb';
 import { RoleGuard } from '@/components/guards/RoleGuard';
🤖 Fix all issues with AI agents
In `@apps/web/src/app/invitations/page.tsx`:
- Around line 284-295: The SearchBar input is missing an accessible label;
update the SearchBar component to provide one by adding a visible or
programmatic label tied to the input (e.g., add a <label> with htmlFor and give
the input a unique id, or add aria-label/aria-labelledby) so screen readers can
announce the field; ensure the label text conveys "Search invitations" (or
similar) and keep the existing props/value/onChange handlers intact.
🧹 Nitpick comments (1)
apps/web/src/app/invitations/page.tsx (1)

24-24: Clamp page indices when the data set changes.
Handlers reset pages, but if invitations refresh asynchronously, mobilePage / desktopPage can drift past the new totals and render empty results. Clamping on total change keeps pagination valid.

♻️ Suggested fix
-import { useMemo, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
@@
   const currentDesktopInvitations = filteredInvitations.slice(
     (desktopPage - 1) * DESKTOP_ROWS_PER_PAGE,
     desktopPage * DESKTOP_ROWS_PER_PAGE
   );
+
+  useEffect(() => {
+    setMobilePage((prev) => Math.min(prev, mobileTotalPages));
+    setDesktopPage((prev) => Math.min(prev, desktopTotalPages));
+  }, [mobileTotalPages, desktopTotalPages]);

Also applies to: 101-116

Comment on lines +284 to +295
const SearchBar = ({ value, onChange }: { value: string; onChange: (value: string) => void }) => (
<div className="relative w-full max-w-md">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">
<Search className="h-4 w-4" />
</span>
<input
type="text"
placeholder="Search an invitation..."
className="w-full rounded-full border border-gray-800 bg-background py-2 pl-9 pr-3 text-white placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary/60"
value={value}
onChange={(event) => onChange(event.target.value)}
/>
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 | 🟡 Minor

Add an accessible label to the search input.
Without a label, screen readers may announce this as an unlabeled field.

🐛 Suggested fix
     <input
       type="text"
+      aria-label="Search invitations"
       placeholder="Search an invitation..."
       className="w-full rounded-full border border-gray-800 bg-background py-2 pl-9 pr-3 text-white placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary/60"
       value={value}
       onChange={(event) => onChange(event.target.value)}
     />
📝 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
const SearchBar = ({ value, onChange }: { value: string; onChange: (value: string) => void }) => (
<div className="relative w-full max-w-md">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">
<Search className="h-4 w-4" />
</span>
<input
type="text"
placeholder="Search an invitation..."
className="w-full rounded-full border border-gray-800 bg-background py-2 pl-9 pr-3 text-white placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary/60"
value={value}
onChange={(event) => onChange(event.target.value)}
/>
const SearchBar = ({ value, onChange }: { value: string; onChange: (value: string) => void }) => (
<div className="relative w-full max-w-md">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">
<Search className="h-4 w-4" />
</span>
<input
type="text"
aria-label="Search invitations"
placeholder="Search an invitation..."
className="w-full rounded-full border border-gray-800 bg-background py-2 pl-9 pr-3 text-white placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary/60"
value={value}
onChange={(event) => onChange(event.target.value)}
/>
🤖 Prompt for AI Agents
In `@apps/web/src/app/invitations/page.tsx` around lines 284 - 295, The SearchBar
input is missing an accessible label; update the SearchBar component to provide
one by adding a visible or programmatic label tied to the input (e.g., add a
<label> with htmlFor and give the input a unique id, or add
aria-label/aria-labelledby) so screen readers can announce the field; ensure the
label text conveys "Search invitations" (or similar) and keep the existing
props/value/onChange handlers intact.

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.

Create RoleGuard Component Create “Guest Invitations” Placeholder Page

2 participants