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 { withTranslation } 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';
27 const params = useParams();
28 const { name } = params;
29 const { user } = useUser();
31 const [error, setError] = React.useState(null);
32 const [loading, setLoading] = React.useState(true);
33 const [event, setEvent] = React.useState(null);
35 const [editContent, setEditContent] = React.useState(null);
36 const [episodes, setEpisodes] = React.useState([]);
37 const [showContentDialog, setShowContentDialog] = React.useState(false);
39 const actions = React.useMemo(() => ({
40 editContent: mayEditContent(user) ? content => {
41 setEditContent(content);
42 setShowContentDialog(true);
46 const fetchEpisodes = React.useCallback((controller, event) => {
54 if (hasConcluded(event)) {
58 params.after = moment().subtract(3, 'hours').toISOString();
59 params.before = moment().add(14, 'days').toISOString();
61 axios.get(`/api/episodes`, {
62 signal: controller.signal,
65 setEpisodes(response.data || []);
67 if (!axios.isCancel(e)) {
73 const saveContent = React.useCallback(async values => {
75 const response = await axios.put(`/api/content/${values.id}`, {
76 parent_id: event.description_id,
79 toastr.success(i18n.t('content.saveSuccess'));
82 description: response.data,
84 setShowContentDialog(false);
86 toastr.error(i18n.t('content.saveError'));
88 }, [event && event.description_id]);
90 React.useEffect(() => {
91 const ctrl = new AbortController();
94 .get(`/api/events/${name}`, { signal: ctrl.signal })
98 setEvent(response.data);
110 React.useEffect(() => {
111 const controller = new AbortController();
112 fetchEpisodes(controller, event);
113 const timer = setInterval(() => {
114 fetchEpisodes(controller, event);
118 clearInterval(timer);
120 }, [event, fetchEpisodes]);
127 return <ErrorMessage error={error} />;
134 return <ErrorBoundary>
137 {(event.description && getTranslation(event.description, 'title', i18n.language))
141 {event.description ? <Helmet>
144 content={getTranslation(event.description, 'short', i18n.language)}
147 <CanonicalLinks base={`/events/${event.name}`} />
149 <Detail actions={actions} event={event} />
150 {episodes.length ? <>
151 <h2 className="mt-4">
152 {i18n.t(hasConcluded(event)
153 ? 'events.pastEpisodes'
154 : 'events.upcomingEpisodes'
157 <EpisodeList episodes={episodes} />
161 content={editContent}
162 language={i18n.language}
163 onHide={() => { setShowContentDialog(false); }}
164 onSubmit={saveContent}
165 show={showContentDialog}
170 export default withTranslation()(Event);