</Button>
: null}
<Button type="submit" variant="primary">
- {t('button.submit')}
+ {t('button.signUp')}
</Button>
</Modal.Footer>
</Form>;
hasSGRestream,
} from '../../helpers/Episode';
import { canApplyForEpisode } from '../../helpers/permissions';
+import { useEpisodes } from '../../hooks/episodes';
import { useUser } from '../../hooks/user';
-const Crew = ({ episode, onApply }) => {
+const Crew = ({ episode }) => {
+ const { onApplyRestream } = useEpisodes();
const { t } = useTranslation();
const { user } = useUser();
{commentators.map(c =>
<CrewMember crew={c} key={c.id} />
)}
- {onApply && canApplyForEpisode(user, episode, 'commentary') ?
+ {onApplyRestream && canApplyForEpisode(user, episode, 'commentary') ?
<div className="button-bar m-2">
<Button
- onClick={() => onApply(episode, 'commentary')}
+ onClick={() => onApplyRestream(episode, 'commentary')}
variant="outline-secondary"
>
{t('button.signUp')}
{trackers.map(c =>
<CrewMember crew={c} key={c.id} />
)}
- {onApply && canApplyForEpisode(user, episode, 'tracking') ?
+ {onApplyRestream && canApplyForEpisode(user, episode, 'tracking') ?
<div className="button-bar m-2">
<Button
- onClick={() => onApply(episode, 'tracking')}
+ onClick={() => onApplyRestream(episode, 'tracking')}
variant="outline-secondary"
>
{t('button.signUp')}
crew: PropTypes.arrayOf(PropTypes.shape({
})),
}),
- onApply: PropTypes.func,
};
export default Crew;
import { hasPassed, hasSGRestream, isActive } from '../../helpers/Episode';
import { getLink } from '../../helpers/Event';
import { canApplyForEpisode, canRestreamEpisode } from '../../helpers/permissions';
+import { useEpisodes } from '../../hooks/episodes';
import { useUser } from '../../hooks/user';
-const Item = ({ episode, onAddRestream, onApply, onEditRestream }) => {
+const Item = ({ episode }) => {
+ const { onAddRestream, onEditRestream } = useEpisodes();
const { t } = useTranslation();
const { user } = useUser();
|| canApplyForEpisode(user, episode, 'tracking')
)) ?
<div className="mb-3">
- <Crew episode={episode} onApply={onApply} />
+ <Crew episode={episode} />
</div>
: null}
{episode.event ?
start: PropTypes.string,
title: PropTypes.string,
}),
- onAddRestream: PropTypes.func,
- onApply: PropTypes.func,
- onEditRestream: PropTypes.func,
};
export default Item;
const List = ({
compact = false,
episodes,
- onAddRestream,
- onApply,
- onEditRestream,
}) => {
const grouped = React.useMemo(() => episodes.reduce((groups, episode) => {
const day = moment(episode.start).format('YYYY-MM-DD');
</h2>
}
{group.map(episode =>
- <Item
- episode={episode}
- onAddRestream={onAddRestream}
- onApply={onApply}
- onEditRestream={onEditRestream}
- key={episode.id}
- />
+ <Item episode={episode} key={episode.id} />
)}
</div>)}
</div>;
episodes: PropTypes.arrayOf(PropTypes.shape({
start: PropTypes.string,
})),
- onAddRestream: PropTypes.func,
- onApply: PropTypes.func,
- onEditRestream: PropTypes.func,
};
export default List;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import toastr from 'toastr';
+
+import { useUser } from './user';
+import ApplyDialog from '../components/episodes/ApplyDialog';
+import RestreamDialog from '../components/episodes/RestreamDialog';
+
+const context = React.createContext({
+ onAddRestream: null,
+ onApplyRestream: null,
+ onEditRestream: null,
+});
+
+export const useEpisodes = () => React.useContext(context);
+
+export const EpisodesProvider = ({ children, setEpisodes }) => {
+ const [applyAs, setApplyAs] = React.useState('commentary');
+ const [restreamChannel, setRestreamChannel] = React.useState(null);
+ const [restreamEpisode, setRestreamEpisode] = React.useState(null);
+ const [showApplyDialog, setShowApplyDialog] = React.useState(false);
+ const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
+
+ const { t } = useTranslation();
+ const { user } = useUser();
+
+ const onAddRestream = React.useCallback(episode => {
+ setRestreamEpisode(episode);
+ setShowRestreamDialog(true);
+ }, []);
+
+ const onAddRestreamSubmit = React.useCallback(async values => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${values.episode_id}/add-restream`, values);
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ toastr.success(t('episodes.restreamDialog.addSuccess'));
+ } catch (e) {
+ toastr.error(t('episodes.restreamDialog.addError'));
+ throw e;
+ }
+ setRestreamEpisode(null);
+ setShowRestreamDialog(false);
+ }, []);
+
+ const onRemoveRestream = React.useCallback(async (episode, channel) => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id });
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ toastr.success(t('episodes.restreamDialog.removeSuccess'));
+ setRestreamChannel(null);
+ setRestreamEpisode(null);
+ setShowRestreamDialog(false);
+ } catch (error) {
+ toastr.error(t('episodes.restreamDialog.removeError', { error }));
+ }
+ }, []);
+
+ const onEditRestream = React.useCallback((episode, channel) => {
+ setRestreamChannel(channel);
+ setRestreamEpisode(episode);
+ setShowRestreamDialog(true);
+ }, []);
+
+ const editRestream = React.useCallback(async values => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${values.episode_id}/edit-restream`, values);
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ setRestreamEpisode(episode => ({
+ ...episode,
+ ...newEpisode,
+ }));
+ const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
+ setRestreamChannel(channel => ({
+ ...channel,
+ ...newChannel,
+ }));
+ toastr.success(t('episodes.restreamDialog.editSuccess'));
+ } catch (error) {
+ toastr.error(t('episodes.restreamDialog.editError', { error }));
+ }
+ }, []);
+
+ const manageCrew = React.useCallback(async values => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${values.episode_id}/crew-manage`, values);
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ setRestreamEpisode(episode => ({
+ ...episode,
+ ...newEpisode,
+ }));
+ const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
+ setRestreamChannel(channel => ({
+ ...channel,
+ ...newChannel,
+ }));
+ toastr.success(t('episodes.restreamDialog.crewSuccess'));
+ } catch (error) {
+ toastr.error(t('episodes.restreamDialog.crewError', { error }));
+ }
+ }, []);
+
+ const onHideRestreamDialog = React.useCallback(() => {
+ setShowRestreamDialog(false);
+ setRestreamChannel(null);
+ setRestreamEpisode(null);
+ }, []);
+
+ const onApplyRestream = React.useCallback((episode, as) => {
+ setShowApplyDialog(true);
+ setRestreamEpisode(episode);
+ setApplyAs(as);
+ }, []);
+
+ const onSubmitApplyDialog = React.useCallback(async values => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${values.episode_id}/crew-signup`, values);
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ toastr.success(t('episodes.applyDialog.applySuccess'));
+ } catch (e) {
+ toastr.error(t('episodes.applyDialog.applyError'));
+ throw e;
+ }
+ setRestreamEpisode(null);
+ setShowApplyDialog(false);
+ }, []);
+
+ const onHideApplyDialog = React.useCallback(() => {
+ setShowApplyDialog(false);
+ setRestreamEpisode(null);
+ }, []);
+
+ const value = React.useMemo(() => ({
+ onAddRestream,
+ onApplyRestream,
+ onEditRestream,
+ }), [
+ onAddRestream,
+ onApplyRestream,
+ onEditRestream,
+ ]);
+
+ return <context.Provider value={value}>
+ {children}
+ {user ? <>
+ <ApplyDialog
+ as={applyAs}
+ episode={restreamEpisode}
+ onHide={onHideApplyDialog}
+ onSubmit={onSubmitApplyDialog}
+ show={showApplyDialog}
+ />
+ <RestreamDialog
+ channel={restreamChannel}
+ editRestream={editRestream}
+ episode={restreamEpisode}
+ manageCrew={manageCrew}
+ onRemoveRestream={onRemoveRestream}
+ onHide={onHideRestreamDialog}
+ onSubmit={onAddRestreamSubmit}
+ show={showRestreamDialog}
+ />
+ </> : null}
+ </context.Provider>;
+};
+
+EpisodesProvider.propTypes = {
+ children: PropTypes.node,
+ setEpisodes: PropTypes.func,
+};
mayEditContent,
} from '../helpers/permissions';
import { getTranslation } from '../helpers/Technique';
+import { EpisodesProvider } from '../hooks/episodes';
import { useUser } from '../hooks/user';
import i18n from '../i18n';
}));
setShowContentDialog(false);
} catch (e) {
- toastr.error(t('content.saveError'));
+ toastr.error(t('content.saveError', { message: e.message }));
}
}, [event && event.description_id]);
</div>
</div>
{episodes.length ?
- <EpisodeList episodes={episodes} />
+ <EpisodesProvider setEpisodes={setEpisodes}>
+ <EpisodeList episodes={episodes} />
+ </EpisodesProvider>
:
<Alert variant="info">
{t(pastMode ? 'events.noPastEpisodes' : 'events.noUpcomingEpisodes')}
import { Alert, Button, Container } from 'react-bootstrap';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
-import toastr from 'toastr';
import CanonicalLinks from '../components/common/CanonicalLinks';
import ErrorBoundary from '../components/common/ErrorBoundary';
import Icon from '../components/common/Icon';
-import ApplyDialog from '../components/episodes/ApplyDialog';
import Filter from '../components/episodes/Filter';
import List from '../components/episodes/List';
-import RestreamDialog from '../components/episodes/RestreamDialog';
import { getFilterParams, isFilterActive } from '../helpers/Episode';
-import { useUser } from '../hooks/user';
+import { EpisodesProvider } from '../hooks/episodes';
const GAMES = [
'alttp', 'alttpr', 'sm', 'smr', 'smz3',
];
export const Component = () => {
- const [applyAs, setApplyAs] = React.useState('commentary');
const [episodes, setEpisodes] = React.useState([]);
const [events, setEvents] = React.useState([]);
const [filter, setFilter] = React.useState({});
- const [restreamChannel, setRestreamChannel] = React.useState(null);
- const [restreamEpisode, setRestreamEpisode] = React.useState(null);
- const [showApplyDialog, setShowApplyDialog] = React.useState(false);
- const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
const [showFilter, setShowFilter] = React.useState(false);
const { t } = useTranslation();
- const { user } = useUser();
React.useEffect(() => {
const savedFilter = localStorage.getItem('episodes.filter.schedule');
});
}, []);
- const onAddRestream = React.useCallback(episode => {
- setRestreamEpisode(episode);
- setShowRestreamDialog(true);
- }, []);
-
- const onAddRestreamSubmit = React.useCallback(async values => {
- try {
- const response = await axios.post(
- `/api/episodes/${values.episode_id}/add-restream`, values);
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- toastr.success(t('episodes.restreamDialog.addSuccess'));
- } catch (e) {
- toastr.error(t('episodes.restreamDialog.addError'));
- throw e;
- }
- setRestreamEpisode(null);
- setShowRestreamDialog(false);
- }, []);
-
- const onRemoveRestream = React.useCallback(async (episode, channel) => {
- try {
- const response = await axios.post(
- `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id });
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- toastr.success(t('episodes.restreamDialog.removeSuccess'));
- setRestreamChannel(null);
- setRestreamEpisode(null);
- setShowRestreamDialog(false);
- } catch (error) {
- toastr.error(t('episodes.restreamDialog.removeError', { error }));
- }
- }, []);
-
- const onEditRestream = React.useCallback((episode, channel) => {
- setRestreamChannel(channel);
- setRestreamEpisode(episode);
- setShowRestreamDialog(true);
- }, []);
-
- const editRestream = React.useCallback(async values => {
- try {
- const response = await axios.post(
- `/api/episodes/${values.episode_id}/edit-restream`, values);
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- setRestreamEpisode(episode => ({
- ...episode,
- ...newEpisode,
- }));
- const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
- setRestreamChannel(channel => ({
- ...channel,
- ...newChannel,
- }));
- toastr.success(t('episodes.restreamDialog.editSuccess'));
- } catch (error) {
- toastr.error(t('episodes.restreamDialog.editError', { error }));
- }
- }, []);
-
- const manageCrew = React.useCallback(async values => {
- try {
- const response = await axios.post(
- `/api/episodes/${values.episode_id}/crew-manage`, values);
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- setRestreamEpisode(episode => ({
- ...episode,
- ...newEpisode,
- }));
- const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
- setRestreamChannel(channel => ({
- ...channel,
- ...newChannel,
- }));
- toastr.success(t('episodes.restreamDialog.crewSuccess'));
- } catch (error) {
- toastr.error(t('episodes.restreamDialog.crewError', { error }));
- }
- }, []);
-
- const onHideRestreamDialog = React.useCallback(() => {
- setShowRestreamDialog(false);
- setRestreamChannel(null);
- setRestreamEpisode(null);
- }, []);
-
- const onApply = React.useCallback((episode, as) => {
- setShowApplyDialog(true);
- setRestreamEpisode(episode);
- setApplyAs(as);
- }, []);
-
- const onSubmitApplyDialog = React.useCallback(async values => {
- try {
- const response = await axios.post(
- `/api/episodes/${values.episode_id}/crew-signup`, values);
- const newEpisode = response.data;
- setEpisodes(episodes => episodes.map(episode =>
- episode.id === newEpisode.id ? {
- ...episode,
- ...newEpisode,
- } : episode
- ));
- toastr.success(t('episodes.applyDialog.applySuccess'));
- } catch (e) {
- toastr.error(t('episodes.applyDialog.applyError'));
- throw e;
- }
- setRestreamEpisode(null);
- setShowApplyDialog(false);
- }, []);
-
- const onHideApplyDialog = React.useCallback(() => {
- setShowApplyDialog(false);
- setRestreamEpisode(null);
- }, []);
-
React.useEffect(() => {
const controller = new AbortController();
fetchEpisodes(controller, filter);
</div>
<ErrorBoundary>
{episodes.length ?
- <List
- episodes={episodes}
- onAddRestream={onAddRestream}
- onApply={onApply}
- onEditRestream={onEditRestream}
- />
+ <EpisodesProvider setEpisodes={setEpisodes}>
+ <List episodes={episodes} />
+ </EpisodesProvider>
:
<Alert className="my-5" variant="info">
{t('episodes.empty')}
</Alert>
}
</ErrorBoundary>
- {user ? <>
- <ApplyDialog
- as={applyAs}
- episode={restreamEpisode}
- onHide={onHideApplyDialog}
- onSubmit={onSubmitApplyDialog}
- show={showApplyDialog}
- />
- <RestreamDialog
- channel={restreamChannel}
- editRestream={editRestream}
- episode={restreamEpisode}
- manageCrew={manageCrew}
- onRemoveRestream={onRemoveRestream}
- onHide={onHideRestreamDialog}
- onSubmit={onAddRestreamSubmit}
- show={showRestreamDialog}
- />
- </> : null}
</Container>;
};