All files / src/utils eventPublication.ts

100% Statements 3/3
85.71% Branches 6/7
100% Functions 2/2
100% Lines 3/3

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37                  3x         10x                               7x            
/**
 * Published-phase gate for public event surfaces.
 *
 * The generic `GET /api/v1/events` list returns every event regardless of publication
 * state, so public surfaces must filter to events the organizer has actively published.
 * Only a `TOPIC` / `SPEAKERS` / `AGENDA` phase counts — the unpublished sentinel `NONE`
 * is a truthy string, so a bare `Boolean(phase)` check would wrongly pass it. Mirrors the
 * backend `/events/current` gate (`publishedAt != null || currentPublishedPhase != 'none'`).
 */
export const PUBLISHED_PHASES = ['TOPIC', 'SPEAKERS', 'AGENDA'] as const;
 
export type PublishedPhase = (typeof PUBLISHED_PHASES)[number];
 
export function isPublishedPhase(phase: string | null | undefined): boolean {
  return PUBLISHED_PHASES.includes((phase ?? '') as PublishedPhase);
}
 
interface SelfNominationGateInput {
  /** Expanded topic object (present once the topic is set), or a raw code/string, or null. */
  topic?: { name?: string } | string | null;
  currentPublishedPhase?: string | null;
}
 
/**
 * Story 7.2 "I Could Speak on That": speaker self-nomination is open ONLY during the TOPIC
 * publishing phase — from when the topic is published until the speakers are published. Once the
 * lineup is published (SPEAKERS/AGENDA), the program is set and no further nominations are taken.
 * Callers add their own login gate (the panel self-gates) and, where relevant, a future-event check.
 */
export function canOfferSelfNomination(event: SelfNominationGateInput): boolean {
  return (
    typeof event.topic === 'object' &&
    event.topic != null &&
    (event.currentPublishedPhase ?? '') === 'TOPIC'
  );
}