Update all non-major dependencies
[ProtonMail-WebClient.git] / applications / calendar / src / app / components / eventModal / EventForm.tsx
blob72878d0ace919614f6f4be5879630e24d66a8231
1 import type { HTMLAttributes } from 'react';
2 import { useRef } from 'react';
4 import { c } from 'ttag';
6 import {
7     MemoizedIconRow as IconRow,
8     Notifications,
9     NotificationsInDrawer,
10     useModalStateObject,
11 } from '@proton/components';
12 import CalendarSelectIcon from '@proton/components/components/calendarSelect/CalendarSelectIcon';
13 import { useLinkHandler } from '@proton/components/hooks/useLinkHandler';
14 import { useMailSettings } from '@proton/mail/mailSettings/hooks';
15 import type { VIEWS } from '@proton/shared/lib/calendar/constants';
16 import {
17     CALENDAR_INPUT_ID,
18     FREQUENCY,
19     FREQUENCY_INPUT_ID,
20     MAX_NOTIFICATIONS,
21     NOTIFICATION_INPUT_ID,
22 } from '@proton/shared/lib/calendar/constants';
23 import type { WeekStartsOn } from '@proton/shared/lib/date-fns-utc/interface';
24 import type { Address } from '@proton/shared/lib/interfaces';
25 import type {
26     EventModel,
27     EventModelErrors,
28     FrequencyModel,
29     NotificationModel,
30 } from '@proton/shared/lib/interfaces/calendar';
31 import { useFlag } from '@proton/unleash';
32 import clsx from '@proton/utils/clsx';
34 import { getCanChangeCalendarOfEvent } from '../../helpers/event';
35 import CreateEventCalendarSelect from './inputs/CreateEventCalendarSelect';
36 import CustomFrequencyModal from './inputs/CustomFrequencyModal';
37 import CustomFrequencySelector from './inputs/CustomFrequencySelector';
38 import EventColorSelect from './inputs/EventColorSelect';
39 import FrequencyInput from './inputs/FrequencyInput';
40 import { DateTimeRow } from './rows/DateTimeRow';
41 import { MiniDateTimeRows } from './rows/MiniDateTimeRows';
42 import { RowDescription } from './rows/RowDescription';
43 import { RowLocation } from './rows/RowLocation';
44 import { RowParticipants } from './rows/RowParticipants';
45 import { RowTitle } from './rows/RowTitle';
46 import { RowVideoConference } from './rows/RowVideoConference';
48 export interface EventFormProps {
49     isSubmitted: boolean;
50     displayWeekNumbers: boolean;
51     weekStartsOn: WeekStartsOn;
52     addresses: Address[];
53     errors: EventModelErrors;
54     model: EventModel;
55     setModel: (value: EventModel) => void;
56     tzid?: string;
57     canEditSharedEventData?: boolean;
58     isMinimal?: boolean;
59     isCreateEvent: boolean;
60     isInvitation: boolean;
61     setParticipantError?: (value: boolean) => void;
62     isOwnedCalendar?: boolean;
63     isCalendarWritable?: boolean;
64     isDrawerApp?: boolean;
65     isSmallViewport: boolean;
66     onDisplayBusySlots?: () => void;
67     view: VIEWS;
70 const EventForm = ({
71     isSubmitted,
72     displayWeekNumbers,
73     weekStartsOn,
74     addresses,
75     errors,
76     model,
77     setModel,
78     tzid,
79     isMinimal,
80     canEditSharedEventData = true,
81     isCreateEvent,
82     isInvitation,
83     setParticipantError,
84     isOwnedCalendar = true,
85     isCalendarWritable = true,
86     isDrawerApp,
87     isSmallViewport,
88     onDisplayBusySlots,
89     view,
90     ...props
91 }: EventFormProps & HTMLAttributes<HTMLDivElement>) => {
92     const isColorPerEventEnabled = useFlag('ColorPerEventWeb');
94     const [mailSettings] = useMailSettings();
95     const eventFormContentRef = useRef<HTMLDivElement>(null);
96     const { modal: linkModal } = useLinkHandler(eventFormContentRef, mailSettings);
98     const isSingleEdit = !!model.rest?.['recurrence-id'];
100     const isCustomFrequencySet = model.frequencyModel.type === FREQUENCY.CUSTOM;
101     const canChangeCalendar = getCanChangeCalendarOfEvent({
102         isCreateEvent,
103         isOwnedCalendar,
104         isCalendarWritable,
105         isSingleEdit,
106         isInvitation,
107         isAttendee: model.isAttendee,
108         isOrganizer: model.isOrganizer,
109     });
110     const notifications = model.isAllDay ? model.fullDayNotifications : model.partDayNotifications;
111     const canAddNotifications = notifications.length < MAX_NOTIFICATIONS;
112     const showNotifications = canAddNotifications || notifications.length;
114     const customModal = useModalStateObject();
115     const previousFrequencyRef = useRef<FrequencyModel>();
117     const dateRow = isMinimal ? (
118         <MiniDateTimeRows
119             model={model}
120             setModel={setModel}
121             endError={errors.end}
122             displayWeekNumbers={displayWeekNumbers}
123             weekStartsOn={weekStartsOn}
124         >
125             <div className="color-weak hover:color-norm">
126                 <FrequencyInput
127                     className="w-full relative inline-flex flex-nowrap gap-1 text-left sm:text-right items-center rounded"
128                     id={FREQUENCY_INPUT_ID}
129                     frequencyInputType="dropdown"
130                     data-testid="event-modal/frequency:select"
131                     value={model.frequencyModel.type}
132                     onChange={(type) => {
133                         if (type === FREQUENCY.CUSTOM) {
134                             customModal.openModal(true);
135                             previousFrequencyRef.current = model.frequencyModel;
136                         }
137                         setModel({
138                             ...model,
139                             frequencyModel: { ...model.frequencyModel, type },
140                             hasTouchedRrule: true,
141                         });
142                     }}
143                     title={c('Title').t`Select event frequency`}
144                 />
145             </div>
147             {customModal.render && (
148                 <CustomFrequencyModal
149                     modalProps={{
150                         ...customModal.modalProps,
151                         onClose: () => {
152                             customModal.modalProps.onClose();
153                             const frequencyModel = previousFrequencyRef.current;
154                             if (frequencyModel) {
155                                 setModel({ ...model, frequencyModel: model.frequencyModel });
156                             }
157                         },
158                     }}
159                     frequencyModel={model.frequencyModel}
160                     start={model.start}
161                     displayWeekNumbers={displayWeekNumbers}
162                     weekStartsOn={weekStartsOn}
163                     errors={errors}
164                     isSubmitted={isSubmitted}
165                     onChange={(frequencyModel) => {
166                         setModel({ ...model, frequencyModel, hasTouchedRrule: true });
167                         customModal.modalProps.onClose();
168                     }}
169                 />
170             )}
171         </MiniDateTimeRows>
172     ) : (
173         <DateTimeRow
174             model={model}
175             setModel={setModel}
176             endError={errors.end}
177             displayWeekNumbers={displayWeekNumbers}
178             weekStartsOn={weekStartsOn}
179             tzid={tzid!}
180         />
181     );
183     const frequencyRow = (
184         <IconRow icon="arrows-rotate" title={c('Label').t`Frequency`} id={FREQUENCY_INPUT_ID}>
185             <FrequencyInput
186                 className={clsx([isCustomFrequencySet && 'mb-2', 'w-full'])}
187                 id={FREQUENCY_INPUT_ID}
188                 data-testid="event-modal/frequency:select"
189                 value={model.frequencyModel.type}
190                 onChange={(type) =>
191                     setModel({
192                         ...model,
193                         frequencyModel: { ...model.frequencyModel, type },
194                         hasTouchedRrule: true,
195                     })
196                 }
197                 title={c('Title').t`Select event frequency`}
198             />
199             {isCustomFrequencySet && (
200                 <CustomFrequencySelector
201                     frequencyModel={model.frequencyModel}
202                     start={model.start}
203                     displayWeekNumbers={displayWeekNumbers}
204                     weekStartsOn={weekStartsOn}
205                     errors={errors}
206                     isSubmitted={isSubmitted}
207                     onChange={(frequencyModel) => setModel({ ...model, frequencyModel, hasTouchedRrule: true })}
208                 />
209             )}
210         </IconRow>
211     );
213     // temporary code; remove when proper design available
214     const allDayNotificationsRow = isDrawerApp ? (
215         <NotificationsInDrawer
216             id={NOTIFICATION_INPUT_ID}
217             hasType
218             {...{
219                 errors,
220                 canAdd: canAddNotifications,
221                 notifications: model.fullDayNotifications,
222                 defaultNotification: model.defaultFullDayNotification,
223                 onChange: (notifications: NotificationModel[]) => {
224                     setModel({
225                         ...model,
226                         hasDefaultNotifications: false,
227                         fullDayNotifications: notifications,
228                         hasFullDayDefaultNotifications: false,
229                     });
230                 },
231             }}
232         />
233     ) : (
234         <Notifications
235             id={NOTIFICATION_INPUT_ID}
236             hasType
237             {...{
238                 errors,
239                 canAdd: canAddNotifications,
240                 notifications: model.fullDayNotifications,
241                 defaultNotification: model.defaultFullDayNotification,
242                 onChange: (notifications: NotificationModel[]) => {
243                     setModel({
244                         ...model,
245                         hasDefaultNotifications: false,
246                         fullDayNotifications: notifications,
247                         hasFullDayDefaultNotifications: false,
248                     });
249                 },
250             }}
251         />
252     );
253     const partDayNotificationsRow = isDrawerApp ? (
254         <NotificationsInDrawer
255             id={NOTIFICATION_INPUT_ID}
256             hasType
257             {...{
258                 errors,
259                 canAdd: canAddNotifications,
260                 notifications: model.partDayNotifications,
261                 defaultNotification: model.defaultPartDayNotification,
262                 onChange: (notifications: NotificationModel[]) => {
263                     setModel({
264                         ...model,
265                         hasDefaultNotifications: false,
266                         partDayNotifications: notifications,
267                         hasPartDayDefaultNotifications: false,
268                     });
269                 },
270             }}
271         />
272     ) : (
273         <Notifications
274             id={NOTIFICATION_INPUT_ID}
275             hasType
276             {...{
277                 errors,
278                 canAdd: canAddNotifications,
279                 notifications: model.partDayNotifications,
280                 defaultNotification: model.defaultPartDayNotification,
281                 onChange: (notifications: NotificationModel[]) => {
282                     setModel({
283                         ...model,
284                         hasDefaultNotifications: false,
285                         partDayNotifications: notifications,
286                         hasPartDayDefaultNotifications: false,
287                     });
288                 },
289             }}
290         />
291     );
293     const notificationsRow = (
294         <IconRow
295             id={NOTIFICATION_INPUT_ID}
296             icon="bell"
297             title={c('Label').t`Notifications`}
298             labelClassName={isDrawerApp ? '' : 'pb-2 mt-2 md:mt-0'}
299         >
300             {model.isAllDay ? allDayNotificationsRow : partDayNotificationsRow}
301         </IconRow>
302     );
304     const getCalendarIcon = () => {
305         if (isColorPerEventEnabled) {
306             return 'calendar-grid';
307         }
308         if (model.calendars.length === 1) {
309             return <CalendarSelectIcon className="mt-1" color={model.calendars[0].color} />;
310         }
312         if (!canChangeCalendar) {
313             return <CalendarSelectIcon className="mt-1" color={model.calendar.color} />;
314         }
316         return 'calendar-grid';
317     };
319     const calendarRow = (
320         <IconRow
321             icon={getCalendarIcon()}
322             title={c('Label').t`Calendar`}
323             id={CALENDAR_INPUT_ID}
324             className="flex flex-nowrap flex-1 grow"
325             containerClassName={clsx(isMinimal && !isColorPerEventEnabled && 'eventpopover-calendar-select')}
326         >
327             <CreateEventCalendarSelect
328                 id={CALENDAR_INPUT_ID}
329                 className="w-full flex-1"
330                 title={c('Title').t`Select which calendar to add this event to`}
331                 frozen={!canChangeCalendar}
332                 model={model}
333                 setModel={setModel}
334                 isCreateEvent={isCreateEvent}
335                 isColorPerEventEnabled={isColorPerEventEnabled}
336             />
337             {isColorPerEventEnabled && (
338                 <EventColorSelect
339                     model={model}
340                     setModel={setModel}
341                     isSmallViewport={isSmallViewport}
342                     isDrawerApp={isDrawerApp}
343                 />
344             )}
345         </IconRow>
346     );
348     return (
349         <div className="mt-2" {...props} ref={eventFormContentRef}>
350             <RowTitle canEditSharedEventData={canEditSharedEventData} model={model} setModel={setModel} />
351             {canEditSharedEventData && dateRow}
352             {canEditSharedEventData && !isMinimal && frequencyRow}
353             {model.calendars.length > 0 && calendarRow}
354             <RowParticipants
355                 canEditSharedEventData={canEditSharedEventData}
356                 isCreateEvent={isCreateEvent}
357                 model={model}
358                 setModel={setModel}
359                 setParticipantError={setParticipantError}
360                 addresses={addresses}
361                 isMinimal={isMinimal}
362                 onDisplayBusySlots={onDisplayBusySlots}
363                 view={view}
364             />
365             <RowLocation canEditSharedEventData={canEditSharedEventData} model={model} setModel={setModel} />
366             {canEditSharedEventData && <RowVideoConference model={model} setModel={setModel} />}
367             {!isMinimal && showNotifications && notificationsRow}
368             <RowDescription canEditSharedEventData={canEditSharedEventData} model={model} setModel={setModel} />
369             {linkModal}
370         </div>
371     );
374 export default EventForm;