1 import type { HTMLAttributes } from 'react';
2 import { useRef } from 'react';
4 import { c } from 'ttag';
7 MemoizedIconRow as IconRow,
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';
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';
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 {
50 displayWeekNumbers: boolean;
51 weekStartsOn: WeekStartsOn;
53 errors: EventModelErrors;
55 setModel: (value: EventModel) => void;
57 canEditSharedEventData?: 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;
80 canEditSharedEventData = true,
84 isOwnedCalendar = true,
85 isCalendarWritable = true,
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({
107 isAttendee: model.isAttendee,
108 isOrganizer: model.isOrganizer,
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 ? (
121 endError={errors.end}
122 displayWeekNumbers={displayWeekNumbers}
123 weekStartsOn={weekStartsOn}
125 <div className="color-weak hover:color-norm">
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;
139 frequencyModel: { ...model.frequencyModel, type },
140 hasTouchedRrule: true,
143 title={c('Title').t`Select event frequency`}
147 {customModal.render && (
148 <CustomFrequencyModal
150 ...customModal.modalProps,
152 customModal.modalProps.onClose();
153 const frequencyModel = previousFrequencyRef.current;
154 if (frequencyModel) {
155 setModel({ ...model, frequencyModel: model.frequencyModel });
159 frequencyModel={model.frequencyModel}
161 displayWeekNumbers={displayWeekNumbers}
162 weekStartsOn={weekStartsOn}
164 isSubmitted={isSubmitted}
165 onChange={(frequencyModel) => {
166 setModel({ ...model, frequencyModel, hasTouchedRrule: true });
167 customModal.modalProps.onClose();
176 endError={errors.end}
177 displayWeekNumbers={displayWeekNumbers}
178 weekStartsOn={weekStartsOn}
183 const frequencyRow = (
184 <IconRow icon="arrows-rotate" title={c('Label').t`Frequency`} id={FREQUENCY_INPUT_ID}>
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}
193 frequencyModel: { ...model.frequencyModel, type },
194 hasTouchedRrule: true,
197 title={c('Title').t`Select event frequency`}
199 {isCustomFrequencySet && (
200 <CustomFrequencySelector
201 frequencyModel={model.frequencyModel}
203 displayWeekNumbers={displayWeekNumbers}
204 weekStartsOn={weekStartsOn}
206 isSubmitted={isSubmitted}
207 onChange={(frequencyModel) => setModel({ ...model, frequencyModel, hasTouchedRrule: true })}
213 // temporary code; remove when proper design available
214 const allDayNotificationsRow = isDrawerApp ? (
215 <NotificationsInDrawer
216 id={NOTIFICATION_INPUT_ID}
220 canAdd: canAddNotifications,
221 notifications: model.fullDayNotifications,
222 defaultNotification: model.defaultFullDayNotification,
223 onChange: (notifications: NotificationModel[]) => {
226 hasDefaultNotifications: false,
227 fullDayNotifications: notifications,
228 hasFullDayDefaultNotifications: false,
235 id={NOTIFICATION_INPUT_ID}
239 canAdd: canAddNotifications,
240 notifications: model.fullDayNotifications,
241 defaultNotification: model.defaultFullDayNotification,
242 onChange: (notifications: NotificationModel[]) => {
245 hasDefaultNotifications: false,
246 fullDayNotifications: notifications,
247 hasFullDayDefaultNotifications: false,
253 const partDayNotificationsRow = isDrawerApp ? (
254 <NotificationsInDrawer
255 id={NOTIFICATION_INPUT_ID}
259 canAdd: canAddNotifications,
260 notifications: model.partDayNotifications,
261 defaultNotification: model.defaultPartDayNotification,
262 onChange: (notifications: NotificationModel[]) => {
265 hasDefaultNotifications: false,
266 partDayNotifications: notifications,
267 hasPartDayDefaultNotifications: false,
274 id={NOTIFICATION_INPUT_ID}
278 canAdd: canAddNotifications,
279 notifications: model.partDayNotifications,
280 defaultNotification: model.defaultPartDayNotification,
281 onChange: (notifications: NotificationModel[]) => {
284 hasDefaultNotifications: false,
285 partDayNotifications: notifications,
286 hasPartDayDefaultNotifications: false,
293 const notificationsRow = (
295 id={NOTIFICATION_INPUT_ID}
297 title={c('Label').t`Notifications`}
298 labelClassName={isDrawerApp ? '' : 'pb-2 mt-2 md:mt-0'}
300 {model.isAllDay ? allDayNotificationsRow : partDayNotificationsRow}
304 const getCalendarIcon = () => {
305 if (isColorPerEventEnabled) {
306 return 'calendar-grid';
308 if (model.calendars.length === 1) {
309 return <CalendarSelectIcon className="mt-1" color={model.calendars[0].color} />;
312 if (!canChangeCalendar) {
313 return <CalendarSelectIcon className="mt-1" color={model.calendar.color} />;
316 return 'calendar-grid';
319 const calendarRow = (
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')}
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}
334 isCreateEvent={isCreateEvent}
335 isColorPerEventEnabled={isColorPerEventEnabled}
337 {isColorPerEventEnabled && (
341 isSmallViewport={isSmallViewport}
342 isDrawerApp={isDrawerApp}
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}
355 canEditSharedEventData={canEditSharedEventData}
356 isCreateEvent={isCreateEvent}
359 setParticipantError={setParticipantError}
360 addresses={addresses}
361 isMinimal={isMinimal}
362 onDisplayBusySlots={onDisplayBusySlots}
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} />
374 export default EventForm;