1 import { createSlice } from '@reduxjs/toolkit';
3 import { type ModelState, getInitialModelState, serverEvent } from '@proton/account';
4 import type { ProtonThunkArguments } from '@proton/redux-shared-store-types';
5 import { createAsyncModelThunk, handleAsyncModel, previousSelector } from '@proton/redux-utilities';
6 import { queryContacts } from '@proton/shared/lib/api/contacts';
7 import queryPages from '@proton/shared/lib/api/helpers/queryPages';
8 import { CONTACTS_LIMIT, CONTACTS_REQUESTS_PER_SECOND } from '@proton/shared/lib/constants';
9 import { EVENT_ERRORS } from '@proton/shared/lib/errors';
10 import { hasBit } from '@proton/shared/lib/helpers/bitset';
11 import updateCollection from '@proton/shared/lib/helpers/updateCollection';
12 import { type Api } from '@proton/shared/lib/interfaces';
13 import { type Contact } from '@proton/shared/lib/interfaces/contacts';
15 const name = 'contacts' as const;
17 const compareName = (a: Contact, b: Contact) => a.Name.localeCompare(b.Name);
19 export const getContactsModel = (api: Api) => {
29 pageSize: CONTACTS_LIMIT,
30 pagesPerChunk: CONTACTS_REQUESTS_PER_SECOND,
33 return pages.flatMap(({ Contacts }) => Contacts).sort(compareName);
38 [name]: ModelState<Contact[]>;
41 type SliceState = State[typeof name];
42 type Model = NonNullable<SliceState['value']>;
44 export const selectContacts = (state: State) => state[name];
46 const modelThunk = createAsyncModelThunk<Model, State, ProtonThunkArguments>(`${name}/fetch`, {
47 miss: async ({ extraArgument }) => {
48 return getContactsModel(extraArgument.api);
50 previous: previousSelector(selectContacts),
53 const initialState = getInitialModelState<Model>();
54 const slice = createSlice({
58 extraReducers: (builder) => {
59 handleAsyncModel(builder, modelThunk);
60 builder.addCase(serverEvent, (state, action) => {
61 if (state.value && action.payload.Contacts) {
62 state.value = updateCollection({
64 events: action.payload.Contacts,
66 // Event updates return the full item to use
67 merge: (_, b) => b as Contact,
70 if (state.value && hasBit(action.payload.Refresh, EVENT_ERRORS.CONTACTS)) {
78 export const contactsReducer = { [name]: slice.reducer };
79 export const contactsThunk = modelThunk.thunk;