Skip to content

Removed followers tab and created followers modal#1989

Merged
ragnep merged 2 commits intomainfrom
followers-modal
Feb 26, 2026
Merged

Removed followers tab and created followers modal#1989
ragnep merged 2 commits intomainfrom
followers-modal

Conversation

@ragnep
Copy link
Copy Markdown
Contributor

@ragnep ragnep commented Feb 26, 2026

Summary by CodeRabbit

  • New Features

    • Followers can be viewed in a modal by clicking the followers count on user profiles.
  • Changes

    • Followers tab removed from the profile tabs; the separate followers page now redirects to the main profile.
    • Mobile dialogs gained improved inner scrolling and an optional visible scrollbar for a better mobile experience.

Signed-off-by: ragnep <ragneinfo@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

Replaces the dedicated followers page with a modal-based followers UI. Adds a useFollowersList hook, a UserPageFollowersModal component, and wiring in header and stats components; removes the followers tab route and makes the followers page redirect to the parent profile.

Changes

Cohort / File(s) Summary
Followers Page Redirect
app/[user]/followers/page.tsx
Removed Page/generateMetadata exports; added default async FollowersPage that resolves params and redirects to the parent user route or triggers notFound.
Followers Modal & Hook
components/user/followers/UserPageFollowersModal.tsx, hooks/useFollowersList.ts
Added modal component that uses MobileWrapperDialog and new useFollowersList hook (infinite query, page size 100) to fetch/paginate followers and expose followers/isFetching/onBottomIntersection.
Modal Trigger Integration
components/header/AppSidebarUserStats.tsx, components/user/user-page-header/stats/UserPageHeaderStats.tsx
Added local state to open/close followers modal, render UserPageFollowersModal, and pass onFollowersClick handlers into stats row. Tests updated to expect button role for followers control.
Stats Row Enhancement
components/user/utils/stats/UserStatsRow.tsx
Added optional onFollowersClick prop; conditionally render a button (invoking callback) instead of a Link to the followers page when provided.
Modal Dialog Restructuring
components/mobile-wrapper-dialog/MobileWrapperDialog.tsx
Added showScrollbar?: boolean prop; moved scrolling and padding into an inner container, applied conditional custom scrollbar class and kept height/layout helpers.
Tab & Layout Updates
components/user/layout/userTabs.config.ts, components/utils/followers/Follower.tsx
Removed the Followers tab from TAB_DEFINITIONS; adjusted rendering order of UserCICAndLevel inside follower item.
Tests
__tests__/components/header/AppSidebarUserStats.test.tsx
Mocked UserPageFollowersModal; updated assertions to expect a button for the followers control and adjusted content checks.

Sequence Diagram

sequenceDiagram
    actor User
    participant Stats as UserStatsRow
    participant Header as UserPageHeaderStats
    participant Modal as UserPageFollowersModal
    participant Hook as useFollowersList
    participant API as API Server

    User->>Stats: Click followers count
    Stats->>Header: invoke onFollowersClick()
    Header->>Header: set isFollowersModalOpen = true
    Header->>Modal: render isOpen=true
    Modal->>Hook: initialize useFollowersList(profileId, enabled=true)
    Hook->>API: GET /identity-subscriptions/incoming/IDENTITY/{profileId}
    API-->>Hook: return page 1
    Hook-->>Modal: followers, isFetching, onBottomIntersection
    Modal->>User: render followers in MobileWrapperDialog
    User->>Modal: scroll to bottom
    Modal->>Hook: onBottomIntersection(true)
    Hook->>API: GET ...?page=2
    API-->>Hook: return page 2
    Hook-->>Modal: append followers
    User->>Modal: close modal
    Modal->>Header: call onClose()
    Header->>Header: set isFollowersModalOpen = false
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Followers modal #1956: Same objective—remove followers tab/page and replace with a followers modal and related wiring.

Possibly related PRs

Suggested reviewers

  • simo6529
  • analyticsflowee

Poem

🐰 I hopped from page to modal bright,
Fetching friends in soft moonlight.
Scrolls that pull the crowd below,
Buttons that make the counts all glow—
A tiny rabbit cheers: "Let followers show!"

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: removing the followers tab from the tab definitions and introducing a new followers modal component as an alternative UI for viewing followers.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch followers-modal

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
Copy Markdown

@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 (2)
hooks/useFollowersList.ts (1)

50-56: Consider using useMemo instead of useEffect + useState for derived state.

The followers array is derived directly from data. Using useMemo is more idiomatic for computed values and avoids an extra render cycle.

♻️ Proposed refactor
-  const [followers, setFollowers] = useState<
-    ApiIdentityAndSubscriptionActions[]
-  >([]);
-  useEffect(
-    () => setFollowers(data?.pages.flatMap((page) => page.data) ?? []),
-    [data]
-  );
+  const followers = useMemo(
+    () => data?.pages.flatMap((page) => page.data) ?? [],
+    [data]
+  );

Update the import:

-import { useEffect, useState } from "react";
+import { useMemo } from "react";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useFollowersList.ts` around lines 50 - 56, Replace the derived state
logic that uses useState/setFollowers and useEffect with a memoized computed
value: remove the followers state and the useEffect that sets it from data, and
instead create followers via useMemo(() => data?.pages.flatMap(page =>
page.data) ?? [], [data]) so the array is computed idiomatically; update any
references to the followers variable to use this memo and keep the dependency on
data only.
components/mobile-wrapper-dialog/MobileWrapperDialog.tsx (1)

170-177: Title now scrolls with content — verify this is intentional.

The DialogTitle is rendered inside the scrollable container, meaning it will scroll out of view as the user scrolls down. For modals with long content (like a followers list), users may lose context of what they're viewing. Consider whether the title should remain fixed at the top of the modal.

💡 Suggestion to keep title fixed
                   <div
                     className={`tw-flex tw-scroll-py-3 tw-flex-col tw-overflow-y-auto tw-flex-1 tw-min-h-0 ${
                       noPadding ? "tw-py-0" : "tw-py-6"
                     }${
                       showScrollbar
                         ? " tw-scrollbar-thin tw-scrollbar-track-iron-800 tw-scrollbar-thumb-iron-500 desktop-hover:hover:tw-scrollbar-thumb-iron-300"
                         : ""
                     }`}
                     style={{ paddingBottom: bottomPadding }}
                   >
-                    <div className="tw-px-4 sm:tw-px-6">
-                      {title && (
-                        <DialogTitle className="tw-text-base tw-font-semibold tw-text-iron-50">
-                          {title}
-                        </DialogTitle>
-                      )}
-                    </div>
                     {children}
                   </div>

Then render the title before the scrollable container:

+                   {title && (
+                     <div className="tw-px-4 sm:tw-px-6 tw-py-4 tw-border-b tw-border-iron-800">
+                       <DialogTitle className="tw-text-base tw-font-semibold tw-text-iron-50">
+                         {title}
+                       </DialogTitle>
+                     </div>
+                   )}
                    <div
                      className={`tw-flex tw-scroll-py-3 tw-flex-col tw-overflow-y-auto ...`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/mobile-wrapper-dialog/MobileWrapperDialog.tsx` around lines 170 -
177, The DialogTitle is currently inside the scrollable area in
MobileWrapperDialog.tsx so the title scrolls away with children; move the title
out of the scrollable container so it stays fixed at the top of the modal:
render the title (check the title prop and DialogTitle usage) above the element
that wraps the scrollable children (the container that currently contains
{children}), adjust layout classes as needed so the scrollable container only
contains the list/content and not the DialogTitle, and ensure
accessibility/visual spacing is preserved after the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/`[user]/followers/page.tsx:
- Around line 8-10: The current redirect call uses resolvedParams/params to
compute user and then unconditionally calls redirect(`/${user}`), which will
send an empty user to the root; instead validate resolvedParams and the derived
user before redirecting: if resolvedParams is missing or user is falsy, call
notFound() (or otherwise handle the missing resource) and only call
redirect(`/${user}`) when user is a non-empty string; update the logic around
resolvedParams, params, user, redirect and import/usage of notFound accordingly
so empty/undefined users do not redirect to "/".

---

Nitpick comments:
In `@components/mobile-wrapper-dialog/MobileWrapperDialog.tsx`:
- Around line 170-177: The DialogTitle is currently inside the scrollable area
in MobileWrapperDialog.tsx so the title scrolls away with children; move the
title out of the scrollable container so it stays fixed at the top of the modal:
render the title (check the title prop and DialogTitle usage) above the element
that wraps the scrollable children (the container that currently contains
{children}), adjust layout classes as needed so the scrollable container only
contains the list/content and not the DialogTitle, and ensure
accessibility/visual spacing is preserved after the change.

In `@hooks/useFollowersList.ts`:
- Around line 50-56: Replace the derived state logic that uses
useState/setFollowers and useEffect with a memoized computed value: remove the
followers state and the useEffect that sets it from data, and instead create
followers via useMemo(() => data?.pages.flatMap(page => page.data) ?? [],
[data]) so the array is computed idiomatically; update any references to the
followers variable to use this memo and keep the dependency on data only.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 398fe1d and 1e5c839.

📒 Files selected for processing (10)
  • app/[user]/followers/page.tsx
  • components/header/AppSidebarUserStats.tsx
  • components/mobile-wrapper-dialog/MobileWrapperDialog.tsx
  • components/user/followers/UserPageFollowers.tsx
  • components/user/followers/UserPageFollowersModal.tsx
  • components/user/layout/userTabs.config.ts
  • components/user/user-page-header/stats/UserPageHeaderStats.tsx
  • components/user/utils/stats/UserStatsRow.tsx
  • components/utils/followers/Follower.tsx
  • hooks/useFollowersList.ts
💤 Files with no reviewable changes (1)
  • components/user/layout/userTabs.config.ts

Comment thread app/[user]/followers/page.tsx
Signed-off-by: ragnep <ragneinfo@gmail.com>
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

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

Caution

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

⚠️ Outside diff range comments (1)
__tests__/components/header/AppSidebarUserStats.test.tsx (1)

47-47: ⚠️ Potential issue | 🟡 Minor

Missing required props in test renders.

Based on AppSidebarUserStats component signature (see components/header/AppSidebarUserStats.tsx), the following required props are missing in all test renders: tdh_rate, xtdh, xtdh_rate, and cic. This will cause TypeScript errors and may result in undefined values being passed to UserStatsRow.

Proposed fix
-    render(<AppSidebarUserStats handle="alice" tdh={1500} rep={2} profileId="p1" />);
+    render(<AppSidebarUserStats handle="alice" tdh={1500} tdh_rate={0} xtdh={0} xtdh_rate={0} rep={2} cic={0} profileId="p1" />);
-    render(<AppSidebarUserStats handle="bob" tdh={10} rep={20} profileId={undefined} />);
+    render(<AppSidebarUserStats handle="bob" tdh={10} tdh_rate={0} xtdh={0} xtdh_rate={0} rep={20} cic={0} profileId={undefined} />);
-    render(<AppSidebarUserStats handle="carol" tdh={1} rep={0} profileId="pid" />);
+    render(<AppSidebarUserStats handle="carol" tdh={1} tdh_rate={0} xtdh={0} xtdh_rate={0} rep={0} cic={0} profileId="pid" />);

Also applies to: 67-67, 78-78

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/header/AppSidebarUserStats.test.tsx` at line 47, Tests
render AppSidebarUserStats without required props (tdh_rate, xtdh, xtdh_rate,
cic) causing TypeScript errors and undefined values passed to UserStatsRow;
update each render call in the test file to pass those missing props (e.g.,
provide mock numeric/string values for tdh_rate, xtdh, xtdh_rate and a value for
cic) so AppSidebarUserStats receives a complete props object; locate the render
invocations of AppSidebarUserStats in the test (the calls at or around the lines
invoking render(<AppSidebarUserStats ... />)) and add the four props with
sensible test values to each call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@__tests__/components/header/AppSidebarUserStats.test.tsx`:
- Line 47: Tests render AppSidebarUserStats without required props (tdh_rate,
xtdh, xtdh_rate, cic) causing TypeScript errors and undefined values passed to
UserStatsRow; update each render call in the test file to pass those missing
props (e.g., provide mock numeric/string values for tdh_rate, xtdh, xtdh_rate
and a value for cic) so AppSidebarUserStats receives a complete props object;
locate the render invocations of AppSidebarUserStats in the test (the calls at
or around the lines invoking render(<AppSidebarUserStats ... />)) and add the
four props with sensible test values to each call.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e5c839 and f7e5a84.

📒 Files selected for processing (3)
  • __tests__/components/header/AppSidebarUserStats.test.tsx
  • app/[user]/followers/page.tsx
  • components/user/utils/stats/UserStatsRow.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/[user]/followers/page.tsx

@ragnep ragnep merged commit fcfd457 into main Feb 26, 2026
7 checks passed
@ragnep ragnep deleted the followers-modal branch February 26, 2026 12:03
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.

3 participants