1 import type { PayloadAction } from '@reduxjs/toolkit';
2 import { createSlice } from '@reduxjs/toolkit';
4 import type { VIEWS } from '@proton/shared/lib/calendar/constants';
5 import { canonicalizeEmailByGuess } from '@proton/shared/lib/helpers/email';
6 import type { GetBusySlotsResponse } from '@proton/shared/lib/interfaces/calendar';
7 import diff from '@proton/utils/diff';
9 import type { CalendarViewBusyEvent } from '../../containers/calendar/interface';
11 export interface AttendeeBusySlot extends CalendarViewBusyEvent {}
14 export type BusySlotsEmail = string;
15 /** Attendee visibility */
16 export type BusySlotsVisibility = 'visible' | 'hidden';
18 * Attendee fetchStatus
19 * - loading: request is in progress
20 * - success: request has been successful
21 * - error: request failed.
23 export type BusySlotFetchStatus = 'loading' | 'success' | 'error';
25 export type BusySlot = Exclude<GetBusySlotsResponse['BusySchedule']['BusyTimeSlots'], null>[number] & {
29 export interface BusySlotsAttendeeFetchStatusSuccessActionPayload {
30 email: BusySlotsEmail;
31 busySlots: BusySlot[];
32 isDataAccessible: boolean;
33 visibility: BusySlotsVisibility;
36 export interface BusySlotsState {
37 /** Display or not on calendar grid */
38 displayOnGrid: boolean;
39 /** Metadata of the event */
41 /** The currently selected TZ in the interface */
43 /** The current view when opening event form */
45 /** Start date of the calendar view in UTC unix timestamp */
46 viewStartDate: number;
47 /** End date of the calendar view in UTC unix timestamp */
50 /** List of event attendees */
51 attendees: BusySlotsEmail[];
53 * Selected color for the attendee.
54 * Each attendee will be assigned a different color from the actual
55 * calendar colors the user has and the actual attendees colors assigned
57 attendeeColor: Record<BusySlotsEmail, string>;
58 /** Is attendee email hidden */
59 attendeeVisibility: Record<BusySlotsEmail, BusySlotsVisibility>;
60 /** Are attendee busy slots accessible ? */
61 attendeeDataAccessible: Record<BusySlotsEmail, boolean>;
63 * Allows to know status of getBusySlots request for each user
64 * - loading: request is in progress
65 * - success: request has been successful
66 * - error: request failed.
68 attendeeFetchStatus: Record<BusySlotsEmail, BusySlotFetchStatus>;
69 /** Busy time slots for each attendees */
70 attendeeBusySlots: Record<BusySlotsEmail, BusySlot[]>;
71 /** Highlighted attendee in the ui */
72 attendeeHighlight: BusySlotsEmail | undefined;
75 export const busySlotsSliceName = 'calendarBusySlots';
77 const initialState: BusySlotsState = {
82 attendeeVisibility: {},
83 attendeeDataAccessible: {},
84 attendeeBusySlots: {},
85 attendeeFetchStatus: {},
86 attendeeHighlight: undefined,
89 const busySlotsSlice = createSlice({
90 name: busySlotsSliceName,
94 return { ...initialState, metadata: state.metadata };
96 setAttendees: (state, { payload }: PayloadAction<BusySlotsEmail[]>) => {
97 if (diff(payload, state.attendees).length > 0) {
98 state.attendees = payload.map((email) => canonicalizeEmailByGuess(email));
101 setMetadata: (state, { payload }: PayloadAction<Required<BusySlotsState>['metadata']>) => {
102 state.metadata = payload;
104 removeAttendee: (state, action: PayloadAction<BusySlotsEmail>) => {
105 const emailCanonicalized = canonicalizeEmailByGuess(action.payload);
106 state.attendees = state.attendees.filter((email) => email !== emailCanonicalized);
108 setAttendeeVisibility: (state, action: PayloadAction<{ email: BusySlotsEmail; visible: boolean }>) => {
109 const emailCanonicalized = canonicalizeEmailByGuess(action.payload.email);
111 if (state.attendeeDataAccessible[emailCanonicalized] === false) {
112 state.attendeeVisibility[emailCanonicalized] = 'hidden';
115 state.attendeeVisibility[emailCanonicalized] = action.payload.visible ? 'visible' : 'hidden';
117 setAttendeeFetchStatusLoadingAndColor: (
119 action: PayloadAction<{ email: BusySlotsEmail; color: string }[]>
121 for (const { email, color } of action.payload) {
122 const emailCanonicalized = canonicalizeEmailByGuess(email);
123 state.attendeeFetchStatus[emailCanonicalized] = 'loading';
124 state.attendeeVisibility[email] = 'hidden';
125 state.attendeeColor[emailCanonicalized] = color;
128 setFetchStatusesSuccess: (
130 { payload }: PayloadAction<BusySlotsAttendeeFetchStatusSuccessActionPayload[]>
132 for (const { email, busySlots, isDataAccessible, visibility } of payload) {
133 const emailCanonicalized = canonicalizeEmailByGuess(email);
134 state.attendeeBusySlots[emailCanonicalized] = busySlots;
135 state.attendeeDataAccessible[emailCanonicalized] = isDataAccessible;
136 state.attendeeFetchStatus[emailCanonicalized] = 'success';
137 state.attendeeVisibility[emailCanonicalized] = visibility;
140 setFetchStatusesFail: (state, action: PayloadAction<BusySlotsEmail[]>) => {
141 for (const email of action.payload) {
142 const emailCanonicalized = canonicalizeEmailByGuess(email);
143 state.attendeeFetchStatus[emailCanonicalized] = 'error';
144 state.attendeeVisibility[emailCanonicalized] = 'hidden';
147 setHighlightedAttendee: (state, action: PayloadAction<BusySlotsEmail | undefined>) => {
148 state.attendeeHighlight = action.payload ? canonicalizeEmailByGuess(action.payload) : undefined;
150 setDisplay: (state, action: PayloadAction<boolean>) => {
151 state.displayOnGrid = action.payload;
156 export const busySlotsActions = busySlotsSlice.actions;
157 export const busySlotsReducer = { [busySlotsSliceName]: busySlotsSlice.reducer };