Manage event
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
fe7333ddb0
commit
04b4a57e06
|
@ -6,8 +6,13 @@ import resizeImage from 'soapbox/utils/resize_image';
|
|||
|
||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||
import { fetchMedia, uploadMedia } from './media';
|
||||
import { closeModal } from './modals';
|
||||
import { closeModal, openModal } from './modals';
|
||||
import snackbar from './snackbar';
|
||||
import {
|
||||
STATUS_FETCH_SOURCE_FAIL,
|
||||
STATUS_FETCH_SOURCE_REQUEST,
|
||||
STATUS_FETCH_SOURCE_SUCCESS,
|
||||
} from './statuses';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
@ -17,13 +22,13 @@ const LOCATION_SEARCH_REQUEST = 'LOCATION_SEARCH_REQUEST';
|
|||
const LOCATION_SEARCH_SUCCESS = 'LOCATION_SEARCH_SUCCESS';
|
||||
const LOCATION_SEARCH_FAIL = 'LOCATION_SEARCH_FAIL';
|
||||
|
||||
const CREATE_EVENT_NAME_CHANGE = 'CREATE_EVENT_NAME_CHANGE';
|
||||
const CREATE_EVENT_DESCRIPTION_CHANGE = 'CREATE_EVENT_DESCRIPTION_CHANGE';
|
||||
const CREATE_EVENT_START_TIME_CHANGE = 'CREATE_EVENT_START_TIME_CHANGE';
|
||||
const CREATE_EVENT_HAS_END_TIME_CHANGE = 'CREATE_EVENT_HAS_END_TIME_CHANGE';
|
||||
const CREATE_EVENT_END_TIME_CHANGE = 'CREATE_EVENT_END_TIME_CHANGE';
|
||||
const CREATE_EVENT_APPROVAL_REQUIRED_CHANGE = 'CREATE_EVENT_APPROVAL_REQUIRED_CHANGE';
|
||||
const CREATE_EVENT_LOCATION_CHANGE = 'CREATE_EVENT_LOCATION_CHANGE';
|
||||
const EDIT_EVENT_NAME_CHANGE = 'EDIT_EVENT_NAME_CHANGE';
|
||||
const EDIT_EVENT_DESCRIPTION_CHANGE = 'EDIT_EVENT_DESCRIPTION_CHANGE';
|
||||
const EDIT_EVENT_START_TIME_CHANGE = 'EDIT_EVENT_START_TIME_CHANGE';
|
||||
const EDIT_EVENT_HAS_END_TIME_CHANGE = 'EDIT_EVENT_HAS_END_TIME_CHANGE';
|
||||
const EDIT_EVENT_END_TIME_CHANGE = 'EDIT_EVENT_END_TIME_CHANGE';
|
||||
const EDIT_EVENT_APPROVAL_REQUIRED_CHANGE = 'EDIT_EVENT_APPROVAL_REQUIRED_CHANGE';
|
||||
const EDIT_EVENT_LOCATION_CHANGE = 'EDIT_EVENT_LOCATION_CHANGE';
|
||||
|
||||
const EVENT_BANNER_UPLOAD_REQUEST = 'EVENT_BANNER_UPLOAD_REQUEST';
|
||||
const EVENT_BANNER_UPLOAD_PROGRESS = 'EVENT_BANNER_UPLOAD_PROGRESS';
|
||||
|
@ -59,14 +64,28 @@ const EVENT_PARTICIPATION_REQUESTS_EXPAND_REQUEST = 'EVENT_PARTICIPATION_REQUEST
|
|||
const EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS = 'EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS';
|
||||
const EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL = 'EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL';
|
||||
|
||||
const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST';
|
||||
const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS';
|
||||
const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL';
|
||||
|
||||
const EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST = 'EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST';
|
||||
const EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS = 'EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS';
|
||||
const EVENT_PARTICIPATION_REQUEST_REJECT_FAIL = 'EVENT_PARTICIPATION_REQUEST_REJECT_FAIL';
|
||||
|
||||
const EVENT_COMPOSE_CANCEL = 'EVENT_COMPOSE_CANCEL';
|
||||
|
||||
const EVENT_FORM_SET = 'EVENT_FORM_SET';
|
||||
|
||||
const noOp = () => new Promise(f => f(undefined));
|
||||
|
||||
const messages = defineMessages({
|
||||
exceededImageSizeLimit: { id: 'upload_error.image_size_limit', defaultMessage: 'Image exceeds the current file size limit ({limit})' },
|
||||
success: { id: 'create_event.submit_success', defaultMessage: 'Your event was created' },
|
||||
success: { id: 'compose_event.submit_success', defaultMessage: 'Your event was created' },
|
||||
joinSuccess: { id: 'join_event.success', defaultMessage: 'Joined the event' },
|
||||
joinRequestSuccess: { id: 'join_event.request_success', defaultMessage: 'Requested to join the event' },
|
||||
view: { id: 'snackbar.view', defaultMessage: 'View' },
|
||||
authorized: { id: 'compose_event.participation_requests.authorize_success', defaultMessage: 'User accepted' },
|
||||
rejected: { id: 'compose_event.participation_requests.reject_success', defaultMessage: 'User rejected' },
|
||||
});
|
||||
|
||||
const locationSearch = (query: string, signal?: AbortSignal) =>
|
||||
|
@ -81,37 +100,37 @@ const locationSearch = (query: string, signal?: AbortSignal) =>
|
|||
});
|
||||
};
|
||||
|
||||
const changeCreateEventName = (value: string) => ({
|
||||
type: CREATE_EVENT_NAME_CHANGE,
|
||||
const changeEditEventName = (value: string) => ({
|
||||
type: EDIT_EVENT_NAME_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const changeCreateEventDescription = (value: string) => ({
|
||||
type: CREATE_EVENT_DESCRIPTION_CHANGE,
|
||||
const changeEditEventDescription = (value: string) => ({
|
||||
type: EDIT_EVENT_DESCRIPTION_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const changeCreateEventStartTime = (value: Date) => ({
|
||||
type: CREATE_EVENT_START_TIME_CHANGE,
|
||||
const changeEditEventStartTime = (value: Date) => ({
|
||||
type: EDIT_EVENT_START_TIME_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const changeCreateEventEndTime = (value: Date) => ({
|
||||
type: CREATE_EVENT_END_TIME_CHANGE,
|
||||
const changeEditEventEndTime = (value: Date) => ({
|
||||
type: EDIT_EVENT_END_TIME_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const changeCreateEventHasEndTime = (value: boolean) => ({
|
||||
type: CREATE_EVENT_HAS_END_TIME_CHANGE,
|
||||
const changeEditEventHasEndTime = (value: boolean) => ({
|
||||
type: EDIT_EVENT_HAS_END_TIME_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const changeCreateEventApprovalRequired = (value: boolean) => ({
|
||||
type: CREATE_EVENT_APPROVAL_REQUIRED_CHANGE,
|
||||
const changeEditEventApprovalRequired = (value: boolean) => ({
|
||||
type: EDIT_EVENT_APPROVAL_REQUIRED_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const changeCreateEventLocation = (value: string | null) =>
|
||||
const changeEditEventLocation = (value: string | null) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
let location = null;
|
||||
|
||||
|
@ -120,7 +139,7 @@ const changeCreateEventLocation = (value: string | null) =>
|
|||
}
|
||||
|
||||
dispatch({
|
||||
type: CREATE_EVENT_LOCATION_CHANGE,
|
||||
type: EDIT_EVENT_LOCATION_CHANGE,
|
||||
value: location,
|
||||
});
|
||||
};
|
||||
|
@ -202,15 +221,15 @@ const submitEvent = () =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
|
||||
const name = state.create_event.name;
|
||||
const status = state.create_event.status;
|
||||
const banner = state.create_event.banner;
|
||||
const startTime = state.create_event.start_time;
|
||||
const endTime = state.create_event.end_time;
|
||||
const joinMode = state.create_event.approval_required ? 'restricted' : 'free';
|
||||
const location = state.create_event.location;
|
||||
const name = state.compose_event.name;
|
||||
const status = state.compose_event.status;
|
||||
const banner = state.compose_event.banner;
|
||||
const startTime = state.compose_event.start_time;
|
||||
const endTime = state.compose_event.end_time;
|
||||
const joinMode = state.compose_event.approval_required ? 'restricted' : 'free';
|
||||
const location = state.compose_event.location;
|
||||
|
||||
if (!status || !status.length) {
|
||||
if (!name || !name.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -228,7 +247,7 @@ const submitEvent = () =>
|
|||
if (location) params.location_id = location.origin_id;
|
||||
|
||||
return api(getState).post('/api/v1/pleroma/events', params).then(({ data }) => {
|
||||
dispatch(closeModal('CREATE_EVENT'));
|
||||
dispatch(closeModal('COMPOSE_EVENT'));
|
||||
dispatch(importFetchedStatus(data));
|
||||
dispatch(submitEventSuccess(data));
|
||||
dispatch(snackbar.success(messages.success, messages.view, `/@${data.account.acct}/events/${data.id}`));
|
||||
|
@ -261,7 +280,9 @@ const joinEvent = (id: string, participationMessage?: string) =>
|
|||
|
||||
dispatch(joinEventRequest(status));
|
||||
|
||||
return api(getState).post(`/api/v1/pleroma/events/${id}/join`, { participationMessage }).then(({ data }) => {
|
||||
return api(getState).post(`/api/v1/pleroma/events/${id}/join`, {
|
||||
participation_message: participationMessage,
|
||||
}).then(({ data }) => {
|
||||
dispatch(importFetchedStatus(data));
|
||||
dispatch(joinEventSuccess(data));
|
||||
dispatch(snackbar.success(
|
||||
|
@ -461,21 +482,108 @@ const expandEventParticipationRequestsFail = (id: string, error: AxiosError) =>
|
|||
error,
|
||||
});
|
||||
|
||||
const authorizeEventParticipationRequest = (id: string, accountId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(authorizeEventParticipationRequestRequest(id, accountId));
|
||||
|
||||
return api(getState)
|
||||
.post(`/api/v1/pleroma/events/${id}/participation_requests/${accountId}/authorize`)
|
||||
.then(() => {
|
||||
dispatch(authorizeEventParticipationRequestSuccess(id, accountId));
|
||||
dispatch(snackbar.success(messages.authorized));
|
||||
})
|
||||
.catch(error => dispatch(authorizeEventParticipationRequestFail(id, accountId, error)));
|
||||
};
|
||||
|
||||
const authorizeEventParticipationRequestRequest = (id: string, accountId: string) => ({
|
||||
type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST,
|
||||
id,
|
||||
accountId,
|
||||
});
|
||||
|
||||
const authorizeEventParticipationRequestSuccess = (id: string, accountId: string) => ({
|
||||
type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS,
|
||||
id,
|
||||
accountId,
|
||||
});
|
||||
|
||||
const authorizeEventParticipationRequestFail = (id: string, accountId: string, error: AxiosError) => ({
|
||||
type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL,
|
||||
id,
|
||||
accountId,
|
||||
error,
|
||||
});
|
||||
|
||||
const rejectEventParticipationRequest = (id: string, accountId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(rejectEventParticipationRequestRequest(id, accountId));
|
||||
|
||||
return api(getState)
|
||||
.post(`/api/v1/pleroma/events/${id}/participation_requests/${accountId}/reject`)
|
||||
.then(() => {
|
||||
dispatch(rejectEventParticipationRequestSuccess(id, accountId));
|
||||
dispatch(snackbar.success(messages.rejected));
|
||||
})
|
||||
.catch(error => dispatch(rejectEventParticipationRequestFail(id, accountId, error)));
|
||||
};
|
||||
|
||||
const rejectEventParticipationRequestRequest = (id: string, accountId: string) => ({
|
||||
type: EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST,
|
||||
id,
|
||||
accountId,
|
||||
});
|
||||
|
||||
const rejectEventParticipationRequestSuccess = (id: string, accountId: string) => ({
|
||||
type: EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS,
|
||||
id,
|
||||
accountId,
|
||||
});
|
||||
|
||||
const rejectEventParticipationRequestFail = (id: string, accountId: string, error: AxiosError) => ({
|
||||
type: EVENT_PARTICIPATION_REQUEST_REJECT_FAIL,
|
||||
id,
|
||||
accountId,
|
||||
error,
|
||||
});
|
||||
|
||||
const fetchEventIcs = (id: string) =>
|
||||
(dispatch: any, getState: () => RootState) =>
|
||||
api(getState).get(`/api/v1/pleroma/events/${id}/ics`);
|
||||
|
||||
const cancelEventCompose = () => ({
|
||||
type: EVENT_COMPOSE_CANCEL,
|
||||
});
|
||||
|
||||
const editEvent = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const status = getState().statuses.get(id)!;
|
||||
|
||||
dispatch({ type: STATUS_FETCH_SOURCE_REQUEST });
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${id}/source`).then(response => {
|
||||
dispatch({ type: STATUS_FETCH_SOURCE_SUCCESS });
|
||||
dispatch({
|
||||
type: EVENT_FORM_SET,
|
||||
status,
|
||||
text: response.data.text,
|
||||
location: response.data.location,
|
||||
});
|
||||
dispatch(openModal('COMPOSE_EVENT'));
|
||||
}).catch(error => {
|
||||
dispatch({ type: STATUS_FETCH_SOURCE_FAIL, error });
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
LOCATION_SEARCH_REQUEST,
|
||||
LOCATION_SEARCH_SUCCESS,
|
||||
LOCATION_SEARCH_FAIL,
|
||||
CREATE_EVENT_NAME_CHANGE,
|
||||
CREATE_EVENT_DESCRIPTION_CHANGE,
|
||||
CREATE_EVENT_START_TIME_CHANGE,
|
||||
CREATE_EVENT_END_TIME_CHANGE,
|
||||
CREATE_EVENT_HAS_END_TIME_CHANGE,
|
||||
CREATE_EVENT_APPROVAL_REQUIRED_CHANGE,
|
||||
CREATE_EVENT_LOCATION_CHANGE,
|
||||
EDIT_EVENT_NAME_CHANGE,
|
||||
EDIT_EVENT_DESCRIPTION_CHANGE,
|
||||
EDIT_EVENT_START_TIME_CHANGE,
|
||||
EDIT_EVENT_END_TIME_CHANGE,
|
||||
EDIT_EVENT_HAS_END_TIME_CHANGE,
|
||||
EDIT_EVENT_APPROVAL_REQUIRED_CHANGE,
|
||||
EDIT_EVENT_LOCATION_CHANGE,
|
||||
EVENT_BANNER_UPLOAD_REQUEST,
|
||||
EVENT_BANNER_UPLOAD_PROGRESS,
|
||||
EVENT_BANNER_UPLOAD_SUCCESS,
|
||||
|
@ -502,14 +610,22 @@ export {
|
|||
EVENT_PARTICIPATION_REQUESTS_EXPAND_REQUEST,
|
||||
EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS,
|
||||
EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL,
|
||||
EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST,
|
||||
EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS,
|
||||
EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL,
|
||||
EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST,
|
||||
EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS,
|
||||
EVENT_PARTICIPATION_REQUEST_REJECT_FAIL,
|
||||
EVENT_COMPOSE_CANCEL,
|
||||
EVENT_FORM_SET,
|
||||
locationSearch,
|
||||
changeCreateEventName,
|
||||
changeCreateEventDescription,
|
||||
changeCreateEventStartTime,
|
||||
changeCreateEventEndTime,
|
||||
changeCreateEventHasEndTime,
|
||||
changeCreateEventApprovalRequired,
|
||||
changeCreateEventLocation,
|
||||
changeEditEventName,
|
||||
changeEditEventDescription,
|
||||
changeEditEventStartTime,
|
||||
changeEditEventEndTime,
|
||||
changeEditEventHasEndTime,
|
||||
changeEditEventApprovalRequired,
|
||||
changeEditEventLocation,
|
||||
uploadEventBanner,
|
||||
uploadEventBannerRequest,
|
||||
uploadEventBannerProgress,
|
||||
|
@ -544,5 +660,15 @@ export {
|
|||
expandEventParticipationRequestsRequest,
|
||||
expandEventParticipationRequestsSuccess,
|
||||
expandEventParticipationRequestsFail,
|
||||
authorizeEventParticipationRequest,
|
||||
authorizeEventParticipationRequestRequest,
|
||||
authorizeEventParticipationRequestSuccess,
|
||||
authorizeEventParticipationRequestFail,
|
||||
rejectEventParticipationRequest,
|
||||
rejectEventParticipationRequestRequest,
|
||||
rejectEventParticipationRequestSuccess,
|
||||
rejectEventParticipationRequestFail,
|
||||
fetchEventIcs,
|
||||
cancelEventCompose,
|
||||
editEvent,
|
||||
};
|
||||
|
|
|
@ -63,6 +63,7 @@ interface IAccount {
|
|||
withRelationship?: boolean,
|
||||
showEdit?: boolean,
|
||||
emoji?: string,
|
||||
note?: string,
|
||||
}
|
||||
|
||||
const Account = ({
|
||||
|
@ -86,6 +87,7 @@ const Account = ({
|
|||
withRelationship = true,
|
||||
showEdit = false,
|
||||
emoji,
|
||||
note,
|
||||
}: IAccount) => {
|
||||
const overflowRef = React.useRef<HTMLDivElement>(null);
|
||||
const actionRef = React.useRef<HTMLDivElement>(null);
|
||||
|
@ -163,7 +165,7 @@ const Account = ({
|
|||
return (
|
||||
<div data-testid='account' className='flex-shrink-0 group block w-full' ref={overflowRef}>
|
||||
<HStack alignItems={actionAlignment} justifyContent='between'>
|
||||
<HStack alignItems={withAccountNote ? 'top' : 'center'} space={3}>
|
||||
<HStack alignItems={withAccountNote || note ? 'top' : 'center'} space={3}>
|
||||
<ProfilePopper
|
||||
condition={showProfileHoverCard}
|
||||
wrapper={(children) => <HoverRefWrapper className='relative' accountId={account.id} inline>{children}</HoverRefWrapper>}
|
||||
|
@ -206,7 +208,7 @@ const Account = ({
|
|||
</LinkEl>
|
||||
</ProfilePopper>
|
||||
|
||||
<Stack space={withAccountNote ? 1 : 0}>
|
||||
<Stack space={withAccountNote || note ? 1 : 0}>
|
||||
<HStack alignItems='center' space={1} style={style}>
|
||||
<Text theme='muted' size='sm' truncate>@{username}</Text>
|
||||
|
||||
|
@ -237,7 +239,14 @@ const Account = ({
|
|||
) : null}
|
||||
</HStack>
|
||||
|
||||
{withAccountNote && (
|
||||
{note ? (
|
||||
<Text
|
||||
size='sm'
|
||||
className='mr-2'
|
||||
>
|
||||
{note}
|
||||
</Text>
|
||||
) : withAccountNote && (
|
||||
<Text
|
||||
size='sm'
|
||||
dangerouslySetInnerHTML={{ __html: account.note_emojified }}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { fetchEventIcs } from 'soapbox/actions/events';
|
||||
import { editEvent, fetchEventIcs } from 'soapbox/actions/events';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
|
@ -159,9 +159,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
|||
const handleManageClick: React.MouseEventHandler = e => {
|
||||
e.stopPropagation();
|
||||
|
||||
dispatch(openModal('MANAGE_EVENT', {
|
||||
statusId: status.id,
|
||||
}));
|
||||
dispatch(editEvent(status.id));
|
||||
};
|
||||
|
||||
const handleParticipantsClick: React.MouseEventHandler = e => {
|
||||
|
@ -228,7 +226,6 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
|||
size='sm'
|
||||
theme='secondary'
|
||||
onClick={handleManageClick}
|
||||
to={`/@${account.acct}/events/${status.id}`}
|
||||
>
|
||||
<FormattedMessage id='event.manage' defaultMessage='Manage' />
|
||||
</Button>
|
||||
|
|
|
@ -53,6 +53,9 @@ const icons: Record<NotificationType, string> = {
|
|||
'pleroma:emoji_reaction': require('@tabler/icons/mood-happy.svg'),
|
||||
user_approved: require('@tabler/icons/user-plus.svg'),
|
||||
update: require('@tabler/icons/pencil.svg'),
|
||||
'pleroma:event_reminder': require('@tabler/icons/calendar-time.svg'),
|
||||
'pleroma:participation_request': require('@tabler/icons/calendar-event.svg'),
|
||||
'pleroma:participation_accepted': require('@tabler/icons/calendar-event.svg'),
|
||||
};
|
||||
|
||||
const messages: Record<NotificationType, MessageDescriptor> = defineMessages({
|
||||
|
@ -104,6 +107,18 @@ const messages: Record<NotificationType, MessageDescriptor> = defineMessages({
|
|||
id: 'notification.update',
|
||||
defaultMessage: '{name} edited a post you interacted with',
|
||||
},
|
||||
'pleroma:event_reminder': {
|
||||
id: 'notification.pleroma:event_reminder',
|
||||
defaultMessage: 'An event you are participating in starts soon',
|
||||
},
|
||||
'pleroma:participation_request': {
|
||||
id: 'notification.pleroma:participation_request',
|
||||
defaultMessage: '{name} wants to join your event',
|
||||
},
|
||||
'pleroma:participation_accepted': {
|
||||
id: 'notification.pleroma:participation_accepted',
|
||||
defaultMessage: 'You were accepted to join the event',
|
||||
},
|
||||
});
|
||||
|
||||
const buildMessage = (
|
||||
|
@ -302,6 +317,9 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
case 'poll':
|
||||
case 'update':
|
||||
case 'pleroma:emoji_reaction':
|
||||
case 'pleroma:event_reminder':
|
||||
case 'pleroma:participation_accepted':
|
||||
case 'pleroma:participation_request':
|
||||
return status && typeof status === 'object' ? (
|
||||
<StatusContainer
|
||||
id={status.id}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Button } from 'soapbox/components/ui';
|
|||
|
||||
const ComposeButton = () => {
|
||||
const dispatch = useDispatch();
|
||||
const onOpenCompose = () => dispatch(openModal('CREATE_EVENT'));
|
||||
const onOpenCompose = () => dispatch(openModal('COMPOSE_EVENT'));
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
CompareHistoryModal,
|
||||
VerifySmsModal,
|
||||
FamiliarFollowersModal,
|
||||
CreateEventModal,
|
||||
ComposeEventModal,
|
||||
JoinEventModal,
|
||||
AccountModerationModal,
|
||||
EventMapModal,
|
||||
|
@ -74,7 +74,7 @@ const MODAL_COMPONENTS = {
|
|||
'COMPARE_HISTORY': CompareHistoryModal,
|
||||
'VERIFY_SMS': VerifySmsModal,
|
||||
'FAMILIAR_FOLLOWERS': FamiliarFollowersModal,
|
||||
'CREATE_EVENT': CreateEventModal,
|
||||
'COMPOSE_EVENT': ComposeEventModal,
|
||||
'JOIN_EVENT': JoinEventModal,
|
||||
'ACCOUNT_MODERATION': AccountModerationModal,
|
||||
'EVENT_MAP': EventMapModal,
|
||||
|
|
|
@ -0,0 +1,320 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import Toggle from 'react-toggle';
|
||||
|
||||
import {
|
||||
changeEditEventApprovalRequired,
|
||||
changeEditEventDescription,
|
||||
changeEditEventEndTime,
|
||||
changeEditEventHasEndTime,
|
||||
changeEditEventName,
|
||||
changeEditEventStartTime,
|
||||
changeEditEventLocation,
|
||||
uploadEventBanner,
|
||||
undoUploadEventBanner,
|
||||
submitEvent,
|
||||
fetchEventParticipationRequests,
|
||||
rejectEventParticipationRequest,
|
||||
authorizeEventParticipationRequest,
|
||||
} from 'soapbox/actions/events';
|
||||
import { ADDRESS_ICONS } from 'soapbox/components/autosuggest-location';
|
||||
import LocationSearch from 'soapbox/components/location-search';
|
||||
import { Button, Form, FormGroup, HStack, Icon, IconButton, Input, Modal, Spinner, Stack, Tabs, Text, Textarea } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account_container';
|
||||
import { isCurrentOrFutureDate } from 'soapbox/features/compose/components/schedule_form';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import { DatePicker } from 'soapbox/features/ui/util/async-components';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
import UploadButton from './upload-button';
|
||||
|
||||
const messages = defineMessages({
|
||||
eventNamePlaceholder: { id: 'compose_event.fields.name_placeholder', defaultMessage: 'Name' },
|
||||
eventDescriptionPlaceholder: { id: 'compose_event.fields.description_placeholder', defaultMessage: 'Description' },
|
||||
eventStartTimePlaceholder: { id: 'compose_event.fields.start_time_placeholder', defaultMessage: 'Event begins on…' },
|
||||
eventEndTimePlaceholder: { id: 'compose_event.fields.end_time_placeholder', defaultMessage: 'Event ends on…' },
|
||||
resetLocation: { id: 'compose_event.reset_location', defaultMessage: 'Reset location' },
|
||||
edit: { id: 'compose_event.tabs.edit', defaultMessage: 'Edit details' },
|
||||
pending: { id: 'compose_event.tabs.pending', defaultMessage: 'Manage requests' },
|
||||
authorize: { id: 'compose_event.participation_requests.authorize', defaultMessage: 'Authorize' },
|
||||
reject: { id: 'compose_event.participation_requests.reject', defaultMessage: 'Reject' },
|
||||
});
|
||||
|
||||
|
||||
interface IAccount {
|
||||
eventId: string,
|
||||
id: string,
|
||||
participationMessage: string | null,
|
||||
}
|
||||
|
||||
const Account: React.FC<IAccount> = ({ eventId, id, participationMessage }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleAuthorize = () => {
|
||||
dispatch(authorizeEventParticipationRequest(eventId, id));
|
||||
};
|
||||
|
||||
const handleReject = () => {
|
||||
dispatch(rejectEventParticipationRequest(eventId, id));
|
||||
};
|
||||
|
||||
return (
|
||||
<AccountContainer
|
||||
id={id}
|
||||
note={participationMessage || undefined}
|
||||
action={
|
||||
<HStack space={2}>
|
||||
<Button
|
||||
theme='secondary'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.authorize)}
|
||||
onClick={handleAuthorize}
|
||||
/>
|
||||
<Button
|
||||
theme='danger'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.reject)}
|
||||
onClick={handleReject}
|
||||
/>
|
||||
</HStack>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface IComposeEventModal {
|
||||
onClose: (type?: string) => void,
|
||||
}
|
||||
|
||||
const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [tab, setTab] = useState<'edit' | 'pending'>('edit');
|
||||
|
||||
const banner = useAppSelector((state) => state.compose_event.banner);
|
||||
const isUploading = useAppSelector((state) => state.compose_event.is_uploading);
|
||||
|
||||
const name = useAppSelector((state) => state.compose_event.name);
|
||||
const description = useAppSelector((state) => state.compose_event.status);
|
||||
const startTime = useAppSelector((state) => state.compose_event.start_time);
|
||||
const endTime = useAppSelector((state) => state.compose_event.end_time);
|
||||
const approvalRequired = useAppSelector((state) => state.compose_event.approval_required);
|
||||
const location = useAppSelector((state) => state.compose_event.location);
|
||||
|
||||
const id = useAppSelector((state) => state.compose_event.id);
|
||||
|
||||
const isSubmitting = useAppSelector((state) => state.compose_event.is_submitting);
|
||||
|
||||
const onChangeName: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
|
||||
dispatch(changeEditEventName(target.value));
|
||||
};
|
||||
|
||||
const onChangeDescription: React.ChangeEventHandler<HTMLTextAreaElement> = ({ target }) => {
|
||||
dispatch(changeEditEventDescription(target.value));
|
||||
};
|
||||
|
||||
const onChangeStartTime = (date: Date) => {
|
||||
dispatch(changeEditEventStartTime(date));
|
||||
};
|
||||
|
||||
const onChangeEndTime = (date: Date) => {
|
||||
dispatch(changeEditEventEndTime(date));
|
||||
};
|
||||
|
||||
const onChangeHasEndTime: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
|
||||
dispatch(changeEditEventHasEndTime(target.checked));
|
||||
};
|
||||
|
||||
const onChangeApprovalRequired: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
|
||||
dispatch(changeEditEventApprovalRequired(target.checked));
|
||||
};
|
||||
|
||||
const onChangeLocation = (value: string | null) => {
|
||||
dispatch(changeEditEventLocation(value));
|
||||
};
|
||||
|
||||
const onClickClose = () => {
|
||||
onClose('COMPOSE_EVENT');
|
||||
};
|
||||
|
||||
const handleFiles = (files: FileList) => {
|
||||
dispatch(uploadEventBanner(files[0], intl));
|
||||
};
|
||||
|
||||
const handleClearBanner = () => {
|
||||
dispatch(undoUploadEventBanner());
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
dispatch(submitEvent());
|
||||
};
|
||||
|
||||
const accounts = useAppSelector((state) => state.user_lists.event_participation_requests.get(id!)?.items);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) dispatch(fetchEventParticipationRequests(id));
|
||||
}, []);
|
||||
|
||||
const renderLocation = () => location && (
|
||||
<HStack className='h-[38px] text-gray-700 dark:text-gray-500' alignItems='center' space={2}>
|
||||
<Icon src={ADDRESS_ICONS[location.type] || require('@tabler/icons/map-pin.svg')} />
|
||||
<Stack className='flex-grow'>
|
||||
<Text>{location.description}</Text>
|
||||
<Text theme='muted' size='xs'>{[location.street, location.locality, location.country].filter(val => val.trim()).join(' · ')}</Text>
|
||||
</Stack>
|
||||
<IconButton title={intl.formatMessage(messages.resetLocation)} src={require('@tabler/icons/x.svg')} onClick={() => onChangeLocation(null)} />
|
||||
</HStack>
|
||||
);
|
||||
|
||||
const renderTabs = () => {
|
||||
const items = [
|
||||
{
|
||||
text: intl.formatMessage(messages.edit),
|
||||
action: () => setTab('edit'),
|
||||
name: 'edit',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage(messages.pending),
|
||||
action: () => setTab('pending'),
|
||||
name: 'pending',
|
||||
},
|
||||
];
|
||||
|
||||
return <Tabs items={items} activeItem={tab} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={id
|
||||
? <FormattedMessage id='navigation_bar.compose_event' defaultMessage='Manage event' />
|
||||
: <FormattedMessage id='navigation_bar.create_event' defaultMessage='Create new event' />}
|
||||
confirmationAction={tab === 'edit' ? handleSubmit : undefined}
|
||||
confirmationText={id
|
||||
? <FormattedMessage id='compose_event.update' defaultMessage='Update' />
|
||||
: <FormattedMessage id='compose_event.create' defaultMessage='Create' />}
|
||||
confirmationDisabled={isSubmitting}
|
||||
onClose={onClickClose}
|
||||
>
|
||||
<Stack space={2}>
|
||||
{id && renderTabs()}
|
||||
{tab === 'edit' ? (
|
||||
<Form>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='compose_event.fields.banner_label' defaultMessage='Event banner' />}
|
||||
>
|
||||
<div className='flex items-center justify-center bg-gray-200 dark:bg-gray-900/50 rounded-lg text-black dark:text-white sm:shadow dark:sm:shadow-inset overflow-hidden h-24 sm:h-32 relative'>
|
||||
{banner ? (
|
||||
<>
|
||||
<img className='h-full w-full object-cover' src={banner.url} alt='' />
|
||||
<IconButton className='absolute top-2 right-2' src={require('@tabler/icons/x.svg')} onClick={handleClearBanner} />
|
||||
</>
|
||||
) : (
|
||||
<UploadButton disabled={isUploading} onSelectFile={handleFiles} />
|
||||
)}
|
||||
|
||||
</div>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='compose_event.fields.name_label' defaultMessage='Event name' />}
|
||||
>
|
||||
<Input
|
||||
type='text'
|
||||
placeholder={intl.formatMessage(messages.eventNamePlaceholder)}
|
||||
value={name}
|
||||
onChange={onChangeName}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='compose_event.fields.description_label' defaultMessage='Event description' />}
|
||||
hintText={<FormattedMessage id='compose_event.fields.description_hint' defaultMessage='Markdown syntax is supported' />}
|
||||
>
|
||||
<Textarea
|
||||
autoComplete='off'
|
||||
placeholder={intl.formatMessage(messages.eventDescriptionPlaceholder)}
|
||||
value={description}
|
||||
onChange={onChangeDescription}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='compose_event.fields.location_label' defaultMessage='Event location' />}
|
||||
>
|
||||
{location ? renderLocation() : (
|
||||
<LocationSearch
|
||||
onSelected={onChangeLocation}
|
||||
/>
|
||||
)}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='compose_event.fields.start_time_label' defaultMessage='Event start date' />}
|
||||
>
|
||||
<BundleContainer fetchComponent={DatePicker}>
|
||||
{Component => (<Component
|
||||
showTimeSelect
|
||||
dateFormat='MMMM d, yyyy h:mm aa'
|
||||
timeIntervals={15}
|
||||
wrapperClassName='react-datepicker-wrapper'
|
||||
placeholderText={intl.formatMessage(messages.eventStartTimePlaceholder)}
|
||||
filterDate={isCurrentOrFutureDate}
|
||||
selected={startTime}
|
||||
onChange={onChangeStartTime}
|
||||
/>)}
|
||||
</BundleContainer>
|
||||
</FormGroup>
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
checked={!!endTime}
|
||||
onChange={onChangeHasEndTime}
|
||||
/>
|
||||
<Text tag='span' theme='muted'>
|
||||
<FormattedMessage id='compose_event.fields.has_end_time' defaultMessage='The event has end date' />
|
||||
</Text>
|
||||
</HStack>
|
||||
{endTime && (
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='compose_event.fields.end_time_label' defaultMessage='Event end date' />}
|
||||
>
|
||||
<BundleContainer fetchComponent={DatePicker}>
|
||||
{Component => (<Component
|
||||
showTimeSelect
|
||||
dateFormat='MMMM d, yyyy h:mm aa'
|
||||
timeIntervals={15}
|
||||
wrapperClassName='react-datepicker-wrapper'
|
||||
placeholderText={intl.formatMessage(messages.eventEndTimePlaceholder)}
|
||||
filterDate={isCurrentOrFutureDate}
|
||||
selected={endTime}
|
||||
onChange={onChangeEndTime}
|
||||
/>)}
|
||||
</BundleContainer>
|
||||
</FormGroup>
|
||||
)}
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
checked={approvalRequired}
|
||||
onChange={onChangeApprovalRequired}
|
||||
/>
|
||||
<Text tag='span' theme='muted'>
|
||||
<FormattedMessage id='compose_event.fields.approval_required' defaultMessage='I want to approve participation requests manually' />
|
||||
</Text>
|
||||
</HStack>
|
||||
</Form>
|
||||
) : accounts ? (
|
||||
<Stack space={3}>
|
||||
{accounts.size > 0 ? (
|
||||
accounts.map(({ account, participation_message }) =>
|
||||
<Account key={account} eventId={id!} id={account} participationMessage={participation_message} />,
|
||||
)
|
||||
) : (
|
||||
<FormattedMessage id='empty_column.event_participant_requests' defaultMessage='There are no pending event participation requests.' />
|
||||
)}
|
||||
</Stack>
|
||||
) : <Spinner />}
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComposeEventModal;
|
|
@ -7,7 +7,7 @@ import { useAppSelector } from 'soapbox/hooks';
|
|||
import type { List as ImmutableList } from 'immutable';
|
||||
|
||||
const messages = defineMessages({
|
||||
upload: { id: 'create_event.upload_banner', defaultMessage: 'Upload event banner' },
|
||||
upload: { id: 'compose_event.upload_banner', defaultMessage: 'Upload event banner' },
|
||||
});
|
||||
|
||||
interface IUploadButton {
|
|
@ -1,223 +0,0 @@
|
|||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import Toggle from 'react-toggle';
|
||||
|
||||
import {
|
||||
changeCreateEventApprovalRequired,
|
||||
changeCreateEventDescription,
|
||||
changeCreateEventEndTime,
|
||||
changeCreateEventHasEndTime,
|
||||
changeCreateEventName,
|
||||
changeCreateEventStartTime,
|
||||
changeCreateEventLocation,
|
||||
uploadEventBanner,
|
||||
undoUploadEventBanner,
|
||||
submitEvent,
|
||||
} from 'soapbox/actions/events';
|
||||
import { ADDRESS_ICONS } from 'soapbox/components/autosuggest-location';
|
||||
import LocationSearch from 'soapbox/components/location-search';
|
||||
import { Form, FormGroup, HStack, Icon, IconButton, Input, Modal, Stack, Text, Textarea } from 'soapbox/components/ui';
|
||||
import { isCurrentOrFutureDate } from 'soapbox/features/compose/components/schedule_form';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import { DatePicker } from 'soapbox/features/ui/util/async-components';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
import UploadButton from './upload-button';
|
||||
|
||||
const messages = defineMessages({
|
||||
eventNamePlaceholder: { id: 'create_event.fields.name_placeholder', defaultMessage: 'Name' },
|
||||
eventDescriptionPlaceholder: { id: 'create_event.fields.description_placeholder', defaultMessage: 'Description' },
|
||||
eventStartTimePlaceholder: { id: 'create_event.fields.start_time_placeholder', defaultMessage: 'Event begins on…' },
|
||||
eventEndTimePlaceholder: { id: 'create_event.fields.end_time_placeholder', defaultMessage: 'Event ends on…' },
|
||||
resetLocation: { id: 'create_event.reset_location', defaultMessage: 'Reset location' },
|
||||
});
|
||||
|
||||
interface ICreateEventModal {
|
||||
onClose: (type?: string) => void,
|
||||
}
|
||||
|
||||
const CreateEventModal: React.FC<ICreateEventModal> = ({ onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const banner = useAppSelector((state) => state.create_event.banner);
|
||||
const isUploading = useAppSelector((state) => state.create_event.is_uploading);
|
||||
|
||||
const name = useAppSelector((state) => state.create_event.name);
|
||||
const description = useAppSelector((state) => state.create_event.status);
|
||||
const startTime = useAppSelector((state) => state.create_event.start_time);
|
||||
const endTime = useAppSelector((state) => state.create_event.end_time);
|
||||
const approvalRequired = useAppSelector((state) => state.create_event.approval_required);
|
||||
const location = useAppSelector((state) => state.create_event.location);
|
||||
|
||||
const isSubmitting = useAppSelector((state) => state.create_event.is_submitting);
|
||||
|
||||
const onChangeName: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
|
||||
dispatch(changeCreateEventName(target.value));
|
||||
};
|
||||
|
||||
const onChangeDescription: React.ChangeEventHandler<HTMLTextAreaElement> = ({ target }) => {
|
||||
dispatch(changeCreateEventDescription(target.value));
|
||||
};
|
||||
|
||||
const onChangeStartTime = (date: Date) => {
|
||||
dispatch(changeCreateEventStartTime(date));
|
||||
};
|
||||
|
||||
const onChangeEndTime = (date: Date) => {
|
||||
dispatch(changeCreateEventEndTime(date));
|
||||
};
|
||||
|
||||
const onChangeHasEndTime: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
|
||||
dispatch(changeCreateEventHasEndTime(target.checked));
|
||||
};
|
||||
|
||||
const onChangeApprovalRequired: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
|
||||
dispatch(changeCreateEventApprovalRequired(target.checked));
|
||||
};
|
||||
|
||||
const onChangeLocation = (value: string | null) => {
|
||||
dispatch(changeCreateEventLocation(value));
|
||||
};
|
||||
|
||||
const onClickClose = () => {
|
||||
onClose('CREATE_EVENT');
|
||||
};
|
||||
|
||||
const handleFiles = (files: FileList) => {
|
||||
dispatch(uploadEventBanner(files[0], intl));
|
||||
};
|
||||
|
||||
const handleClearBanner = () => {
|
||||
dispatch(undoUploadEventBanner());
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
dispatch(submitEvent());
|
||||
};
|
||||
|
||||
const renderLocation = () => location && (
|
||||
<HStack className='h-[38px] text-gray-700 dark:text-gray-500' alignItems='center' space={2}>
|
||||
<Icon src={ADDRESS_ICONS[location.type] || require('@tabler/icons/map-pin.svg')} />
|
||||
<Stack className='flex-grow'>
|
||||
<Text>{location.description}</Text>
|
||||
<Text theme='muted' size='xs'>{[location.street, location.locality, location.country].filter(val => val.trim()).join(' · ')}</Text>
|
||||
</Stack>
|
||||
<IconButton title={intl.formatMessage(messages.resetLocation)} src={require('@tabler/icons/x.svg')} onClick={() => onChangeLocation(null)} />
|
||||
</HStack>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<FormattedMessage id='navigation_bar.create_event' defaultMessage='Create new event' />}
|
||||
confirmationAction={handleSubmit}
|
||||
confirmationText={<FormattedMessage id='create_event.create' defaultMessage='Create' />}
|
||||
confirmationDisabled={isSubmitting}
|
||||
onClose={onClickClose}
|
||||
>
|
||||
<Form>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='create_event.fields.banner_label' defaultMessage='Event banner' />}
|
||||
>
|
||||
<div className='flex items-center justify-center bg-gray-200 dark:bg-gray-900/50 rounded-lg text-black dark:text-white sm:shadow dark:sm:shadow-inset overflow-hidden h-24 sm:h-32 relative'>
|
||||
{banner ? (
|
||||
<>
|
||||
<img className='h-full w-full object-cover' src={banner.url} alt='' />
|
||||
<IconButton className='absolute top-2 right-2' src={require('@tabler/icons/x.svg')} onClick={handleClearBanner} />
|
||||
</>
|
||||
) : (
|
||||
<UploadButton disabled={isUploading} onSelectFile={handleFiles} />
|
||||
)}
|
||||
|
||||
</div>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='create_event.fields.name_label' defaultMessage='Event name' />}
|
||||
>
|
||||
<Input
|
||||
type='text'
|
||||
placeholder={intl.formatMessage(messages.eventNamePlaceholder)}
|
||||
value={name}
|
||||
onChange={onChangeName}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='create_event.fields.description_label' defaultMessage='Event description' />}
|
||||
hintText={<FormattedMessage id='create_event.fields.description_hint' defaultMessage='Markdown syntax is supported' />}
|
||||
>
|
||||
<Textarea
|
||||
autoComplete='off'
|
||||
placeholder={intl.formatMessage(messages.eventDescriptionPlaceholder)}
|
||||
value={description}
|
||||
onChange={onChangeDescription}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='create_event.fields.location_label' defaultMessage='Event location' />}
|
||||
>
|
||||
{location ? renderLocation() : (
|
||||
<LocationSearch
|
||||
onSelected={onChangeLocation}
|
||||
/>
|
||||
)}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='create_event.fields.start_time_label' defaultMessage='Event start date' />}
|
||||
>
|
||||
<BundleContainer fetchComponent={DatePicker}>
|
||||
{Component => (<Component
|
||||
showTimeSelect
|
||||
dateFormat='MMMM d, yyyy h:mm aa'
|
||||
timeIntervals={15}
|
||||
wrapperClassName='react-datepicker-wrapper'
|
||||
placeholderText={intl.formatMessage(messages.eventStartTimePlaceholder)}
|
||||
filterDate={isCurrentOrFutureDate}
|
||||
selected={startTime}
|
||||
onChange={onChangeStartTime}
|
||||
/>)}
|
||||
</BundleContainer>
|
||||
</FormGroup>
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
checked={!!endTime}
|
||||
onChange={onChangeHasEndTime}
|
||||
/>
|
||||
<Text tag='span' theme='muted'>
|
||||
<FormattedMessage id='create_event.fields.has_end_time' defaultMessage='The event has end date' />
|
||||
</Text>
|
||||
</HStack>
|
||||
{endTime && (
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='create_event.fields.end_time_label' defaultMessage='Event end date' />}
|
||||
>
|
||||
<BundleContainer fetchComponent={DatePicker}>
|
||||
{Component => (<Component
|
||||
showTimeSelect
|
||||
dateFormat='MMMM d, yyyy h:mm aa'
|
||||
timeIntervals={15}
|
||||
wrapperClassName='react-datepicker-wrapper'
|
||||
placeholderText={intl.formatMessage(messages.eventEndTimePlaceholder)}
|
||||
filterDate={isCurrentOrFutureDate}
|
||||
selected={endTime}
|
||||
onChange={onChangeEndTime}
|
||||
/>)}
|
||||
</BundleContainer>
|
||||
</FormGroup>
|
||||
)}
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Toggle
|
||||
icons={false}
|
||||
checked={approvalRequired}
|
||||
onChange={onChangeApprovalRequired}
|
||||
/>
|
||||
<Text tag='span' theme='muted'>
|
||||
<FormattedMessage id='create_event.fields.approval_required' defaultMessage='I want to approve participation requests manually' />
|
||||
</Text>
|
||||
</HStack>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateEventModal;
|
|
@ -1,6 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { cancelReplyCompose } from 'soapbox/actions/compose';
|
||||
import { cancelEventCompose } from 'soapbox/actions/events';
|
||||
import { closeModal } from 'soapbox/actions/modals';
|
||||
import { cancelReport } from 'soapbox/actions/reports';
|
||||
|
||||
|
@ -24,6 +25,9 @@ const mapDispatchToProps = (dispatch) => ({
|
|||
case 'COMPOSE':
|
||||
dispatch(cancelReplyCompose());
|
||||
break;
|
||||
case 'COMPOSE_EVENT':
|
||||
dispatch(cancelEventCompose());
|
||||
break;
|
||||
case 'REPORT':
|
||||
dispatch(cancelReport());
|
||||
break;
|
||||
|
|
|
@ -506,8 +506,8 @@ export function AnnouncementsPanel() {
|
|||
return import(/* webpackChunkName: "features/announcements" */'../../../components/announcements/announcements-panel');
|
||||
}
|
||||
|
||||
export function CreateEventModal() {
|
||||
return import(/* webpackChunkName: "features/create_event_modal" */'../components/modals/create-event-modal/create-event-modal');
|
||||
export function ComposeEventModal() {
|
||||
return import(/* webpackChunkName: "features/compose_event_modal" */'../components/modals/compose-event-modal/compose-event-modal');
|
||||
}
|
||||
|
||||
export function JoinEventModal() {
|
||||
|
|
|
@ -2,13 +2,13 @@ import { fromJS, Record as ImmutableRecord } from 'immutable';
|
|||
import { AnyAction } from 'redux';
|
||||
|
||||
import {
|
||||
CREATE_EVENT_APPROVAL_REQUIRED_CHANGE,
|
||||
CREATE_EVENT_DESCRIPTION_CHANGE,
|
||||
CREATE_EVENT_END_TIME_CHANGE,
|
||||
CREATE_EVENT_HAS_END_TIME_CHANGE,
|
||||
CREATE_EVENT_LOCATION_CHANGE,
|
||||
CREATE_EVENT_NAME_CHANGE,
|
||||
CREATE_EVENT_START_TIME_CHANGE,
|
||||
EDIT_EVENT_APPROVAL_REQUIRED_CHANGE,
|
||||
EDIT_EVENT_DESCRIPTION_CHANGE,
|
||||
EDIT_EVENT_END_TIME_CHANGE,
|
||||
EDIT_EVENT_HAS_END_TIME_CHANGE,
|
||||
EDIT_EVENT_LOCATION_CHANGE,
|
||||
EDIT_EVENT_NAME_CHANGE,
|
||||
EDIT_EVENT_START_TIME_CHANGE,
|
||||
EVENT_BANNER_UPLOAD_REQUEST,
|
||||
EVENT_BANNER_UPLOAD_PROGRESS,
|
||||
EVENT_BANNER_UPLOAD_SUCCESS,
|
||||
|
@ -17,12 +17,15 @@ import {
|
|||
EVENT_SUBMIT_REQUEST,
|
||||
EVENT_SUBMIT_SUCCESS,
|
||||
EVENT_SUBMIT_FAIL,
|
||||
EVENT_COMPOSE_CANCEL,
|
||||
EVENT_FORM_SET,
|
||||
} from 'soapbox/actions/events';
|
||||
import { normalizeAttachment } from 'soapbox/normalizers';
|
||||
import { normalizeAttachment, normalizeLocation } from 'soapbox/normalizers';
|
||||
|
||||
import type {
|
||||
Attachment as AttachmentEntity,
|
||||
Location as LocationEntity,
|
||||
Status as StatusEntity,
|
||||
} from 'soapbox/types/entities';
|
||||
|
||||
export const ReducerRecord = ImmutableRecord({
|
||||
|
@ -36,6 +39,7 @@ export const ReducerRecord = ImmutableRecord({
|
|||
progress: 0,
|
||||
is_uploading: false,
|
||||
is_submitting: false,
|
||||
id: null as string | null,
|
||||
});
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
|
@ -48,22 +52,22 @@ const setHasEndTime = (state: State) => {
|
|||
return state.set('end_time', endTime);
|
||||
};
|
||||
|
||||
export default function create_event(state = ReducerRecord(), action: AnyAction): State {
|
||||
export default function compose_event(state = ReducerRecord(), action: AnyAction): State {
|
||||
switch (action.type) {
|
||||
case CREATE_EVENT_NAME_CHANGE:
|
||||
case EDIT_EVENT_NAME_CHANGE:
|
||||
return state.set('name', action.value);
|
||||
case CREATE_EVENT_DESCRIPTION_CHANGE:
|
||||
case EDIT_EVENT_DESCRIPTION_CHANGE:
|
||||
return state.set('status', action.value);
|
||||
case CREATE_EVENT_START_TIME_CHANGE:
|
||||
case EDIT_EVENT_START_TIME_CHANGE:
|
||||
return state.set('start_time', action.value);
|
||||
case CREATE_EVENT_END_TIME_CHANGE:
|
||||
case EDIT_EVENT_END_TIME_CHANGE:
|
||||
return state.set('end_time', action.value);
|
||||
case CREATE_EVENT_HAS_END_TIME_CHANGE:
|
||||
case EDIT_EVENT_HAS_END_TIME_CHANGE:
|
||||
if (action.value) return setHasEndTime(state);
|
||||
return state.set('end_time', null);
|
||||
case CREATE_EVENT_APPROVAL_REQUIRED_CHANGE:
|
||||
case EDIT_EVENT_APPROVAL_REQUIRED_CHANGE:
|
||||
return state.set('approval_required', action.value);
|
||||
case CREATE_EVENT_LOCATION_CHANGE:
|
||||
case EDIT_EVENT_LOCATION_CHANGE:
|
||||
return state.set('location', action.value);
|
||||
case EVENT_BANNER_UPLOAD_REQUEST:
|
||||
return state.set('is_uploading', true);
|
||||
|
@ -80,6 +84,23 @@ export default function create_event(state = ReducerRecord(), action: AnyAction)
|
|||
case EVENT_SUBMIT_SUCCESS:
|
||||
case EVENT_SUBMIT_FAIL:
|
||||
return state.set('is_submitting', false);
|
||||
case EVENT_COMPOSE_CANCEL:
|
||||
return ReducerRecord();
|
||||
case EVENT_FORM_SET:
|
||||
return ReducerRecord({
|
||||
name: action.status.event.name,
|
||||
status: action.text,
|
||||
// location: null as LocationEntity | null,
|
||||
start_time: new Date(action.status.event.start_time),
|
||||
end_time: action.status.event.start_time ? new Date(action.status.event.end_time) : null,
|
||||
approval_required: action.status.event.join_mode !== 'free',
|
||||
banner: (action.status as StatusEntity).media_attachments.find(({ description }) => description === 'Banner') || null,
|
||||
location: action.location ? normalizeLocation(action.location) : null,
|
||||
progress: 0,
|
||||
is_uploading: false,
|
||||
is_submitting: false,
|
||||
id: action.status.id,
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
|
@ -19,9 +19,9 @@ import chat_message_lists from './chat_message_lists';
|
|||
import chat_messages from './chat_messages';
|
||||
import chats from './chats';
|
||||
import compose from './compose';
|
||||
import compose_event from './compose_event';
|
||||
import contexts from './contexts';
|
||||
import conversations from './conversations';
|
||||
import create_event from './create_event';
|
||||
import custom_emojis from './custom_emojis';
|
||||
import domain_lists from './domain_lists';
|
||||
import dropdown_menu from './dropdown_menu';
|
||||
|
@ -127,7 +127,7 @@ const reducers = {
|
|||
rules,
|
||||
history,
|
||||
announcements,
|
||||
create_event,
|
||||
compose_event,
|
||||
};
|
||||
|
||||
// Build a default state from all reducers: it has the key and `undefined`
|
||||
|
|
|
@ -34,6 +34,8 @@ import {
|
|||
EVENT_PARTICIPATIONS_FETCH_SUCCESS,
|
||||
EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS,
|
||||
EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS,
|
||||
EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS,
|
||||
EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS,
|
||||
} from 'soapbox/actions/events';
|
||||
import {
|
||||
FAMILIAR_FOLLOWERS_FETCH_SUCCESS,
|
||||
|
@ -212,7 +214,7 @@ export default function userLists(state = ReducerRecord(), action: AnyAction) {
|
|||
case EVENT_PARTICIPATIONS_EXPAND_SUCCESS:
|
||||
return appendToList(state, ['event_participations', action.id], action.accounts, action.next);
|
||||
case EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS:
|
||||
return state.setIn(['event_participations', action.id], ParticipationRequestListRecord({
|
||||
return state.setIn(['event_participation_requests', action.id], ParticipationRequestListRecord({
|
||||
next: action.next,
|
||||
items: ImmutableOrderedSet(action.participations.map(({ account, participation_message }: APIEntity) => ParticipationRequestRecord({
|
||||
account: account.id,
|
||||
|
@ -221,13 +223,19 @@ export default function userLists(state = ReducerRecord(), action: AnyAction) {
|
|||
}));
|
||||
case EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS:
|
||||
return state.updateIn(
|
||||
['event_participations', action.id, 'items'],
|
||||
['event_participation_requests', action.id, 'items'],
|
||||
(items) => (items as ImmutableOrderedSet<ParticipationRequest>)
|
||||
.union(action.participations.map(({ account, participation_message }: APIEntity) => ParticipationRequestRecord({
|
||||
account: account.id,
|
||||
participation_message,
|
||||
}))),
|
||||
);
|
||||
case EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS:
|
||||
case EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS:
|
||||
return state.updateIn(
|
||||
['event_participation_requests', action.id, 'items'],
|
||||
items => (items as ImmutableOrderedSet<ParticipationRequest>).filter(({ account }) => account !== action.accountId),
|
||||
);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ const NOTIFICATION_TYPES = [
|
|||
'pleroma:emoji_reaction',
|
||||
'user_approved',
|
||||
'update',
|
||||
'pleroma:event_reminder',
|
||||
'pleroma:participation_request',
|
||||
'pleroma:participation_accepted',
|
||||
] as const;
|
||||
|
||||
type NotificationType = typeof NOTIFICATION_TYPES[number];
|
||||
|
|
Loading…
Reference in New Issue