+import axios from 'axios';
+import moment from 'moment';
+import React from 'react';
+import { Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
+import { withTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
+import toastr from 'toastr';
+
+import NotFound from './NotFound';
+import CanonicalLinks from '../common/CanonicalLinks';
+import ErrorBoundary from '../common/ErrorBoundary';
+import ErrorMessage from '../common/ErrorMessage';
+import Loading from '../common/Loading';
+import EpisodeList from '../episodes/List';
+import Detail from '../events/Detail';
+import Dialog from '../techniques/Dialog';
+import {
+ mayEditContent,
+} from '../../helpers/permissions';
+import { useUser } from '../../helpers/UserContext';
+import i18n from '../../i18n';
+
+const Event = () => {
+ const params = useParams();
+ const { name } = params;
+ const user = useUser();
+
+ const [error, setError] = React.useState(null);
+ const [loading, setLoading] = React.useState(true);
+ const [event, setEvent] = React.useState(null);
+
+ const [editContent, setEditContent] = React.useState(null);
+ const [episodes, setEpisodes] = React.useState([]);
+ const [showContentDialog, setShowContentDialog] = React.useState(false);
+
+ const actions = React.useMemo(() => ({
+ editContent: mayEditContent(user) ? content => {
+ setEditContent(content);
+ setShowContentDialog(true);
+ } : null,
+ }), [user]);
+
+ const fetchEpisodes = React.useCallback((controller, event) => {
+ if (!event) {
+ setEpisodes([]);
+ return;
+ }
+ axios.get(`/api/episodes`, {
+ signal: controller.signal,
+ params: {
+ after: moment().subtract(3, 'hours').toISOString(),
+ before: moment().add(14, 'days').toISOString(),
+ event: [event.id],
+ },
+ }).then(response => {
+ setEpisodes(response.data || []);
+ }).catch(e => {
+ if (!axios.isCancel(e)) {
+ console.error(e);
+ }
+ });
+ }, []);
+
+ const saveContent = React.useCallback(async values => {
+ try {
+ const response = await axios.put(`/api/content/${values.id}`, {
+ parent_id: event.description_id,
+ ...values,
+ });
+ toastr.success(i18n.t('content.saveSuccess'));
+ setEvent(event => ({
+ ...event,
+ description: response.data,
+ }));
+ setShowContentDialog(false);
+ } catch (e) {
+ toastr.error(i18n.t('content.saveError'));
+ }
+ }, [event && event.description_id]);
+
+ React.useEffect(() => {
+ const ctrl = new AbortController();
+ setLoading(true);
+ axios
+ .get(`/api/events/${name}`, { signal: ctrl.signal })
+ .then(response => {
+ setError(null);
+ setLoading(false);
+ setEvent(response.data);
+ })
+ .catch(error => {
+ setError(error);
+ setLoading(false);
+ setEvent(null);
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [name]);
+
+ React.useEffect(() => {
+ const controller = new AbortController();
+ fetchEpisodes(controller, event);
+ const timer = setInterval(() => {
+ fetchEpisodes(controller, event);
+ }, 1.5 * 60 * 1000);
+ return () => {
+ controller.abort();
+ clearInterval(timer);
+ };
+ }, [event, fetchEpisodes]);
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ if (error) {
+ return <ErrorMessage error={error} />;
+ }
+
+ if (!event) {
+ return <NotFound />;
+ }
+
+ return <ErrorBoundary>
+ <Helmet>
+ <title>{event.title}</title>
+ </Helmet>
+ <CanonicalLinks base={`/event/${event.name}`} />
+ <Container>
+ <Detail actions={actions} event={event} />
+ {episodes.length ? <>
+ <h2>{i18n.t('events.upcomingEpisodes')}</h2>
+ <EpisodeList episodes={episodes} />
+ </> : null}
+ </Container>
+ <Dialog
+ content={editContent}
+ language={i18n.language}
+ onHide={() => { setShowContentDialog(false); }}
+ onSubmit={saveContent}
+ show={showContentDialog}
+ />
+ </ErrorBoundary>;
+};
+
+export default withTranslation()(Event);