diff --git a/client/src/campaigns/Status.js b/client/src/campaigns/Status.js index a348daa0..4eab8df2 100644 --- a/client/src/campaigns/Status.js +++ b/client/src/campaigns/Status.js @@ -13,11 +13,11 @@ import axios from "../lib/axios"; import {getPublicUrl, getUrl} from "../lib/urls"; import interoperableErrors from '../../../shared/interoperable-errors'; import {CampaignStatus, CampaignType} from "../../../shared/campaigns"; -import moment from 'moment'; +import moment from 'moment-timezone'; import campaignsStyles from "./styles.scss"; import {withComponentMixins} from "../lib/decorator-helpers"; import {TestSendModalDialog, TestSendModalDialogMode} from "./TestSendModalDialog"; - +import styles from "../lib/styles.scss"; @withComponentMixins([ withTranslation, @@ -114,6 +114,8 @@ class SendControls extends Component { this.initForm({ leaveConfirmation: false }); + + this.timezoneOptions = moment.tz.names().map(x => [x]); } static propTypes = { @@ -126,6 +128,7 @@ class SendControls extends Component { state.setIn(['date', 'error'], null); state.setIn(['time', 'error'], null); + state.setIn(['timezone', 'error'], null); if (state.getIn(['sendLater', 'value'])) { const dateValue = state.getIn(['date', 'value']).trim(); @@ -141,36 +144,54 @@ class SendControls extends Component { } else if (!moment(timeValue, 'HH:mm', true).isValid()) { state.setIn(['time', 'error'], t('timeIsInvalid')); } + + const timezone = state.getIn(['timezone', 'value']); + if (!timezone) { + state.setIn(['timezone', 'error'], t('Timezone must be selected')); + } } } - componentDidMount() { + populateSendLater() { const entity = this.props.entity; if (entity.scheduled) { - const date = moment.utc(entity.scheduled); + const timezone = entity.data.timezone || moment.tz.guess(); + const date = moment.tz(entity.scheduled, timezone); this.populateFormValues({ sendLater: true, date: date.format('YYYY-MM-DD'), - time: date.format('HH:mm') + time: date.format('HH:mm'), + timezone }); } else { this.populateFormValues({ sendLater: false, date: '', - time: '' + time: '', + timezone: moment.tz.guess() }); } } + componentDidMount() { + this.populateSendLater(); + } + + componentDidUpdate(prevProps) { + if (prevProps.entity.scheduled !== this.props.entity.scheduled) { + this.populateSendLater(); + } + } + async refreshEntity() { await this.props.refreshEntity(); } - async postAndMaskStateError(url) { + async postAndMaskStateError(url, data) { try { - await axios.post(getUrl(url)); + await axios.post(getUrl(url), data); } catch (err) { if (err instanceof interoperableErrors.InvalidStateError) { // Just mask the fact that it's not possible to start anything and refresh instead. @@ -183,17 +204,12 @@ class SendControls extends Component { async scheduleAsync() { if (this.isFormWithoutErrors()) { const data = this.getFormValues(); - const date = moment(data.date, 'YYYY-MM-DD'); - const time = moment(data.time, 'HH:mm'); + const dateTime = moment.tz(data.date + ' ' + data.time, 'YYYY-MM-DD HH:mm', data.timezone); - date.hour(time.hour()); - date.minute(time.minute()); - date.second(0); - date.millisecond(0); - date.utcOffset(0, true); // TODO, process offset from user settings - - - await this.postAndMaskStateError(`rest/campaign-start-at/${this.props.entity.id}/${date.valueOf()}`); + await this.postAndMaskStateError(`rest/campaign-start-at/${this.props.entity.id}`, { + startAt: dateTime.valueOf(), + timezone: data.timezone + }); } else { this.showFormValidation(); @@ -219,7 +235,17 @@ class SendControls extends Component { t('doYouWantToLaunchTheCampaign?'), async () => { await this.startAsync(); - await this.refreshEntity(); + } + ); + } + + async confirmSchedule() { + const t = this.props.t; + this.actionDialog( + t('confirmLaunch'), + t('Do you want to schedule the campaign for launch?'), + async () => { + await this.scheduleAsync(); } ); } @@ -305,10 +331,30 @@ class SendControls extends Component { const subscrInfo = entity.subscriptionsToSend === undefined ? '' : ` (${entity.subscriptionsToSend} ${t('subscribers-1')})`; + const timezoneColumns = [ + { data: 0, title: t('Timezone') } + ]; + + const dateValue = (this.getFormValue('date') || '').trim(); + const timeValue = (this.getFormValue('time') || '').trim(); + const timezone = this.getFormValue('timezone'); + + let dateTimeHelp = t('Select date, time and a timezone to display the date and time with offset'); + let dateTimeAlert = null; + if (moment(dateValue, 'YYYY-MM-DD', true).isValid() && moment(timeValue, 'HH:mm', true).isValid() && timezone) { + const dateTime = moment.tz(dateValue + ' ' + timeValue, 'YYYY-MM-DD HH:mm', timezone); + + dateTimeHelp = dateTime.toString(); + if (!moment().isBefore(dateTime)) { + dateTimeAlert =
{t('Scheduled date/time seems to be in the past. If you schedule the send, campaign will be sent immediately.')}
; + } + } + + return (
{dialogs} - {entity.scheduled ? t('campaignIsScheduledForDelivery') : t('campaignIsReadyToBeSentOut')} + {entity.status === CampaignStatus.SCHEDULED ? t('campaignIsScheduledForDelivery') : t('campaignIsReadyToBeSentOut')}
@@ -317,16 +363,20 @@ class SendControls extends Component {
- {/* TODO: Timezone selector */} + + {dateTimeAlert && {dateTimeAlert}}
}
{this.getFormValue('sendLater') ? -