feat(scheduling-webui): model invitation actions

This commit is contained in:
2026-07-22 04:24:01 +02:00
parent f512784dd3
commit fc356e22c6
3 changed files with 215 additions and 0 deletions
@@ -42,6 +42,20 @@ export type SchedulingSortPhase =
| "determined"
| "past";
export type SchedulingInvitationActionBlock =
| "participation_policy_unavailable"
| "cancellation_notice_expired"
| "delivery_unavailable"
| "no_delivery_target"
| "no_active_invitation"
| "participant_revision_unavailable";
export type SchedulingInvitationActionBlocks = {
copy: SchedulingInvitationActionBlock | null;
send: SchedulingInvitationActionBlock | null;
revoke: SchedulingInvitationActionBlock | null;
};
type DirectorySelection = {
selection_key?: string;
kind?: PeoplePickerItem["kind"];
@@ -268,6 +282,65 @@ export function schedulingRequestIsPast(
return slotEnds.every((value) => Number.isFinite(value) && value < now.getTime());
}
export function schedulingInvitationActionBlocks(
request: SchedulingRequest,
participant: SchedulingParticipant,
now = new Date()
): SchedulingInvitationActionBlocks {
if (!participant.revision) {
return {
copy: "participant_revision_unavailable",
send: "participant_revision_unavailable",
revoke: "participant_revision_unavailable"
};
}
const policyAvailable = Boolean(
request.poll_id &&
request.public_participation_policy_enforcement_available === true
);
let issueBlock: SchedulingInvitationActionBlock | null = policyAvailable
? null
: "participation_policy_unavailable";
if (!issueBlock && request.status === "cancelled") {
const noticeUntil = request.cancellation_notice_until
? Date.parse(request.cancellation_notice_until)
: Number.NaN;
if (!Number.isFinite(noticeUntil) || noticeUntil <= now.getTime()) {
issueBlock = "cancellation_notice_expired";
}
}
const respondentId = participant.respondent_id?.trim() ?? "";
const hasDeliveryTarget = Boolean(
participant.email?.trim() ||
(respondentId && !respondentId.startsWith("scheduling-participant:"))
);
const sendBlock = issueBlock
?? (request.participant_invitation_delivery_available === true
? null
: "delivery_unavailable")
?? (hasDeliveryTarget ? null : "no_delivery_target");
const revokeBlock = participant.poll_invitation_id
? (policyAvailable ? null : "participation_policy_unavailable")
: "no_active_invitation";
return { copy: issueBlock, send: sendBlock, revoke: revokeBlock };
}
export function schedulingPublicInvitationUrl(
actionUrl: string,
applicationOrigin: string
): string | null {
try {
const origin = new URL(applicationOrigin);
const url = new URL(actionUrl, origin);
if (url.origin !== origin.origin || !url.pathname.startsWith("/scheduling/public/")) return null;
return url.toString();
} catch {
return null;
}
}
export function applySchedulingAvailabilityChoice(
slotIds: string[],
current: Record<string, SchedulingAvailabilityValue | "">,