Skip to content

feat(web): implement invitations page with search, filters (closes #191)#209

Closed
Macnelson9 wants to merge 6 commits intoStellar-Rent:mainfrom
Macnelson9:feature/placeholder-guest-invitations-page
Closed

feat(web): implement invitations page with search, filters (closes #191)#209
Macnelson9 wants to merge 6 commits intoStellar-Rent:mainfrom
Macnelson9:feature/placeholder-guest-invitations-page

Conversation

@Macnelson9
Copy link
Contributor

@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

  • New Features

    • Redesigned Invitations page with search, multi-criteria filtering, sorting, and per-view pagination
    • Improved responsive UI: mobile drawer navigation, compact header, mobile list and desktop table views, and a desktop right sidebar
  • Bug Fixes

    • Consolidated role-based access and loading checks for more reliable authorization flow
    • Simplified loading and insufficient-access messaging during access 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

Relocates and refactors RoleGuard into a dedicated component, removes legacy role hook, and rewrites the Invitations page into a responsive, auth-aware UI with client-side search, filtering, sorting, and separate mobile/desktop pagination. Dashboard imports updated to reference the new RoleGuard.

Changes

Cohort / File(s) Change Summary
RoleGuard (new component)
apps/web/src/components/guards/RoleGuard.tsx
New named RoleGuard: unified isLoading, useMemo-based hasAccess, useEffect redirect on denial, simplified loading/insufficient-access UI, children prop type updated to ReactNode.
Removed legacy hooks
apps/web/src/hooks/auth/RoleGuard.tsx, apps/web/src/hooks/useUserRole.ts
Deleted previous client RoleGuard and the useUserRole hook (mock profile, role/hostStatus/properties state, canAccessHostDashboard logic).
Invitations page rewrite
apps/web/src/app/invitations/page.tsx
Replaces placeholder with interactive Invitations page: header, responsive layout, mobile drawer, SearchBar, FilterDropdowns, DesktopInvitationsTable, MobileInvitationsList, per-view pagination, client-side filtering/sorting and state management.
Import path updates
apps/web/src/app/dashboard/host-dashboard/page.tsx, apps/web/src/app/dashboard/tenant-dashboard/page.tsx
Update RoleGuard import to named export from @/components/guards/RoleGuard; removed unused Breadcrumb import (import cleanup).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as InvitationsUI
  participant Auth as useAuth/RoleGuard
  participant Data as ClientState (search/filter/sort/page)

  User->>UI: open /invitations
  UI->>Auth: check auth + role
  Auth-->>UI: isLoading / hasAccess
  UI->>Data: initialize search/filter/sort/pagination state
  User->>UI: type search / change filter / change page
  UI->>Data: update state (search/filter/sort/page)
  Data-->>UI: filtered & paginated list
  UI-->>User: render mobile list or desktop table
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #181: Modifies the same user-role hook area and likely overlaps with removal/replacement of useUserRole.
  • PR #189: Introduced an Invitations placeholder for the same file now rewritten into a full interactive page.
  • PR #207: Alters role-based access logic / hook return shape that intersects with the new RoleGuard and removed hooks.

Poem

🐰 I hopped through imports, guards, and drawers bright,
Moved roles to a new home and polished the light.
Invitations now dance with search, sort, and page,
Mobile and desktop — the rabbit’s onstage! 🎉

🚥 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 reflects the main changes: implementing an invitations page with search and filter functionality, and closes issue #191.
Linked Issues check ✅ Passed PR addresses both linked issues: #191 creates the invitations page at /invitations with search/filters per Figma spec; #201 creates RoleGuard component protecting routes based on user roles.
Out of Scope Changes check ✅ Passed File removals (useUserRole.ts, old RoleGuard.tsx) and refactoring RoleGuard placement are necessary in-scope changes to consolidate code as mentioned in PR objectives.

✏️ 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
Contributor 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

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.

Hey @Macnelson9, thanks for the PR! I've reviewed the code and it looks great. However, it currently has conflicts with the main branch due to some recent merges.

Could you please rebase your branch on top of the latest main and resolve those conflicts? Once the CI passes and the branch is up to date, I'll merge it immediately. Thanks

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/web/src/app/dashboard/tenant-dashboard/page.tsx (1)

321-321: Missing Breadcrumb import causes runtime error.

The Breadcrumb component is used on line 321 but was not imported. This will throw a ReferenceError: Breadcrumb is not defined when the page renders.

🐛 Proposed fix: Add the missing import

Add the Breadcrumb import at the top of the file with the other component imports:

 import BookingHistory from '@/components/dashboard/BookingHistory';
 import NotificationSystem from '@/components/dashboard/NotificationSystem';
 import ProfileManagement from '@/components/dashboard/ProfileManagement';
+import { Breadcrumb } from '@/components/ui/breadcrumb';
 import { RoleGuard } from '@/components/guards/RoleGuard';

@Macnelson9
Copy link
Contributor Author

@respp Hello, I've done just that. i've rebased and my branch is up to date with the main branch, check the latest commit. Thank you for your time.

@Macnelson9 Macnelson9 requested a review from respp January 26, 2026 22:43
@respp
Copy link
Contributor

respp commented Jan 26, 2026

@respp Hello, I've done just that. i've rebased and my branch is up to date with the main branch, check the latest commit. Thank you for your time.

but the PR is still showing merge conflicts on GitHub. It seems the rebase wasn't successful or wasn't pushed correctly, as the UI shows the branch cannot be rebased.

@Macnelson9 Macnelson9 closed this Jan 26, 2026
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