teqo-backup/src/lib/shareLinkHomeSection.ts
Francisco Solla 0a8b0774d3
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
feat(S44): home divulga a Plenária da Vitória (agenda pré-live, embed ao vivo e kill switch)
2026-10-01 17:47:30 -03:00

82 lines
2.9 KiB
TypeScript

import { resolveCalendarEventWindow } from '@/lib/calendarEvent'
import {
normalizeAbsoluteHttpUrl,
resolveShareLinkYoutubeVideoId,
YOUTUBE_HOSTS,
type ShareLinkDestinationLike,
type ShareLinkLiveTarget,
} from '@/lib/shareLink'
import {
buildShareLinkAnnouncementView,
type ShareLinkAnnouncementSource,
type ShareLinkAnnouncementView,
} from '@/lib/shareLinkAnnouncement'
/**
* S44 — the pure contract of the home section that announces the Plenária da
* Vitória. The section reflects the state of one fixed share link: while the
* event window is open it renders the announcement view (image + agenda before
* the broadcast; YouTube embed + entry CTA while on air), and it disappears
* (fail-closed) once the link is unpublished or the window closes. No Payload
* import here — the server read orchestrates and hands over a serializable view.
*/
export const SHARE_LINK_HOME_SECTION_SLUG = 'plenaria-vitoria'
export type ShareLinkHomeSectionSource = ShareLinkAnnouncementSource & {
destinations?: ShareLinkDestinationLike[] | null
}
export type ShareLinkHomeSectionView = ShareLinkAnnouncementView & {
/** Epoch ms when the event window closes (`endsAt`, else `startsAt` + 2h). */
expiresAt: number
/** Video id of the link's pre-registered YouTube destination, when there is one. */
youtubeVideoId: string | null
}
/**
* The visibility window is the S29 calendar window (the same 2h fallback
* duration): no valid `startsAt` means no event window, and a closed window
* hides the section entirely — never a stale promo.
*/
export const buildShareLinkHomeSectionView = ({
link,
imageUrl,
canonicalUrl,
nowMs,
}: {
link: ShareLinkHomeSectionSource
imageUrl: string | null
canonicalUrl: string | null
nowMs: number
}): ShareLinkHomeSectionView | null => {
const window = resolveCalendarEventWindow(link.startsAt, link.endsAt)
if (!window || nowMs >= window.end.getTime()) return null
return {
...buildShareLinkAnnouncementView({ link, imageUrl, canonicalUrl }),
expiresAt: window.end.getTime(),
youtubeVideoId: resolveShareLinkYoutubeVideoId(link.destinations),
}
}
const MEET_HOST = 'meet.google.com'
/**
* S44 — the entry CTA label follows the destination on air (design gate): the
* Google Meet is "Entrar na plenária", the YouTube is "Assistir no YouTube".
* The host is the source of truth (a relabeled destination never mislabels the
* button); anything else falls back to the S29 generic "Entrar".
*/
export const resolveShareLinkLiveActionLabel = (target: ShareLinkLiveTarget): string => {
const url = normalizeAbsoluteHttpUrl(target.href)
if (!url) return 'Entrar'
const host = new URL(url).hostname.replace(/^www\./, '')
if (host === MEET_HOST) return 'Entrar na plenária'
if (host === 'youtu.be' || (YOUTUBE_HOSTS as readonly string[]).includes(host)) {
return 'Assistir no YouTube'
}
return 'Entrar'
}