Conversation
✅ Deploy Preview for servicestart ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
renatodellosso
left a comment
There was a problem hiding this comment.
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:

Figma has a way to select which columns are shown:

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 |
There was a problem hiding this comment.
Put this in a service.
There was a problem hiding this comment.
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.
components/MembersTable.tsx
Outdated
| type SortField = "role" | "hours" | "lastActive" | null; | ||
| type SortDir = "asc" | "desc"; | ||
|
|
||
| const FONT_STACK = '"Open Sans Regular", ui-sans-serif, system-ui, sans-serif'; |
There was a problem hiding this comment.
Fonts should be set globals.css.
components/MembersTable.tsx
Outdated
|
|
||
| const FONT_STACK = '"Open Sans Regular", ui-sans-serif, system-ui, sans-serif'; | ||
|
|
||
| function formatDate(iso: string): string { |
There was a problem hiding this comment.
Put this function in /lib/clientUtils.ts
components/MembersTable.tsx
Outdated
| return `${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")}/${d.getFullYear()}`; | ||
| } | ||
|
|
||
| function CustomCheckbox({ |
There was a problem hiding this comment.
Use the Checkbox from the BoG design system.
components/MembersTable.tsx
Outdated
|
|
||
| {/* Table */} | ||
| <div className="w-full overflow-hidden"> | ||
| <table |
There was a problem hiding this comment.
Use the Table component from the BoG design system.
components/MembersTable.tsx
Outdated
| }} | ||
| > | ||
| <span | ||
| style={{ |
There was a problem hiding this comment.
Use Tailwind instead of CSS.
Closes #52
Summary
Implements the
/membersadmin 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.tsxServer component at
/members. Enforces:/loginif unauthenticated/if user is not an admin/owner of the active organization/if themembers_page_enabledorg config is set to"false"Fetches paginated member rows server-side and passes them to
MembersTable.New component:
components/MembersTable.tsxClient component rendering the member directory table with columns:
"Admin"(foradmin/owner) or"Member"/api/members/activity)authClient.organization.removeMember; disabled for the currently logged-in userIncludes Previous/Next pagination controls driven by
?page=query params.New API routes:
api/members.tsGET /api/members— requires admin. Returns paginated member list{ data, total, page, pageSize }.GET /api/members/activity— requires admin. AcceptsuserIdsas 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 ofuserId → { totalHours, lastActive }.Schema:
lib/schema.tsAdded
MembersPageEnabled = "members_page_enabled"to theOrganizationConfigKeyenum.Config service:
lib/services/OrganizationConfigService.tsAdded
getMembersPageEnabled(defaults to"true") andsetMembersPageEnabled(validates"true"/"false"), registered in the existingkeyMap.Member service:
lib/services/MemberService.tsAdded
listMembers(organizationId, { limit, offset })andcountByOrganization(organizationId)for paginated queries joiningmembersandusers.Auth hook:
lib/auth.tsAdded
beforeRemoveMemberhook to the better-authorganizationplugin. 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.tsRegistered the new
membersHono app at/api/members.Drizzle migration
drizzle/20260302211406_add_members_page_enabled_config— adds themembers_page_enabledenum value.CSS:
styles/globals.cssAdded
--color-grey-icon-weak: #22070b66to@themefor the trash icon default state, replacing a rawtext-black/40utility. Also replacedtext-black/50→text-grey-stroke-strongandhover:text-red-500→hover:text-status-red-textinMembersTable.tsxto 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 shapesGET /api/members/activity: 401, 403, zero-hour baseline, past event hours, past shift hours, future events excluded, combined accumulation, cross-org isolation, multi-user, missinguserIds→ 400Unit —
tests/unit/lib/services/MemberService.test.ts(9 tests)listMembers: empty org, field correctness, org isolation, limit, offset, multiple rolescountByOrganization: empty org, correct count, cross-org isolationUnit —
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 isolationUnit —
tests/unit/lib/auth.test.ts(5 tests)beforeRemoveMemberhook: 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 = falseredirect, 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 1New
createTestAdminAndSignInhelper added totests/e2e/testUtils.ts.Test results
Checklist: