1 import { type PayloadAction, type UnknownAction, createSlice, miniSerializeError } from '@reduxjs/toolkit';
2 import type { ThunkAction } from 'redux-thunk';
4 import { type ModelState, serverEvent } from '@proton/account';
5 import type { ProtonThunkArguments } from '@proton/redux-shared-store-types';
12 } from '@proton/redux-utilities';
13 import { getContact } from '@proton/shared/lib/api/contacts';
14 import { EVENT_ACTIONS } from '@proton/shared/lib/constants';
15 import { EVENT_ERRORS } from '@proton/shared/lib/errors';
16 import { hasBit } from '@proton/shared/lib/helpers/bitset';
17 import type { Contact } from '@proton/shared/lib/interfaces/contacts';
19 const name = 'contact';
21 export interface ContactState {
22 [name]: { [id: string]: ModelState<Contact> };
25 type SliceState = ContactState[typeof name];
26 type Model = SliceState;
28 export const selectContact = (state: ContactState) => state[name];
30 const initialState: Model = {};
31 const slice = createSlice({
35 pending: (state, action: PayloadAction<{ id: string }>) => {
36 const oldValue = state[action.payload.id];
37 if (oldValue && oldValue.error) {
38 oldValue.error = undefined;
41 fulfilled: (state, action: PayloadAction<{ id: string; value: Contact }>) => {
42 state[action.payload.id] = {
43 value: action.payload.value,
45 meta: { fetchedAt: getFetchedAt(), fetchedEphemeral: getFetchedEphemeral() },
48 rejected: (state, action: PayloadAction<{ id: string; value: any }>) => {
49 state[action.payload.id] = {
51 error: action.payload.value,
52 meta: { fetchedAt: getFetchedAt(), fetchedEphemeral: undefined },
56 extraReducers: (builder) => {
57 builder.addCase(serverEvent, (state, action) => {
58 if (action.payload.Contacts) {
59 for (const update of action.payload.Contacts) {
60 if (update.Action === EVENT_ACTIONS.DELETE) {
61 delete state[update.ID];
63 if (update.Action === EVENT_ACTIONS.UPDATE) {
64 if (state[update.ID]) {
65 state[update.ID].value = update.Contact as Contact;
70 if (hasBit(action.payload.Refresh, EVENT_ERRORS.CONTACTS)) {
77 const promiseStore = createPromiseMapStore<Contact>();
79 export const contactThunk = ({
85 }): ThunkAction<Promise<Contact>, ContactState, ProtonThunkArguments, UnknownAction> => {
86 return (dispatch, getState, extraArgument) => {
87 const select = () => {
88 return selectContact(getState())?.[contactID || ''];
90 const cb = async () => {
92 dispatch(slice.actions.pending({ id: contactID }));
93 const contact = await extraArgument
96 }>(getContact(contactID))
97 .then(({ Contact }) => Contact);
98 dispatch(slice.actions.fulfilled({ id: contactID, value: contact }));
101 dispatch(slice.actions.rejected({ id: contactID, value: miniSerializeError(error) }));
105 return cacheHelper({ store: promiseStore, key: contactID, select, cb, cache });
109 export const contactReducer = { [name]: slice.reducer };