Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / containers / autoReply / AutoReplyForm / useAutoReplyForm.ts
blob87c0fb2ade39925714d34e2fc361d31350355bd7
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';
7 import {
8     convertUTCDateTimeToZone,
9     convertZonedDateTimeToUTC,
10     fromLocalDate,
11     fromUTCDate,
12     getTimeZoneOptions,
13     getTimezone,
14     toLocalDate,
15     toUTCDate,
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)),
25 });
27 export const getDefaultAutoResponder = (AutoResponder?: tsAutoResponder) => {
28     const timezones = getTimeZoneOptions();
30     return {
31         ...AutoResponder,
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.
35         Subject: 'Auto',
36         Message: AutoResponder?.Message || c('Autoresponse').t`I'm out of the office with limited access to my email.`,
37         Repeat: AutoReplyDuration.FIXED,
38         DaysSelected: [],
39         IsEnabled: true,
40         Zone: getMatchingTimezone(getTimezone(), timezones).value,
41         ...getDefaultFixedTimes(),
42     };
45 const toDateTimes = (unixTimestamp: number, timezone: string, repeat: AutoReplyDuration) => {
46     if (repeat === AutoReplyDuration.PERMANENT) {
47         return {};
48     }
50     if (repeat === AutoReplyDuration.FIXED) {
51         const zonedTime = convertUTCDateTimeToZone(fromUTCDate(fromUnixTime(unixTimestamp)), timezone);
52         return {
53             date: startOfDay(toLocalDate(zonedTime)),
54             time: new Date(2000, 0, 1, zonedTime.hours, zonedTime.minutes),
55         };
56     }
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) {
66         return {
67             time: localTime,
68         };
69     }
71     if (repeat === AutoReplyDuration.MONTHLY) {
72         return {
73             day: day % 31,
74             time: localTime,
75         };
76     }
78     if (repeat === AutoReplyDuration.WEEKLY) {
79         return {
80             day: day % 7,
81             time: localTime,
82         };
83     }
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);
91     return {
92         timezone: matchingTimezone,
93         duration,
94     };
97 export const toModel = (
98     { Message, StartTime, EndTime, DaysSelected, Subject, IsEnabled }: tsAutoResponder,
99     { timezone, duration }: { timezone: string; duration: AutoReplyDuration }
100 ): AutoReplyFormModel => {
101     return {
102         message: Message,
103         daysOfWeek: DaysSelected,
104         subject: Subject,
105         enabled: IsEnabled,
106         duration,
107         timezone,
108         start: toDateTimes(StartTime, timezone, duration) || {},
109         end: toDateTimes(EndTime, timezone, duration) || {},
110     };
113 const toUnixTime = (
114     { date = new Date(), time = new Date(), day = new Date().getDay() }: AutoReplyFormDate,
115     timezone: string,
116     repeat: AutoReplyDuration
117 ) => {
118     if (repeat === AutoReplyDuration.PERMANENT) {
119         return 0;
120     }
122     const utcUnixTime = time.getHours() * HOUR_IN_SECONDS + time.getMinutes() * MINUTE_IN_SECONDS;
124     switch (repeat) {
125         case AutoReplyDuration.FIXED:
126             return getUnixTime(
127                 toUTCDate(
128                     convertZonedDateTimeToUTC(
129                         {
130                             ...fromLocalDate(date),
131                             hours: time.getHours(),
132                             minutes: time.getMinutes(),
133                         },
134                         timezone
135                     )
136                 )
137             );
138         case AutoReplyDuration.DAILY:
139             return utcUnixTime;
140         case AutoReplyDuration.WEEKLY:
141             return day * DAY_IN_SECONDS + utcUnixTime;
142         case AutoReplyDuration.MONTHLY:
143             return day * DAY_IN_SECONDS + utcUnixTime;
144         default:
145             return utcUnixTime;
146     }
149 const toAutoResponder = ({
150     message,
151     duration,
152     daysOfWeek,
153     timezone,
154     subject,
155     start,
156     end,
157 }: AutoReplyFormModel): tsAutoResponder => ({
158     Message: message,
159     Repeat: duration,
160     DaysSelected: daysOfWeek,
161     Zone: timezone,
162     Subject: subject,
163     IsEnabled: true,
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>
171 ) => void;
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,
180         });
181     };
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.
188             return (value) =>
189                 setModel({
190                     ...toModel(
191                         {
192                             ...AutoResponder,
193                             /**
194                              * When switching to fixed repeat duration, reset the start time and end time
195                              * to avoid having the date be 1/1/1970
196                              */
197                             ...(value === AutoReplyDuration.FIXED && AutoResponder.Repeat !== AutoReplyDuration.FIXED
198                                 ? {
199                                       ...getDefaultFixedTimes(),
200                                   }
201                                 : undefined),
202                         },
203                         {
204                             timezone: matches.timezone?.value,
205                             duration: value as AutoReplyDuration,
206                         }
207                     ),
208                 });
209         }
211         const [a, b] = key.split('.');
213         return (value) => {
214             const genericSetter: UpdateFunction = (value, extra?: Partial<AutoReplyFormModel>) =>
215                 setModel((prev: any) => ({
216                     ...prev,
217                     [a]: !b
218                         ? value
219                         : {
220                               ...prev[a],
221                               [b]: value,
222                           },
223                     ...extra,
224                 }));
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);
230                 /*
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
233                  */
234                 if (key === 'start.date') {
235                     if (
236                         valueDate.getTime() === model.end.date.getTime() &&
237                         model.start.time.getTime() > model.end.time.getTime()
238                     ) {
239                         return genericSetter(value, { end: { date: model.end.date, time: model.start.time } });
240                     }
241                     if (valueDate.getTime() > model.end.date.getTime()) {
242                         return genericSetter(value, { end: { date: valueDate, time: model.start.time } });
243                     }
244                 }
246                 if (
247                     key === 'start.time' &&
248                     model.start.date.getTime() === model.end.date.getTime() &&
249                     valueDate.getTime() > model.end.time.getTime()
250                 ) {
251                     return genericSetter(value, { end: { date: model.end.date, time: valueDate } });
252                 }
254                 if (key === 'end.date') {
255                     if (
256                         valueDate.getTime() === model.start.date.getTime() &&
257                         model.end.time.getTime() < model.start.time.getTime()
258                     ) {
259                         return genericSetter(value, { end: { date: model.start.date, time: model.start.time } });
260                     }
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 } });
265                         }
266                         return genericSetter(value, { end: { date: model.start.date, time: model.end.time } });
267                     }
268                 }
270                 if (
271                     key === 'end.time' &&
272                     model.start.date.getTime() === model.end.date.getTime() &&
273                     valueDate.getTime() < model.start.time.getTime()
274                 ) {
275                     return genericSetter(value, { end: { date: model.end.date, time: model.start.time } });
276                 }
277             }
279             return genericSetter(value);
280         };
281     };
283     return {
284         model,
285         toAutoResponder,
286         updateModel,
287     };
290 export default useAutoReplyForm;