101 lines
2.6 KiB
TypeScript
101 lines
2.6 KiB
TypeScript
/**
|
|
* Mirrors backend `event-edit-policy.ts` for host/admin edit UI.
|
|
* Keep in sync when changing the lock matrix.
|
|
*/
|
|
|
|
export const EVENT_SENSITIVE_FIELDS = [
|
|
'startsAt',
|
|
'endsAt',
|
|
'provinceId',
|
|
'cityId',
|
|
'address',
|
|
'lat',
|
|
'lng',
|
|
'isFree',
|
|
'price',
|
|
'cancellationFeePercent',
|
|
'cancellationFeePercent12To24Hours',
|
|
'cancellationFeePercentMoreThan24Hours',
|
|
'genderRestriction',
|
|
'ageRestriction',
|
|
] as const
|
|
|
|
export type EventSensitiveField = (typeof EVENT_SENSITIVE_FIELDS)[number]
|
|
|
|
export interface EventEditPolicy {
|
|
canEdit: boolean
|
|
hasActiveBookings: boolean
|
|
minCapacity: number
|
|
lockedFields: ReadonlySet<EventSensitiveField>
|
|
/** When true, behavioral settings (discoverability, etc.) cannot change. */
|
|
lockBehavioralSettings: boolean
|
|
}
|
|
|
|
const TERMINAL = new Set(['cancelled', 'completed'])
|
|
const LIVE = new Set(['published', 'full'])
|
|
|
|
export function isTerminalEventStatus(status: string): boolean {
|
|
return TERMINAL.has(status)
|
|
}
|
|
|
|
export function isLiveEventStatus(status: string): boolean {
|
|
return LIVE.has(status)
|
|
}
|
|
|
|
/**
|
|
* Slug cannot change on terminal events.
|
|
* Live slug changes go through pending revisions (applied on admin approve).
|
|
*/
|
|
export function isEventSlugLocked(status: string): boolean {
|
|
return TERMINAL.has(status)
|
|
}
|
|
|
|
/**
|
|
* Event is locked for mutations when it reached a terminal status OR its
|
|
* scheduled end time has passed (covers early complete vs clock end).
|
|
*/
|
|
export function isEventEnded(status: string, endsAt: string | Date, now: Date = new Date()): boolean {
|
|
if (isTerminalEventStatus(status)) return true
|
|
|
|
return new Date(endsAt).getTime() <= now.getTime()
|
|
}
|
|
|
|
export function resolveEventEditPolicy(status: string, bookedCount: number, reservedCapacity = 0): EventEditPolicy {
|
|
const occupied = bookedCount + reservedCapacity
|
|
const minCapacity = Math.max(1, occupied)
|
|
|
|
if (isTerminalEventStatus(status)) {
|
|
return {
|
|
canEdit: false,
|
|
hasActiveBookings: bookedCount > 0,
|
|
minCapacity,
|
|
lockedFields: new Set(EVENT_SENSITIVE_FIELDS),
|
|
lockBehavioralSettings: true,
|
|
}
|
|
}
|
|
|
|
if (status === 'draft' || !LIVE.has(status)) {
|
|
return {
|
|
canEdit: true,
|
|
hasActiveBookings: false,
|
|
minCapacity,
|
|
lockedFields: new Set(),
|
|
lockBehavioralSettings: false,
|
|
}
|
|
}
|
|
|
|
const hasActiveBookings = bookedCount > 0
|
|
|
|
return {
|
|
canEdit: true,
|
|
hasActiveBookings,
|
|
minCapacity,
|
|
lockedFields: hasActiveBookings ? new Set(EVENT_SENSITIVE_FIELDS) : new Set(),
|
|
lockBehavioralSettings: hasActiveBookings,
|
|
}
|
|
}
|
|
|
|
export function isFieldLocked(policy: EventEditPolicy, field: EventSensitiveField): boolean {
|
|
return policy.lockedFields.has(field)
|
|
}
|