Skip to content

Member Directory#83

Open
ayan-goel wants to merge 10 commits intomainfrom
member-directory
Open

Member Directory#83
ayan-goel wants to merge 10 commits intomainfrom
member-directory

Conversation

@ayan-goel
Copy link
Collaborator

Closes #52

Summary

Implements the /members admin page for organization member management, including a paginated member directory, activity data API, member removal with a cleanup hook, and full test coverage.


Changes

New page: app/members/page.tsx

Server component at /members. Enforces:

  • Redirect to /login if unauthenticated
  • Redirect to / if user is not an admin/owner of the active organization
  • Redirect to / if the members_page_enabled org config is set to "false"

Fetches paginated member rows server-side and passes them to MembersTable.


New component: components/MembersTable.tsx

Client component rendering the member directory table with columns:

  • Member — display name
  • Role"Admin" (for admin/owner) or "Member"
  • Contact — email + phone number if available
  • Hours — total hours from past event/shift RSVPs (fetched client-side from /api/members/activity)
  • Last Active — date of most recent past RSVP
  • Remove — trash icon button (22×24px per Figma spec) that calls authClient.organization.removeMember; disabled for the currently logged-in user

Includes Previous/Next pagination controls driven by ?page= query params.


New API routes: api/members.ts

GET /api/members — requires admin. Returns paginated member list { data, total, page, pageSize }.

GET /api/members/activity — requires admin. Accepts userIds as query parameters. Queries past event RSVPs and past shift RSVPs in parallel (filtered to the active org), aggregates total hours and last active date per user, and returns a map of userId → { totalHours, lastActive }.


Schema: lib/schema.ts

Added MembersPageEnabled = "members_page_enabled" to the OrganizationConfigKey enum.

Config service: lib/services/OrganizationConfigService.ts

Added getMembersPageEnabled (defaults to "true") and setMembersPageEnabled (validates "true" / "false"), registered in the existing keyMap.

Member service: lib/services/MemberService.ts

Added listMembers(organizationId, { limit, offset }) and countByOrganization(organizationId) for paginated queries joining members and users.

Auth hook: lib/auth.ts

Added beforeRemoveMember hook to the better-auth organization plugin. On member removal, deletes all future event RSVPs and shift RSVPs for that user scoped to the organization they are being removed from. Past RSVPs are preserved.

App routing: lib/app.ts + app/api/[[...route]]/route.ts

Registered the new members Hono app at /api/members.

Drizzle migration

drizzle/20260302211406_add_members_page_enabled_config — adds the members_page_enabled enum value.

CSS: styles/globals.css

Added --color-grey-icon-weak: #22070b66 to @theme for the trash icon default state, replacing a raw text-black/40 utility. Also replaced text-black/50text-grey-stroke-strong and hover:text-red-500hover:text-status-red-text in MembersTable.tsx to use design system tokens throughout.


Tests

Unit — tests/unit/api/members/route.test.ts (16 tests)

  • GET /api/members: 401, 403, paginated list, pagination params, owner access, field shapes
  • GET /api/members/activity: 401, 403, zero-hour baseline, past event hours, past shift hours, future events excluded, combined accumulation, cross-org isolation, multi-user, missing userIds → 400

Unit — tests/unit/lib/services/MemberService.test.ts (9 tests)

listMembers: empty org, field correctness, org isolation, limit, offset, multiple roles
countByOrganization: empty org, correct count, cross-org isolation

Unit — tests/unit/lib/services/OrganizationConfigService.test.ts (7 tests)

Default value "true", set/get "true" and "false", upsert on second write, invalid value throws, per-org isolation

Unit — tests/unit/lib/auth.test.ts (5 tests)

beforeRemoveMember hook: deletes future event RSVPs, deletes future shift RSVPs, preserves past event RSVPs, preserves past shift RSVPs, only deletes RSVPs from the removed org (not others)

E2E — tests/e2e/members.spec.ts (9 tests)

Redirect when unauthenticated, redirect when not admin, members_page_enabled = false redirect, heading visible, all 5 column headers present, admin's own name/email in table, Admin role label, remove button present for others and disabled/absent for self, pagination controls render with Previous disabled on page 1

New createTestAdminAndSignIn helper added to tests/e2e/testUtils.ts.


Test results

Checklist:

  • I have mentioned a ticket above
  • My changes meet the acceptance criteria of said ticket
  • I have self-reviewed my changes
  • I have requested a review from the appropriate team member(s)
  • I have fixed all merge conflicts
  • I have ensured CI checks pass

@netlify
Copy link

netlify bot commented Mar 2, 2026

Deploy Preview for servicestart ready!

Name Link
🔨 Latest commit b975b79
🔍 Latest deploy log https://app.netlify.com/projects/servicestart/deploys/69a7c043a4a1c9000847bc7f
😎 Deploy Preview https://deploy-preview-83--servicestart.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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

@ayan-goel ayan-goel changed the title initial commit Member Directory Mar 2, 2026
Copy link
Collaborator

@renatodellosso renatodellosso left a comment

Choose a reason for hiding this comment

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

There are significant parts of the Figma design missing (beyond those I said to ignore).

The header icon doesn't match and there is no search bar. See Figma:
Image

Figma has a way to select which columns are shown:
Image

In Figma, the "remove user" button only appears when a row is hovered and replaces the "last active" column.

Figma also has functionality for selecting rows and performing bulk actions.

Figma has a confirmation modal when you try to remove a member.

Users' avatars appear in the table in Figma. The font sizes and colors also do not match Figma. The contact column's formatting is not the same as in Figma.

Be sure you match Figma's styling! If you're unsure if you should include a feature, DM me.

api/members.ts Outdated
const now = new Date();

const [eventActivity, shiftActivity] = await Promise.all([
db
Copy link
Collaborator

Choose a reason for hiding this comment

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

Put this in a service.

Copy link
Collaborator

@renatodellosso renatodellosso left a comment

Choose a reason for hiding this comment

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

Good progress! Still needs some changes though.

There's still no confirmation modal for removing users. Buttons also do not have the pointer mouse icon and the icons don't match Figma (see my question in Slack as to which icons Figma uses).

The settings button doesn't do anything.

type SortField = "role" | "hours" | "lastActive" | null;
type SortDir = "asc" | "desc";

const FONT_STACK = '"Open Sans Regular", ui-sans-serif, system-ui, sans-serif';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Fonts should be set globals.css.


const FONT_STACK = '"Open Sans Regular", ui-sans-serif, system-ui, sans-serif';

function formatDate(iso: string): string {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Put this function in /lib/clientUtils.ts

return `${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")}/${d.getFullYear()}`;
}

function CustomCheckbox({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use the Checkbox from the BoG design system.


{/* Table */}
<div className="w-full overflow-hidden">
<table
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use the Table component from the BoG design system.

}}
>
<span
style={{
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use Tailwind instead of CSS.

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