2025-04-01 21:50:20 +00:00

276 lines
6.3 KiB
JavaScript
Executable File

import React, { useState, useEffect } from "react"
import { Skeleton, Button, Popover } from "antd"
import ReactMarkdown from "react-markdown"
import remarkGfm from "remark-gfm"
import { DateTime } from "luxon"
import { Icons } from "@components/Icons"
import EventsModel from "@models/events"
import useCenteredContainer from "@hooks/useCenteredContainer"
import createGoogleCalendarEvent from "@utils/createGoogleCalendarEvent"
import ContrastYIQ from "@utils/contrastYIQ"
import ProcessString from "@utils/processString"
import "./index.less"
const LocationProcessRegexs = [
{
regex: /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi,
fn: (key, result) => (
<a
key={key}
href={result[1]}
target="_blank"
rel="noopener noreferrer"
>
{result[1]}
</a>
),
},
]
const EventCountdown = ({ date, prefix }) => {
const [label, setLabel] = useState(null)
useEffect(() => {
function updateCountdown() {
const nowDate = DateTime.local()
const fromDate = DateTime.fromISO(date)
const diff = fromDate.diff(nowDate, "minutes").values
setLabel(nowDate.plus(diff).toRelative())
}
updateCountdown()
const interval = setInterval(updateCountdown, 1000)
return () => clearInterval(interval)
}, [date])
return (
<div className="field">
<div className="field-label">
<Icons.FiClock />
<p>
{prefix} {label}
</p>
</div>
</div>
)
}
const EventStartDate = ({ startDate, started }) => (
<Popover
content={
<EventCountdown
date={startDate}
prefix={started ? "Started" : "Starts"}
/>
}
>
<div className="field">
<div className="field-label">
<Icons.FiCalendar />
{startDate.toLocaleString(DateTime.DATE_FULL)}
</div>
<div className="field-label">
<Icons.FiClock />
{startDate.toLocaleString(DateTime.TIME_SIMPLE)}
</div>
</div>
</Popover>
)
const EventHeader = ({ pageConfig, event, contrastColor }) => {
if (pageConfig.header) {
return (
<div
id="eventHeader"
className="header custom"
style={{
...(pageConfig.header.style ?? {}),
color: `var(--text-color-${contrastColor})`,
}}
>
{pageConfig.header.displayLogo && (
<div className="logo">
<img src={pageConfig.header.logoImg} alt="Event Logo" />
</div>
)}
{pageConfig.header.displayTitle && (
<div className="title">
<h1>{pageConfig.header.title}</h1>
<h2>{pageConfig.header.description}</h2>
</div>
)}
</div>
)
}
return (
<div
className="header"
style={event.announcement.backgroundStyle}
id="eventHeader"
>
{event.announcement.logoImg && (
<div className="logo">
<img src={event.announcement.logoImg} alt="Event Logo" />
</div>
)}
<div className="title">
<h1>{event.name}</h1>
<h2>{event.announcement.description}</h2>
</div>
</div>
)
}
const EventPage = (props) => {
useCenteredContainer(false)
const [L_Event, R_Event, E_Event, M_Event] = app.cores.api.useRequest(
EventsModel.data,
props.params["id"],
)
const [contrastColor, setContrastColor] = useState(null)
const [started, setStarted] = useState(false)
const [ended, setEnded] = useState(false)
useEffect(() => {
if (!R_Event) return
// Calculate event status
const now = DateTime.local()
const eventStart = DateTime.fromISO(R_Event.startDate)
const eventEnd = DateTime.fromISO(R_Event.endDate)
const startDiff = eventStart.diff(now, "minutes").values
const endDiff = eventEnd.diff(now, "minutes").values
setStarted(startDiff.minutes < 0)
setEnded(endDiff.minutes < 0)
// Calculate contrast color for header
if (R_Event.pageConfig?.header?.style?.backgroundImage) {
const url = R_Event.pageConfig.header.style.backgroundImage
.replace("url(", "")
.replace(")", "")
.replace(/['"]/gi, "")
ContrastYIQ.fromUrl(url).then(setContrastColor)
}
}, [R_Event])
const handleClickWatchLiveStream = () => {
if (!R_Event?.pageConfig?.livestreamId) return
app.location.push(`/tv/live/${R_Event.pageConfig.livestreamId}`)
}
const handleClickAddToCalendar = () => {
createGoogleCalendarEvent({
title: R_Event.name,
startDate: new Date(R_Event.startDate),
endDate: new Date(R_Event.endDate),
description: `${R_Event.shortDescription} - See details at ${location.href}`,
location: R_Event.location,
})
}
if (E_Event) {
return null
}
if (L_Event) {
return <Skeleton active />
}
if (!R_Event) {
return null
}
const eventStartedOrEnded = started || ended
const startDate = DateTime.fromISO(R_Event.startDate)
const endDate = DateTime.fromISO(R_Event.endDate)
const { pageConfig } = R_Event
return (
<div className="event">
<EventHeader
pageConfig={pageConfig}
event={R_Event}
contrastColor={contrastColor}
/>
<div className="content">
<div className="panel">
<div className="card">
{started && !ended && (
<div className="field">
<div className="field-label">
<div className="pulse_circle" />
<p>Started</p>
</div>
<div className="field-value">
<EventCountdown
date={endDate}
prefix="Ends"
/>
</div>
</div>
)}
{!started && (
<EventStartDate
startDate={startDate}
started={started}
/>
)}
<div className="field">
<div className="field-label">
<Icons.FiMapPin />
{ProcessString(LocationProcessRegexs)(
R_Event.location,
)}
</div>
</div>
</div>
{!eventStartedOrEnded && (
<div className="card">
<Button onClick={handleClickAddToCalendar}>
<Icons.FiCalendar /> Add to Calendar
</Button>
</div>
)}
{started && pageConfig.livestreamId && (
<div className="card">
<Button
type="primary"
onClick={handleClickWatchLiveStream}
>
<Icons.FiPlay /> Watch Live
</Button>
</div>
)}
</div>
<div className="panel">
<div className="card">
<div className="page-render">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
children={R_Event.page}
/>
</div>
</div>
</div>
</div>
</div>
)
}
export default EventPage