Enhance audience management features with new components and API integrations

- Added `AssignCoursesModal`, `AudienceMemberRow`, `AudienceTable`, and related components for improved audience management UI.
- Implemented audience invite actions including resend and revoke functionality.
- Updated audience API to support importing members and assigning courses.
- Refactored audience data handling to include status and invite management.
- Improved overall user experience with pagination and selection features in the audience table.
This commit is contained in:
Rotimi Best
2026-03-22 18:42:23 +01:00
parent e9c4082f89
commit 3919ff6c2f
157 changed files with 13183 additions and 872 deletions
+21 -4
View File
@@ -1,11 +1,12 @@
import { AppError, ErrorCodes } from '@api/utils/errors';
import type { TNewOrganizationPlan, TOrganization, TOrganizationPlan, TPlan } from '@db/types';
import type { TNewOrganizationPlan, TOrganization, TOrganizationPlan } from '@db/types';
import {
cancelOrganizationPlan,
createOrganizationPlan,
deleteOrganizationMember,
getOrgIdBySiteName,
getFirstOrganizationWithPlans,
getLatestOrgInvitesByEmails,
getOrganizationAudience,
getOrganizationBySiteName,
getOrganizationTeam,
@@ -32,6 +33,8 @@ import { ROLE } from '@cio/utils/constants';
import { createOrganizationWithOwner } from '@api/services/onboarding';
import { getProfileById } from '@cio/db/queries/auth';
import { inviteTeamMembers as inviteTeamMembersSecure } from './organization/invite';
import { deriveAudienceMemberStatus } from '@api/utils/audience-member-status';
import type { OrgAudienceMember } from '@api/types/org';
/**
* Creates a new organization with the current user as owner
@@ -116,12 +119,26 @@ export async function getOrgTeam(orgId: string) {
/**
* Gets organization audience (students)
* @param orgId - The organization ID
* @returns Array of student profiles
* @returns Audience members with invite status for profile-less rows
*/
export async function getOrgAudience(orgId: string) {
export async function getOrgAudience(orgId: string): Promise<OrgAudienceMember[]> {
try {
const audience = await getOrganizationAudience(orgId);
return audience;
const emailsWithoutProfile = audience.filter((m) => !m.profileId && m.email).map((m) => m.email.toLowerCase());
const invites = await getLatestOrgInvitesByEmails(orgId, emailsWithoutProfile);
const inviteByEmail = new Map(invites.map((i) => [i.email.toLowerCase(), i]));
return audience.map(
(member): OrgAudienceMember => ({
...member,
status: deriveAudienceMemberStatus(
member.profileId,
member.email ? inviteByEmail.get(member.email.toLowerCase()) : undefined
)
})
);
} catch (error) {
throw new AppError(
error instanceof Error ? error.message : 'Failed to fetch organization audience',