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