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 { getContactGroup, getFolders, getLabels, getSystemFolders } from '@proton/shared/lib/api/labels';
7 import updateCollection, { sortCollection } from '@proton/shared/lib/helpers/updateCollection';
8 import type { Api, Category } from '@proton/shared/lib/interfaces';
10 const name = 'categories' as const;
12 const extractLabels = ({ Labels = [] }) => Labels;
14 const getLabelsModel = async (api: Api) => {
15 const [labels = [], folders = [], contactGroups = [], systemFolders = []] = await Promise.all([
16 api(getLabels()).then(extractLabels),
17 api(getFolders()).then(extractLabels),
18 api(getContactGroup()).then(extractLabels),
19 api(getSystemFolders()).then(extractLabels),
21 return sortCollection('Order', [...labels, ...folders, ...contactGroups, ...systemFolders]);
24 export interface CategoriesState {
25 [name]: ModelState<Category[]>;
28 type SliceState = CategoriesState[typeof name];
29 type Model = NonNullable<SliceState['value']>;
31 export const selectCategories = (state: CategoriesState) => state[name];
33 const modelThunk = createAsyncModelThunk<Model, CategoriesState, ProtonThunkArguments>(`${name}/fetch`, {
34 miss: async ({ extraArgument }) => {
35 return getLabelsModel(extraArgument.api);
37 previous: previousSelector(selectCategories),
40 const initialState = getInitialModelState<Model>();
41 const slice = createSlice({
45 extraReducers: (builder) => {
46 handleAsyncModel(builder, modelThunk);
47 builder.addCase(serverEvent, (state, action) => {
48 if (state.value && action.payload.Labels) {
49 state.value = sortCollection(
53 events: action.payload.Labels,
55 // Event updates don't return e.g. ParentID so it's better that the old value gets completely dropped instead of merged
56 merge: (_, b) => b as Category,
64 export const categoriesReducer = { [name]: slice.reducer };
65 export const categoriesThunk = modelThunk.thunk;