1 import { useMemo, useState } from 'react';
3 import { addDays, addHours, fromUnixTime, getUnixTime, startOfDay } from 'date-fns';
4 import { c } from 'ttag';
6 import { AutoReplyDuration, DAY_IN_SECONDS, HOUR_IN_SECONDS, MINUTE_IN_SECONDS } from '@proton/shared/lib/constants';
8 convertUTCDateTimeToZone,
9 convertZonedDateTimeToUTC,
16 } from '@proton/shared/lib/date/timezone';
17 import type { AutoResponder as tsAutoResponder } from '@proton/shared/lib/interfaces';
19 import { getDurationOptions, getMatchingTimezone } from '../utils';
20 import type { AutoReplyFormDate, AutoReplyFormModel } from './interfaces';
22 const getDefaultFixedTimes = () => ({
23 StartTime: getUnixTime(new Date()),
24 EndTime: getUnixTime(addHours(addDays(new Date(), 7), 2)),
27 export const getDefaultAutoResponder = (AutoResponder?: tsAutoResponder) => {
28 const timezones = getTimeZoneOptions();
32 // Comment copied from anglar:
33 // Not translated: it's not editable, foreign people doing international business wouldn't want it to be translated
34 // if we make it editable we can translate it again.
36 Message: AutoResponder?.Message || c('Autoresponse').t`I'm out of the office with limited access to my email.`,
37 Repeat: AutoReplyDuration.FIXED,
40 Zone: getMatchingTimezone(getTimezone(), timezones).value,
41 ...getDefaultFixedTimes(),
45 const toDateTimes = (unixTimestamp: number, timezone: string, repeat: AutoReplyDuration) => {
46 if (repeat === AutoReplyDuration.PERMANENT) {
50 if (repeat === AutoReplyDuration.FIXED) {
51 const zonedTime = convertUTCDateTimeToZone(fromUTCDate(fromUnixTime(unixTimestamp)), timezone);
53 date: startOfDay(toLocalDate(zonedTime)),
54 time: new Date(2000, 0, 1, zonedTime.hours, zonedTime.minutes),
58 const day = Math.floor(unixTimestamp / DAY_IN_SECONDS);
59 const secondsInDay = unixTimestamp % DAY_IN_SECONDS;
60 const hours = Math.floor(secondsInDay / HOUR_IN_SECONDS);
61 const minutes = Math.floor((secondsInDay - hours * HOUR_IN_SECONDS) / 60);
63 const localTime = new Date(2000, 0, 1, hours, minutes);
65 if (repeat === AutoReplyDuration.DAILY) {
71 if (repeat === AutoReplyDuration.MONTHLY) {
78 if (repeat === AutoReplyDuration.WEEKLY) {
86 export const getMatchingValues = ({ Zone, Repeat }: tsAutoResponder) => {
87 const duration = getDurationOptions().find(({ value }) => value === Repeat);
88 const timezones = getTimeZoneOptions();
89 const matchingTimezone = getMatchingTimezone(Zone, timezones) || getMatchingTimezone(getTimezone(), timezones);
92 timezone: matchingTimezone,
97 export const toModel = (
98 { Message, StartTime, EndTime, DaysSelected, Subject, IsEnabled }: tsAutoResponder,
99 { timezone, duration }: { timezone: string; duration: AutoReplyDuration }
100 ): AutoReplyFormModel => {
103 daysOfWeek: DaysSelected,
108 start: toDateTimes(StartTime, timezone, duration) || {},
109 end: toDateTimes(EndTime, timezone, duration) || {},
114 { date = new Date(), time = new Date(), day = new Date().getDay() }: AutoReplyFormDate,
116 repeat: AutoReplyDuration
118 if (repeat === AutoReplyDuration.PERMANENT) {
122 const utcUnixTime = time.getHours() * HOUR_IN_SECONDS + time.getMinutes() * MINUTE_IN_SECONDS;
125 case AutoReplyDuration.FIXED:
128 convertZonedDateTimeToUTC(
130 ...fromLocalDate(date),
131 hours: time.getHours(),
132 minutes: time.getMinutes(),
138 case AutoReplyDuration.DAILY:
140 case AutoReplyDuration.WEEKLY:
141 return day * DAY_IN_SECONDS + utcUnixTime;
142 case AutoReplyDuration.MONTHLY:
143 return day * DAY_IN_SECONDS + utcUnixTime;
149 const toAutoResponder = ({
157 }: AutoReplyFormModel): tsAutoResponder => ({
160 DaysSelected: daysOfWeek,
164 StartTime: toUnixTime(start, timezone, duration),
165 EndTime: toUnixTime(end, timezone, duration),
168 type UpdateFunction = (
169 value: number | number[] | string | boolean | AutoReplyFormDate,
170 extra?: Partial<AutoReplyFormModel>
173 const useAutoReplyForm = (AutoResponder: tsAutoResponder) => {
174 const matches = useMemo(() => getMatchingValues(AutoResponder), [AutoResponder]);
176 const getInitialModel = () => {
177 return toModel(AutoResponder.IsEnabled ? AutoResponder : getDefaultAutoResponder(AutoResponder), {
178 timezone: matches.timezone?.value,
179 duration: matches.duration?.value || AutoReplyDuration.FIXED,
183 const [model, setModel] = useState<AutoReplyFormModel>(getInitialModel());
185 const updateModel = (key: string): UpdateFunction => {
186 if (key === 'duration') {
187 // When changing the duration, reset the model.
194 * When switching to fixed repeat duration, reset the start time and end time
195 * to avoid having the date be 1/1/1970
197 ...(value === AutoReplyDuration.FIXED && AutoResponder.Repeat !== AutoReplyDuration.FIXED
199 ...getDefaultFixedTimes(),
204 timezone: matches.timezone?.value,
205 duration: value as AutoReplyDuration,
211 const [a, b] = key.split('.');
214 const genericSetter: UpdateFunction = (value, extra?: Partial<AutoReplyFormModel>) =>
215 setModel((prev: any) => ({
226 // If start date, end date, start time and end time are set, it means that we are using the Fixed duration autoreply type
227 if (model.start.date && model.end.date && model.start.time && model.end.time) {
228 const valueDate = new Date(value as string);
231 In Fixed duration, we don't want to have end date before start date.
232 For each cases we set the end date if the change we are making makes the end date before the start date
234 if (key === 'start.date') {
236 valueDate.getTime() === model.end.date.getTime() &&
237 model.start.time.getTime() > model.end.time.getTime()
239 return genericSetter(value, { end: { date: model.end.date, time: model.start.time } });
241 if (valueDate.getTime() > model.end.date.getTime()) {
242 return genericSetter(value, { end: { date: valueDate, time: model.start.time } });
247 key === 'start.time' &&
248 model.start.date.getTime() === model.end.date.getTime() &&
249 valueDate.getTime() > model.end.time.getTime()
251 return genericSetter(value, { end: { date: model.end.date, time: valueDate } });
254 if (key === 'end.date') {
256 valueDate.getTime() === model.start.date.getTime() &&
257 model.end.time.getTime() < model.start.time.getTime()
259 return genericSetter(value, { end: { date: model.start.date, time: model.start.time } });
261 // should not be possible with the dateInput component
262 if (valueDate.getTime() < model.start.date.getTime()) {
263 if (model.end.time.getTime() > model.start.time.getTime()) {
264 return genericSetter(value, { end: { date: model.start.date, time: model.start.time } });
266 return genericSetter(value, { end: { date: model.start.date, time: model.end.time } });
271 key === 'end.time' &&
272 model.start.date.getTime() === model.end.date.getTime() &&
273 valueDate.getTime() < model.start.time.getTime()
275 return genericSetter(value, { end: { date: model.end.date, time: model.start.time } });
279 return genericSetter(value);
290 export default useAutoReplyForm;