]> git.localhorst.tv Git - alttp.git/blob - resources/js/components/pages/Event.js
fix event canonical link
[alttp.git] / resources / js / components / pages / Event.js
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';
9
10 import NotFound from './NotFound';
11 import CanonicalLinks from '../common/CanonicalLinks';
12 import ErrorBoundary from '../common/ErrorBoundary';
13 import ErrorMessage from '../common/ErrorMessage';
14 import Loading from '../common/Loading';
15 import EpisodeList from '../episodes/List';
16 import Detail from '../events/Detail';
17 import Dialog from '../techniques/Dialog';
18 import {
19         mayEditContent,
20 } from '../../helpers/permissions';
21 import { useUser } from '../../helpers/UserContext';
22 import i18n from '../../i18n';
23
24 const Event = () => {
25         const params = useParams();
26         const { name } = params;
27         const user = useUser();
28
29         const [error, setError] = React.useState(null);
30         const [loading, setLoading] = React.useState(true);
31         const [event, setEvent] = React.useState(null);
32
33         const [editContent, setEditContent] = React.useState(null);
34         const [episodes, setEpisodes] = React.useState([]);
35         const [showContentDialog, setShowContentDialog] = React.useState(false);
36
37         const actions = React.useMemo(() => ({
38                 editContent: mayEditContent(user) ? content => {
39                         setEditContent(content);
40                         setShowContentDialog(true);
41                 } : null,
42         }), [user]);
43
44         const fetchEpisodes = React.useCallback((controller, event) => {
45                 if (!event) {
46                         setEpisodes([]);
47                         return;
48                 }
49                 axios.get(`/api/episodes`, {
50                         signal: controller.signal,
51                         params: {
52                                 after: moment().subtract(3, 'hours').toISOString(),
53                                 before: moment().add(14, 'days').toISOString(),
54                                 event: [event.id],
55                         },
56                 }).then(response => {
57                         setEpisodes(response.data || []);
58                 }).catch(e => {
59                         if (!axios.isCancel(e)) {
60                                 console.error(e);
61                         }
62                 });
63         }, []);
64
65         const saveContent = React.useCallback(async values => {
66                 try {
67                         const response = await axios.put(`/api/content/${values.id}`, {
68                                 parent_id: event.description_id,
69                                 ...values,
70                         });
71                         toastr.success(i18n.t('content.saveSuccess'));
72                         setEvent(event => ({
73                                 ...event,
74                                 description: response.data,
75                         }));
76                         setShowContentDialog(false);
77                 } catch (e) {
78                         toastr.error(i18n.t('content.saveError'));
79                 }
80         }, [event && event.description_id]);
81
82         React.useEffect(() => {
83                 const ctrl = new AbortController();
84                 setLoading(true);
85                 axios
86                         .get(`/api/events/${name}`, { signal: ctrl.signal })
87                         .then(response => {
88                                 setError(null);
89                                 setLoading(false);
90                                 setEvent(response.data);
91                         })
92                         .catch(error => {
93                                 setError(error);
94                                 setLoading(false);
95                                 setEvent(null);
96                         });
97                 return () => {
98                         ctrl.abort();
99                 };
100         }, [name]);
101
102         React.useEffect(() => {
103                 const controller = new AbortController();
104                 fetchEpisodes(controller, event);
105                 const timer = setInterval(() => {
106                         fetchEpisodes(controller, event);
107                 }, 1.5 * 60 * 1000);
108                 return () => {
109                         controller.abort();
110                         clearInterval(timer);
111                 };
112         }, [event, fetchEpisodes]);
113
114         if (loading) {
115                 return <Loading />;
116         }
117
118         if (error) {
119                 return <ErrorMessage error={error} />;
120         }
121
122         if (!event) {
123                 return <NotFound />;
124         }
125
126         return <ErrorBoundary>
127                 <Helmet>
128                         <title>{event.title}</title>
129                 </Helmet>
130                 <CanonicalLinks base={`/events/${event.name}`} />
131                 <Container>
132                         <Detail actions={actions} event={event} />
133                         {episodes.length ? <>
134                                 <h2>{i18n.t('events.upcomingEpisodes')}</h2>
135                                 <EpisodeList episodes={episodes} />
136                         </> : null}
137                 </Container>
138                 <Dialog
139                         content={editContent}
140                         language={i18n.language}
141                         onHide={() => { setShowContentDialog(false); }}
142                         onSubmit={saveContent}
143                         show={showContentDialog}
144                 />
145         </ErrorBoundary>;
146 };
147
148 export default withTranslation()(Event);