1 import type { Action, Reducer } from 'redux';
7 inviteBatchCreateSuccess,
9 newUserInvitePromoteSuccess,
10 newUserInviteRemoveSuccess,
13 shareEditMemberAccessSuccess,
17 shareRemoveMemberAccessSuccess,
24 vaultTransferOwnershipSuccess,
25 } from '@proton/pass/store/actions';
26 import type { Share } from '@proton/pass/types';
27 import { NewUserInviteState, ShareRole, type ShareType } from '@proton/pass/types';
28 import type { NewUserPendingInvite, PendingInvite, ShareMember } from '@proton/pass/types/data/invites';
29 import { or } from '@proton/pass/utils/fp/predicates';
30 import { objectDelete } from '@proton/pass/utils/object/delete';
31 import { fullMerge, partialMerge } from '@proton/pass/utils/object/merge';
33 export type ShareItem<T extends ShareType = ShareType> = Share<T> & {
34 invites?: PendingInvite[];
35 newUserInvites?: NewUserPendingInvite[];
36 members?: ShareMember[];
39 export type WithItemCount<T> = T & { count: number };
40 export type VaultShareItem = ShareItem<ShareType.Vault>;
42 export type SharesState = { [shareId: string]: ShareItem };
44 export const shares: Reducer<SharesState> = (state = {}, action: Action) => {
45 if (bootSuccess.match(action) && action.payload?.shares !== undefined) return action.payload.shares;
46 if (syncSuccess.match(action)) return action.payload.shares;
47 if (sharesSync.match(action)) return fullMerge(state, action.payload.shares);
49 if (shareEvent.match(action) && state !== null) {
50 const { shareId, Events } = action.payload;
51 const currentEventId = state[shareId].eventId;
53 return Events.LatestEventID === currentEventId
55 : partialMerge(state, {
56 [action.payload.shareId]: { eventId: action.payload.Events.LatestEventID },
60 if (vaultCreationSuccess.match(action)) {
61 const { share } = action.payload;
62 return fullMerge(state, { [share.shareId]: share });
65 if (vaultEditSuccess.match(action)) {
66 const { share } = action.payload;
67 return partialMerge(state, { [share.shareId]: share });
70 if (shareEditSync.match(action)) {
71 const { id, share } = action.payload;
72 return fullMerge(state, { [id]: share });
75 if (or(vaultDeleteSuccess.match, shareDeleteSync.match, shareLeaveSuccess.match)(action)) {
76 return objectDelete(state, action.payload.shareId);
79 if (vaultTransferOwnershipSuccess.match(action)) {
80 const { shareId, userShareId } = action.payload;
81 const members = (state[shareId].members ?? []).map((member) => {
82 if (member.owner) return { ...member, owner: false };
83 if (member.shareId === userShareId) return { ...member, owner: true };
87 return partialMerge(state, { [shareId]: { owner: false, shareRoleId: ShareRole.ADMIN, members } });
90 if (sharedVaultCreated.match(action)) {
91 const { share } = action.payload;
92 return partialMerge(state, { [share.shareId]: share });
95 if (inviteBatchCreateSuccess.match(action)) partialMerge(state, { [action.payload.shareId]: { shared: true } });
97 if (newUserInvitePromoteSuccess.match(action)) {
98 const { shareId, invites, newUserInvites } = action.payload;
99 const { newUserInvitesReady } = state[shareId];
100 return partialMerge(state, {
104 newUserInvitesReady: Math.max(newUserInvitesReady - 1, 0),
109 if (inviteRemoveSuccess.match(action)) {
110 const { shareId, inviteId } = action.payload;
111 const { members = [], invites = [], newUserInvites = [] } = state[shareId];
113 const update = invites.filter((invite) => invite.inviteId !== inviteId);
114 const shared = members.length > 1 || update.length > 0 || newUserInvites.length > 0;
116 return partialMerge(state, { [shareId]: { invites: update, shared } });
119 if (newUserInviteRemoveSuccess.match(action)) {
120 const { shareId, newUserInviteId } = action.payload;
121 const { members = [], invites = [], newUserInvites = [], newUserInvitesReady } = state[shareId];
123 const update = newUserInvites.filter((invite) => invite.newUserInviteId !== newUserInviteId);
124 const shared = members.length > 1 || invites.length > 0 || update.length > 0;
126 return partialMerge(state, {
128 newUserInvites: update,
129 newUserInvitesReady: Math.max(newUserInvitesReady - 1, 0),
135 if (shareAccessChange.match(action)) {
136 const { shareId, ...shareAccessOptions } = action.payload;
137 return partialMerge(state, { [shareId]: shareAccessOptions });
140 if (getShareAccessOptions.success.match(action)) {
141 const { shareId, invites = [], newUserInvites = [], members } = action.payload;
142 const shared = invites.length > 0 || newUserInvites.length > 0 || members.length > 1;
143 const newUserInvitesReady = newUserInvites.filter((invite) => invite.state === NewUserInviteState.READY).length;
145 return partialMerge(state, {
156 if (shareEditMemberAccessSuccess.match(action)) {
157 const { shareId, userShareId, shareRoleId } = action.payload;
158 const members = state[shareId].members ?? [];
160 return partialMerge(state, {
162 members: members.map<ShareMember>((member) =>
163 member.shareId === userShareId ? { ...member, shareRoleId } : member
169 if (shareRemoveMemberAccessSuccess.match(action)) {
170 const { shareId, userShareId } = action.payload;
171 return partialMerge(state, {
173 members: (state[shareId]?.members ?? []).filter(({ shareId }) => shareId !== userShareId),
178 if (inviteAcceptSuccess.match(action)) {
179 return partialMerge(state, { [action.payload.share.shareId]: action.payload.share });
185 export default shares;