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
@@ -1,15 +1,26 @@
import { BaseApiWithErrors, classroomio } from '$lib/utils/services/api';
import type {
AssignAudienceCoursesRequest,
DeleteTeamRequest,
DomainRequestRequest,
GetAudienceRequest,
GetOrgPublicCoursesRequest,
ImportAudienceRequest,
InviteTeamRequest,
OrgPublicCourses,
OrganizationAudience,
OrganizationTeamMembers
OrganizationTeamMembers,
ResendAudienceInviteRequest,
RevokeAudienceInviteRequest
} from '../utils/types';
import type { TCreateOrganization, TGetOrganizations, TUpdateOrganization } from '@cio/utils/validation/organization';
import { BaseApiWithErrors, classroomio } from '$lib/utils/services/api';
import type {
TAssignAudienceCourses,
TAudienceInviteByEmail,
TCreateOrganization,
TGetOrganizations,
TImportAudienceMembers,
TUpdateOrganization
} from '@cio/utils/validation/organization';
import { ZCreateOrganization, ZUpdateOrganization } from '@cio/utils/validation/organization';
import { currentOrg, orgs } from '$lib/utils/store/org';
@@ -370,6 +381,117 @@ class OrgApi extends BaseApiWithErrors {
}
});
}
/**
* Imports users into the organization as students
*/
async importAudienceMembers(data: TImportAudienceMembers) {
console.log('importAudienceMembers', data);
return this.execute<ImportAudienceRequest>({
requestFn: () =>
classroomio.organization.audience.import.$post({
json: data
}),
logContext: 'importing audience members',
onSuccess: (response) => {
const d = response.data;
snackbar.success(
t.get('audience.import.snackbar_success', {
imported: d.imported,
skipped: d.skipped,
emailsSent: d.emailsSent
})
);
this.success = true;
},
onError: (result) => {
if (typeof result === 'string') {
snackbar.error(result);
return;
}
if ('error' in result && 'field' in result) {
this.errors[result.field as string] = result.error;
}
}
});
}
/**
* Assigns existing audience members to courses
*/
async assignAudienceToCourses(data: TAssignAudienceCourses) {
return this.execute<AssignAudienceCoursesRequest>({
requestFn: () =>
classroomio.organization.audience['assign-courses'].$post({
json: data
}),
logContext: 'assigning audience to courses',
onSuccess: (response) => {
const d = response.data;
snackbar.success(
t.get('audience.assign.snackbar_success', {
assigned: d.assigned,
alreadyEnrolled: d.alreadyEnrolled,
emailsSent: d.emailsSent
})
);
this.success = true;
},
onError: (result) => {
if (typeof result === 'string') {
snackbar.error(result);
return;
}
if ('error' in result && 'field' in result) {
this.errors[result.field as string] = result.error;
}
}
});
}
async resendAudienceInvite(data: TAudienceInviteByEmail) {
return this.execute<ResendAudienceInviteRequest>({
requestFn: () =>
classroomio.organization.audience['resend-invite'].$post({
json: data
}),
logContext: 'resending audience invite',
onSuccess: (response) => {
if (response.data.emailSent) {
snackbar.success('audience.invite.resend_snackbar_success');
} else {
snackbar.error('audience.invite.resend_snackbar_email_failed');
}
const orgId = get(currentOrg).id;
if (orgId) this.getOrgAudience(orgId);
},
onError: (result) => {
if (typeof result === 'string') {
snackbar.error(result);
}
}
});
}
async revokeAudienceInvite(data: TAudienceInviteByEmail) {
return this.execute<RevokeAudienceInviteRequest>({
requestFn: () =>
classroomio.organization.audience['revoke-invite'].$post({
json: data
}),
logContext: 'revoking audience invite',
onSuccess: () => {
snackbar.success('audience.invite.revoke_snackbar_success');
const orgId = get(currentOrg).id;
if (orgId) this.getOrgAudience(orgId);
},
onError: (result) => {
if (typeof result === 'string') {
snackbar.error(result);
}
}
});
}
}
export const orgApi = new OrgApi();
@@ -23,6 +23,8 @@ export type OrganizationAudienceSuccess = Extract<InferResponseType<GetAudienceR
export type OrganizationAudience = OrganizationAudienceSuccess['data'];
export type OrganizationAudienceMember = OrganizationAudience[number];
// Org Public Courses types
export type GetOrgPublicCoursesRequest = typeof classroomio.organization.courses.public.$get;
export type OrgPublicCoursesResponse = InferResponseType<GetOrgPublicCoursesRequest> | null;
@@ -83,6 +85,20 @@ export type SsoDiscoveryResult = GetSsoDiscoverySuccess['data'];
export type GetOrgSsoInfoSuccess = Extract<InferResponseType<GetOrgSsoInfoRequest>, { success: true }>;
export type OrgSsoInfo = GetOrgSsoInfoSuccess['data'];
// Audience import types
export type ImportAudienceRequest = (typeof classroomio.organization)['audience']['import']['$post'];
export type ImportAudienceSuccess = Extract<InferResponseType<ImportAudienceRequest>, { success: true }>;
export type ImportAudienceData = ImportAudienceSuccess['data'];
// Audience assign courses types
export type AssignAudienceCoursesRequest = (typeof classroomio.organization)['audience']['assign-courses']['$post'];
export type AssignAudienceCoursesSuccess = Extract<InferResponseType<AssignAudienceCoursesRequest>, { success: true }>;
export type AssignAudienceCoursesData = AssignAudienceCoursesSuccess['data'];
// Audience invite actions
export type ResendAudienceInviteRequest = (typeof classroomio.organization)['audience']['resend-invite']['$post'];
export type RevokeAudienceInviteRequest = (typeof classroomio.organization)['audience']['revoke-invite']['$post'];
// Token auth types
export type CreateTokenAuthRequest = (typeof classroomio.organization)['token-auth']['$post'];
export type GetTokenAuthRequest = (typeof classroomio.organization)['token-auth']['$get'];