1 import axios from 'axios';
2 import moment from 'moment';
3 import React from 'react';
4 import { Container } from 'react-bootstrap';
5 import { Helmet } from 'react-helmet';
6 import { useTranslation } from 'react-i18next';
7 import { useParams } from 'react-router-dom';
8 import toastr from 'toastr';
10 import NotFound from './NotFound';
11 import CanonicalLinks from '../components/common/CanonicalLinks';
12 import ErrorBoundary from '../components/common/ErrorBoundary';
13 import ErrorMessage from '../components/common/ErrorMessage';
14 import Loading from '../components/common/Loading';
15 import EpisodeList from '../components/episodes/List';
16 import Detail from '../components/events/Detail';
17 import Dialog from '../components/techniques/Dialog';
18 import { hasConcluded } from '../helpers/Event';
21 } from '../helpers/permissions';
22 import { getTranslation } from '../helpers/Technique';
23 import { useUser } from '../hooks/user';
24 import i18n from '../i18n';
26 export const Component = () => {
27 const params = useParams();
28 const { name } = params;
29 const { user } = useUser();
30 const { t } = useTranslation();
32 const [error, setError] = React.useState(null);
33 const [loading, setLoading] = React.useState(true);
34 const [event, setEvent] = React.useState(null);
36 const [editContent, setEditContent] = React.useState(null);
37 const [episodes, setEpisodes] = React.useState([]);
38 const [showContentDialog, setShowContentDialog] = React.useState(false);
40 const actions = React.useMemo(() => ({
41 editContent: mayEditContent(user) ? content => {
42 setEditContent(content);
43 setShowContentDialog(true);
47 const fetchEpisodes = React.useCallback((controller, event) => {
55 if (hasConcluded(event)) {
59 params.after = moment().subtract(3, 'hours').toISOString();
60 params.before = moment().add(14, 'days').toISOString();
62 axios.get(`/api/episodes`, {
63 signal: controller.signal,
66 setEpisodes(response.data || []);
68 if (!axios.isCancel(e)) {
74 const saveContent = React.useCallback(async values => {
76 const response = await axios.put(`/api/content/${values.id}`, {
77 parent_id: event.description_id,
80 toastr.success(t('content.saveSuccess'));
83 description: response.data,
85 setShowContentDialog(false);
87 toastr.error(t('content.saveError'));
89 }, [event && event.description_id]);
91 React.useEffect(() => {
92 const ctrl = new AbortController();
95 .get(`/api/events/${name}`, { signal: ctrl.signal })
99 setEvent(response.data);
111 React.useEffect(() => {
112 const controller = new AbortController();
113 fetchEpisodes(controller, event);
114 const timer = setInterval(() => {
115 fetchEpisodes(controller, event);
119 clearInterval(timer);
121 }, [event, fetchEpisodes]);
128 return <ErrorMessage error={error} />;
135 return <ErrorBoundary>
138 {(event.description && getTranslation(event.description, 'title', i18n.language))
142 {event.description ? <Helmet>
145 content={getTranslation(event.description, 'short', i18n.language)}
148 <CanonicalLinks base={`/events/${event.name}`} />
150 <Detail actions={actions} event={event} />
151 {episodes.length ? <>
152 <h2 className="mt-4">
153 {t(hasConcluded(event)
154 ? 'events.pastEpisodes'
155 : 'events.upcomingEpisodes'
158 <EpisodeList episodes={episodes} />
162 content={editContent}
163 language={i18n.language}
164 onHide={() => { setShowContentDialog(false); }}
165 onSubmit={saveContent}
166 show={showContentDialog}