2022-09-25 14:18:11 -07:00
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 ,
2022-11-30 10:32:35 -08:00
cancelEventCompose ,
2022-09-25 14:18:11 -07:00
} from 'soapbox/actions/events' ;
2022-11-30 10:32:35 -08:00
import { closeModal , openModal } from 'soapbox/actions/modals' ;
2022-09-25 14:18:11 -07:00
import { ADDRESS_ICONS } from 'soapbox/components/autosuggest-location' ;
import LocationSearch from 'soapbox/components/location-search' ;
2022-11-30 10:32:35 -08:00
import { checkEventComposeContent } from 'soapbox/components/modal-root' ;
2022-09-25 14:18:11 -07:00
import { Button , Form , FormGroup , HStack , Icon , IconButton , Input , Modal , Spinner , Stack , Tabs , Text , Textarea } from 'soapbox/components/ui' ;
2022-11-26 11:25:48 -08:00
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' ;
2022-09-25 14:18:11 -07:00
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' } ,
2022-11-30 10:32:35 -08:00
confirm : { id : 'confirmations.delete.confirm' , defaultMessage : 'Delete' } ,
cancelEditing : { id : 'confirmations.cancel_editing.confirm' , defaultMessage : 'Cancel editing' } ,
2022-09-25 14:18:11 -07:00
} ) ;
interface IAccount {
2023-02-15 13:26:27 -08:00
eventId : string
id : string
participationMessage : string | null
2022-09-25 14:18:11 -07:00
}
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 {
2023-02-15 13:26:27 -08:00
onClose : ( type ? : string ) = > void
2022-09-25 14:18:11 -07:00
}
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 = ( ) = > {
2022-11-30 10:32:35 -08:00
dispatch ( ( dispatch , getState ) = > {
if ( checkEventComposeContent ( getState ( ) . compose_event ) ) {
dispatch ( openModal ( 'CONFIRM' , {
icon : require ( '@tabler/icons/trash.svg' ) ,
heading : id
? < FormattedMessage id = 'confirmations.cancel_event_editing.heading' defaultMessage = 'Cancel event editing' / >
: < FormattedMessage id = 'confirmations.delete_event.heading' defaultMessage = 'Delete event' / > ,
message : id
? < FormattedMessage id = 'confirmations.cancel_event_editing.message' defaultMessage = 'Are you sure you want to cancel editing this event? All changes will be lost.' / >
: < FormattedMessage id = 'confirmations.delete_event.message' defaultMessage = 'Are you sure you want to delete this event?' / > ,
confirm : intl.formatMessage ( messages . confirm ) ,
onConfirm : ( ) = > {
dispatch ( closeModal ( 'COMPOSE_EVENT' ) ) ;
dispatch ( cancelEventCompose ( ) ) ;
} ,
} ) ) ;
} else {
onClose ( 'COMPOSE_EVENT' ) ;
}
} ) ;
2022-09-25 14:18:11 -07:00
} ;
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' ) } / >
2023-02-01 14:13:42 -08:00
< Stack className = 'grow' >
2022-09-25 14:18:11 -07:00
< 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 } / > ;
} ;
2022-10-05 15:01:26 -07:00
let body ;
if ( tab === 'edit' ) body = (
< Form >
< FormGroup
labelText = { < FormattedMessage id = 'compose_event.fields.banner_label' defaultMessage = 'Event banner' / > }
>
2023-02-01 14:13:42 -08:00
< div className = 'dark:sm:shadow-inset relative flex h-24 items-center justify-center overflow-hidden rounded-lg bg-primary-100 text-primary-500 dark:bg-gray-800 dark:text-white sm:h-32 sm:shadow' >
2022-10-05 15:01:26 -07:00
{ 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 >
) }
{ ! id && (
< 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 >
) ;
else body = 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 / > ;
2022-09-25 14:18:11 -07:00
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 ( ) }
2022-10-05 15:01:26 -07:00
{ body }
2022-09-25 14:18:11 -07:00
< / Stack >
< / Modal >
) ;
} ;
export default ComposeEventModal ;