feat(ux): bulk member select + Approve selected

New bulkApproveMembers server action (ADMIN_ROLES, Zod-validated ids,
one batched space-scoped update that only flips still-unverified rows,
audited). Members table + mobile cards get a selection checkbox (admin
only) with a header select-all; a sticky bar shows the count with
Approve selected / Clear. Optimistic local update + toast.
This commit is contained in:
Moheeb Zara
2026-05-17 02:00:58 -07:00
parent d3481129a8
commit 390046a96b
3 changed files with 100 additions and 5 deletions
+25
View File
@@ -17,6 +17,7 @@ import {
updateMyProfileSchema,
discloseAffiliationsSchema,
uuidSchema,
bulkMemberIdsSchema,
} from '@/lib/validations'
export async function addMember(formData: {
@@ -131,6 +132,30 @@ export async function approveMember(memberId: string) {
return { success: true as const }
}
export async function bulkApproveMembers(ids: unknown) {
const v = parseInput(bulkMemberIdsSchema, ids)
if (!v.ok) return { error: v.error }
const supabase = await createClient()
const auth = await requireMemberWithRole(supabase, ADMIN_ROLES, 'Admin access required')
if (!auth.ok) return { error: auth.error }
const { member: self } = auth
// One batched, space-scoped update; only still-unverified rows flip.
const { data, error } = await supabase
.from('space_members')
.update({ status: 'current', approved: true })
.in('id', v.data)
.eq('space_id', self.space_id)
.eq('status', 'unverified')
.select('id')
if (error) return { error: error.message }
await logActivity(supabase, self, 'approved', 'member', null, `${data?.length ?? 0} member(s) bulk-approved`)
revalidatePath('/members')
return { data: { approved: data?.length ?? 0 } }
}
/**
* Self-edit profile fields: display_name, handle, phone, skills, interests,
* willing_to. The privilege-escalation trigger (migration 015) blocks
+5
View File
@@ -1001,6 +1001,11 @@ export const checkOutSchema = z.object({
export const uuidSchema = z.string().uuid('Invalid ID format')
export const bulkMemberIdsSchema = z
.array(z.string().uuid('Invalid member ID'))
.min(1, 'Select at least one member')
.max(1000)
// Helper type exports
export type CreateTaskInput = z.infer<typeof createTaskSchema>
export type CreateProjectInput = z.infer<typeof createProjectSchema>