diff --git a/package.json b/package.json
index 84a2b15b3a..309a040132 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,7 @@
"react-virtualized": "^9.22.3",
"react-zoom-pan-pinch": "^3.2.0",
"redux": "^4.0.4",
+ "redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"reselect": "^4.0.0",
"slate": "^0.94.1",
diff --git a/src/pages/App/AppWrapper.tsx b/src/pages/App/AppWrapper.tsx
index 3d8e85deb3..1a769f9468 100644
--- a/src/pages/App/AppWrapper.tsx
+++ b/src/pages/App/AppWrapper.tsx
@@ -1,11 +1,14 @@
import React, { FC } from "react";
import { Provider } from "react-redux";
-import { store } from "@/shared/appConfig";
+import { PersistGate } from "redux-persist/integration/react";
+import { store, persistor } from "@/shared/appConfig";
import { NotificationProvider } from "./providers";
const AppWrapper: FC = ({ children }) => (
- {children}
+
+ {children}
+
);
diff --git a/src/pages/Auth/store/saga.tsx b/src/pages/Auth/store/saga.tsx
index 6aa36334b2..81641a1cb1 100755
--- a/src/pages/Auth/store/saga.tsx
+++ b/src/pages/Auth/store/saga.tsx
@@ -15,7 +15,9 @@ import { getProvider } from "@/shared/utils/authProvider";
import { getFundingRequestNotification } from "@/shared/utils/notifications";
import {
cacheActions,
+ commonActions,
commonLayoutActions,
+ inboxActions,
multipleSpacesLayoutActions,
} from "@/store/states";
import {
@@ -301,6 +303,16 @@ const updateUserData = async (user: User): Promise => {
});
};
+const resetGlobalData = (fullReset: boolean) => {
+ if (fullReset) {
+ store.dispatch(multipleSpacesLayoutActions.resetMultipleSpacesLayout());
+ store.dispatch(commonLayoutActions.clearData());
+ }
+ store.dispatch(inboxActions.resetInbox());
+ store.dispatch(cacheActions.resetFeedStates());
+ store.dispatch(commonActions.resetCommon());
+};
+
function* socialLoginSaga({
payload,
}: ReturnType) {
@@ -465,8 +477,6 @@ function* logOut() {
window.ReactNativeWebView.postMessage(WebviewActions.logout);
}
- yield put(multipleSpacesLayoutActions.resetMultipleSpacesLayout());
- yield put(commonLayoutActions.clearData());
history.push(ROUTE_PATHS.HOME);
yield true;
}
@@ -567,6 +577,12 @@ function* authSagas() {
firebase.auth().onAuthStateChanged(async (res) => {
try {
+ const { user: userInStore } = store.getState().auth;
+
+ if (userInStore?.uid !== res?.uid) {
+ resetGlobalData(!res);
+ }
+
store.dispatch(
actions.setAuthProvider(
getAuthProviderFromProviderData(res?.providerData),
diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx
index acdc57a258..5b270c797a 100644
--- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx
+++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx
@@ -12,9 +12,9 @@ import { DeletePrompt, GlobalOverlay, ReportModal } from "@/shared/components";
import { EntityTypes } from "@/shared/constants";
import { useModal, useNotification } from "@/shared/hooks";
import {
+ FeedItemFollowState,
useCommon,
useDiscussionById,
- useFeedItemFollow,
useFeedItemUserMetadata,
useUserById,
} from "@/shared/hooks/useCases";
@@ -63,6 +63,7 @@ interface DiscussionFeedCardProps {
getNonAllowedItems?: GetNonAllowedItemsOptions;
onActiveItemDataChange?: (data: FeedLayoutItemChangeData) => void;
directParent?: DirectParent | null;
+ feedItemFollow: FeedItemFollowState;
onUserSelect?: (userId: string, commonId?: string) => void;
}
@@ -89,6 +90,7 @@ const DiscussionFeedCard = forwardRef(
getNonAllowedItems,
onActiveItemDataChange,
directParent,
+ feedItemFollow,
onUserSelect,
} = props;
const {
@@ -124,10 +126,6 @@ const DiscussionFeedCard = forwardRef(
fetchFeedItemUserMetadata,
} = useFeedItemUserMetadata();
const { data: common } = useCommon(isHome ? commonId : "");
- const feedItemFollow = useFeedItemFollow(
- { feedItemId: item.id, commonId },
- { withSubscription: true },
- );
const menuItems = useMenuItems(
{
commonId,
diff --git a/src/pages/common/components/FeedItem/FeedItem.tsx b/src/pages/common/components/FeedItem/FeedItem.tsx
index 96b79f19fb..6936c90e34 100644
--- a/src/pages/common/components/FeedItem/FeedItem.tsx
+++ b/src/pages/common/components/FeedItem/FeedItem.tsx
@@ -1,4 +1,5 @@
-import React, { forwardRef, memo } from "react";
+import React, { forwardRef, memo, useEffect } from "react";
+import { useFeedItemFollow } from "@/shared/hooks/useCases";
import { FeedLayoutItemChangeData } from "@/shared/interfaces";
import {
Circles,
@@ -64,10 +65,31 @@ const FeedItem = forwardRef((props, ref) => {
onActiveItemDataChange,
directParent,
} = props;
- const { onFeedItemUpdate, getLastMessage, getNonAllowedItems, onUserSelect } =
- useFeedItemContext();
+ const {
+ onFeedItemUpdate,
+ onFeedItemUnfollowed,
+ getLastMessage,
+ getNonAllowedItems,
+ onUserSelect,
+ } = useFeedItemContext();
+ const feedItemFollow = useFeedItemFollow(
+ { feedItemId: item.id, commonId },
+ { withSubscription: true },
+ );
useFeedItemSubscription(item.id, commonId, onFeedItemUpdate);
+ useEffect(() => {
+ if (
+ feedItemFollow.isUserFeedItemFollowDataFetched &&
+ !feedItemFollow.userFeedItemFollowData
+ ) {
+ onFeedItemUnfollowed?.(item.id);
+ }
+ }, [
+ feedItemFollow.isUserFeedItemFollowDataFetched,
+ feedItemFollow.userFeedItemFollowData,
+ ]);
+
if (
shouldCheckItemVisibility &&
!checkIsItemVisibleForUser(
@@ -103,6 +125,7 @@ const FeedItem = forwardRef((props, ref) => {
isMobileVersion,
onActiveItemDataChange: handleActiveItemDataChange,
directParent,
+ feedItemFollow,
onUserSelect,
};
diff --git a/src/pages/common/components/FeedItem/context.ts b/src/pages/common/components/FeedItem/context.ts
index 3f408ee137..0a23aa1ad7 100644
--- a/src/pages/common/components/FeedItem/context.ts
+++ b/src/pages/common/components/FeedItem/context.ts
@@ -66,6 +66,7 @@ export interface FeedItemContextValue {
setExpandedFeedItemId?: (feedItemId: string | null) => void;
renderFeedItemBaseContent?: (props: FeedItemBaseContentProps) => ReactNode;
onFeedItemUpdate?: (item: CommonFeed, isRemoved: boolean) => void;
+ onFeedItemUnfollowed?: (itemId: string) => void;
feedCardSettings?: FeedCardSettings;
getLastMessage: (options: GetLastMessageOptions) => TextEditorValue;
getNonAllowedItems?: GetNonAllowedItemsOptions;
diff --git a/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx b/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx
index e585b92953..362fd01ba0 100644
--- a/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx
+++ b/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx
@@ -13,8 +13,8 @@ import { DeletePrompt, GlobalOverlay } from "@/shared/components";
import { useRoutesContext } from "@/shared/contexts";
import { useForceUpdate, useModal, useNotification } from "@/shared/hooks";
import {
+ FeedItemFollowState,
useDiscussionById,
- useFeedItemFollow,
useFeedItemUserMetadata,
useProposalById,
useUserById,
@@ -80,6 +80,7 @@ interface ProposalFeedCardProps {
getLastMessage: (options: GetLastMessageOptions) => TextEditorValue;
getNonAllowedItems?: GetNonAllowedItemsOptions;
isMobileVersion?: boolean;
+ feedItemFollow: FeedItemFollowState;
onActiveItemDataChange?: (data: FeedLayoutItemChangeData) => void;
onUserSelect?: (userId: string, commonId?: string) => void;
}
@@ -101,6 +102,7 @@ const ProposalFeedCard = forwardRef(
getLastMessage,
getNonAllowedItems,
isMobileVersion,
+ feedItemFollow,
onActiveItemDataChange,
onUserSelect,
} = props;
@@ -175,10 +177,6 @@ const ProposalFeedCard = forwardRef(
onOpen: onShareModalOpen,
onClose: onShareModalClose,
} = useModal(false);
- const feedItemFollow = useFeedItemFollow(
- { feedItemId: item.id, commonId },
- { withSubscription: true },
- );
const menuItems = useMenuItems(
{
commonId,
diff --git a/src/pages/commonFeed/CommonFeed.tsx b/src/pages/commonFeed/CommonFeed.tsx
index 8182a5d33c..1bea857b83 100644
--- a/src/pages/commonFeed/CommonFeed.tsx
+++ b/src/pages/commonFeed/CommonFeed.tsx
@@ -45,6 +45,7 @@ import {
getCommonPageAboutTabPath,
} from "@/shared/utils";
import {
+ cacheActions,
commonActions,
commonLayoutActions,
selectCommonAction,
@@ -162,7 +163,11 @@ const CommonFeedComponent: FC = (props) => {
hasMore: hasMoreCommonFeedItems,
fetch: fetchCommonFeedItems,
batchNumber,
- } = useCommonFeedItems(commonId, commonFeedItemIdsForNotListening);
+ } = useCommonFeedItems(
+ commonId,
+ commonFeedItemIdsForNotListening,
+ sharedFeedItemId,
+ );
const {
isModalOpen: isCommonJoinModalOpen,
@@ -330,7 +335,13 @@ const CommonFeedComponent: FC = (props) => {
useEffect(() => {
fetchData();
+ const interval = setInterval(() => {
+ dispatch(cacheActions.copyFeedStateByCommonId(commonId));
+ }, 5000);
+
return () => {
+ clearInterval(interval);
+ dispatch(cacheActions.copyFeedStateByCommonId(commonId));
dispatch(commonActions.resetCommon());
};
}, [commonId]);
diff --git a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx
index 5ffca2f17b..7adc25a741 100644
--- a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx
+++ b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx
@@ -119,6 +119,7 @@ interface FeedLayoutProps {
renderFeedItemBaseContent: (props: FeedItemBaseContentProps) => ReactNode;
renderChatChannelItem?: (props: ChatChannelFeedLayoutItemProps) => ReactNode;
onFeedItemUpdate?: (item: CommonFeed, isRemoved: boolean) => void;
+ onFeedItemUnfollowed?: (itemId: string) => void;
getLastMessage: (options: GetLastMessageOptions) => TextEditorValue;
sharedFeedItemId?: string | null;
emptyText?: string;
@@ -159,6 +160,7 @@ const FeedLayout: ForwardRefRenderFunction = (
renderFeedItemBaseContent,
renderChatChannelItem,
onFeedItemUpdate,
+ onFeedItemUnfollowed,
getLastMessage,
sharedFeedItemId,
emptyText,
@@ -326,6 +328,7 @@ const FeedLayout: ForwardRefRenderFunction = (
setExpandedFeedItemId,
renderFeedItemBaseContent,
onFeedItemUpdate,
+ onFeedItemUnfollowed,
getLastMessage,
getNonAllowedItems,
onUserSelect: handleUserWithCommonClick,
@@ -333,6 +336,7 @@ const FeedLayout: ForwardRefRenderFunction = (
[
renderFeedItemBaseContent,
onFeedItemUpdate,
+ onFeedItemUnfollowed,
getLastMessage,
getNonAllowedItems,
handleUserWithCommonClick,
diff --git a/src/pages/inbox/BaseInbox.tsx b/src/pages/inbox/BaseInbox.tsx
index fb3da68852..c0b82a4d29 100644
--- a/src/pages/inbox/BaseInbox.tsx
+++ b/src/pages/inbox/BaseInbox.tsx
@@ -150,6 +150,18 @@ const InboxPage: FC = (props) => {
[dispatch],
);
+ const handleFeedItemUnfollowed = useCallback(
+ (itemId: string) => {
+ dispatch(
+ inboxActions.updateFeedItem({
+ item: { id: itemId },
+ isRemoved: true,
+ }),
+ );
+ },
+ [dispatch],
+ );
+
const handleActiveItemChange = useCallback(
(activeItemId?: string) => {
dispatch(inboxActions.removeEmptyChatChannelItems(activeItemId));
@@ -221,10 +233,6 @@ const InboxPage: FC = (props) => {
useEffect(() => {
fetchData();
-
- return () => {
- dispatch(inboxActions.resetInbox());
- };
}, [userId]);
useEffect(() => {
@@ -271,6 +279,7 @@ const InboxPage: FC = (props) => {
renderFeedItemBaseContent={renderFeedItemBaseContent}
renderChatChannelItem={renderChatChannelItem}
onFeedItemUpdate={handleFeedItemUpdate}
+ onFeedItemUnfollowed={handleFeedItemUnfollowed}
getLastMessage={getLastMessage}
sharedFeedItemId={sharedFeedItemId}
emptyText={
diff --git a/src/services/Chat.ts b/src/services/Chat.ts
index d49b6aa7cd..b46e3a0284 100644
--- a/src/services/Chat.ts
+++ b/src/services/Chat.ts
@@ -234,6 +234,28 @@ class ChatService {
});
};
+ public getChatChannels = async (options: {
+ participantId: string;
+ startAt?: Timestamp;
+ endAt?: Timestamp;
+ }): Promise => {
+ const { participantId, startAt, endAt } = options;
+ let query = this.getChatChannelCollection()
+ .where("participants", "array-contains", participantId)
+ .orderBy("updatedAt", "desc");
+
+ if (startAt) {
+ query = query.startAt(startAt);
+ }
+ if (endAt) {
+ query = query.endAt(endAt);
+ }
+
+ const snapshot = await query.get();
+
+ return snapshot.docs.map((doc) => doc.data());
+ };
+
public subscribeToNewUpdatedChatChannels = (
participantId: string,
endBefore: Timestamp,
diff --git a/src/services/FeedItemFollows.ts b/src/services/FeedItemFollows.ts
index c74bf40d10..98a3d99626 100644
--- a/src/services/FeedItemFollows.ts
+++ b/src/services/FeedItemFollows.ts
@@ -116,6 +116,29 @@ class FeedItemFollowsService {
await Api.post(ApiEndpoint.FollowFeedItem, data, { cancelToken });
};
+ public getFollowFeedItems = async (options: {
+ userId: string;
+ startAt?: Timestamp;
+ endAt?: Timestamp;
+ }): Promise => {
+ const { userId, startAt, endAt } = options;
+ let query = this.getFeedItemFollowsSubCollection(userId).orderBy(
+ "lastActivity",
+ "desc",
+ );
+
+ if (startAt) {
+ query = query.startAt(startAt);
+ }
+ if (endAt) {
+ query = query.endAt(endAt);
+ }
+
+ const snapshot = await query.get();
+
+ return snapshot.docs.map((doc) => doc.data());
+ };
+
public subscribeToNewUpdatedFollowFeedItem = (
userId: string,
endBefore: Timestamp,
diff --git a/src/shared/appConfig.ts b/src/shared/appConfig.ts
index 2650847bfd..07f6406505 100644
--- a/src/shared/appConfig.ts
+++ b/src/shared/appConfig.ts
@@ -1,6 +1,6 @@
import configureStore from "@/store";
import history from "./history";
-const { store } = configureStore(history);
+const { store, persistor } = configureStore(history);
-export { history, store };
+export { history, store, persistor };
diff --git a/src/shared/hooks/useCases/useCommonFeedItems.ts b/src/shared/hooks/useCases/useCommonFeedItems.ts
index 3a4a6c2f66..f4f5a87edd 100644
--- a/src/shared/hooks/useCases/useCommonFeedItems.ts
+++ b/src/shared/hooks/useCases/useCommonFeedItems.ts
@@ -11,6 +11,7 @@ interface Return
export const useCommonFeedItems = (
commonId: string,
idsForNotListening?: string[],
+ sharedFeedItemId?: string | null,
): Return => {
const dispatch = useDispatch();
const feedItems = useSelector(selectFeedItems);
@@ -20,6 +21,7 @@ export const useCommonFeedItems = (
dispatch(
commonActions.getFeedItems.request({
commonId,
+ sharedFeedItemId,
feedItemId,
limit: 15,
}),
@@ -67,7 +69,6 @@ export const useCommonFeedItems = (
dispatch(
commonActions.getFeedItems.cancel("Cancel feed items fetch on unmount"),
);
- dispatch(commonActions.resetFeedItems());
};
}, []);
diff --git a/src/shared/hooks/useCases/useFeedItemFollow.ts b/src/shared/hooks/useCases/useFeedItemFollow.ts
index 0fc1e02687..3524366d07 100644
--- a/src/shared/hooks/useCases/useFeedItemFollow.ts
+++ b/src/shared/hooks/useCases/useFeedItemFollow.ts
@@ -2,6 +2,7 @@ import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { selectUser } from "@/pages/Auth/store/selectors";
import { FollowFeedItemAction } from "@/shared/constants";
+import { FeedItemFollow } from "@/shared/models";
import {
selectCommonFeedFollows,
selectFollowFeedItemMutationState,
@@ -15,6 +16,8 @@ export interface FeedItemFollowState {
isFollowing: boolean;
isDisabled: boolean;
onFollowToggle: (action?: FollowFeedItemAction) => void;
+ isUserFeedItemFollowDataFetched: boolean;
+ userFeedItemFollowData: FeedItemFollow | null;
}
interface Data {
@@ -105,5 +108,7 @@ export function useFeedItemFollow(
isFollowing,
isDisabled,
onFollowToggle,
+ isUserFeedItemFollowDataFetched,
+ userFeedItemFollowData,
};
}
diff --git a/src/shared/hooks/useCases/useInboxItems.ts b/src/shared/hooks/useCases/useInboxItems.ts
index e9329172d4..82805954d4 100644
--- a/src/shared/hooks/useCases/useInboxItems.ts
+++ b/src/shared/hooks/useCases/useInboxItems.ts
@@ -9,8 +9,13 @@ import {
Logger,
} from "@/services";
import { InboxItemType } from "@/shared/constants";
+import { useIsMounted } from "@/shared/hooks";
import { FeedLayoutItemWithFollowData } from "@/shared/interfaces";
-import { FeedItemFollow, FeedItemFollowWithMetadata } from "@/shared/models";
+import {
+ ChatChannel,
+ FeedItemFollow,
+ FeedItemFollowWithMetadata,
+} from "@/shared/models";
import { inboxActions, InboxItems, selectInboxItems } from "@/store/states";
interface Return
@@ -115,6 +120,7 @@ export const useInboxItems = (
options?: { unread?: boolean },
): Return => {
const dispatch = useDispatch();
+ const isMounted = useIsMounted();
const [newItemsBatches, setNewItemsBatches] = useState([]);
const inboxItems = useSelector(selectInboxItems);
const user = useSelector(selectUser());
@@ -135,6 +141,114 @@ export const useInboxItems = (
fetch();
};
+ const addNewChatChannels = (
+ data: {
+ chatChannel: ChatChannel;
+ statuses: {
+ isAdded: boolean;
+ isRemoved: boolean;
+ };
+ }[],
+ ) => {
+ const finalData =
+ feedItemIdsForNotListening && feedItemIdsForNotListening.length > 0
+ ? data.filter(
+ (item) => !feedItemIdsForNotListening.includes(item.chatChannel.id),
+ )
+ : data;
+
+ if (finalData.length === 0) {
+ return;
+ }
+
+ dispatch(
+ inboxActions.addNewInboxItems(
+ finalData.map((item) => ({
+ item: {
+ itemId: item.chatChannel.id,
+ type: InboxItemType.ChatChannel,
+ chatChannel: item.chatChannel,
+ },
+ statuses: item.statuses,
+ })),
+ ),
+ );
+ };
+
+ const addNewFollowFeedItems = (
+ data: {
+ item: FeedItemFollow;
+ statuses: {
+ isAdded: boolean;
+ isRemoved: boolean;
+ };
+ }[],
+ ) => {
+ if (data.length === 0) {
+ return;
+ }
+
+ const finalData =
+ feedItemIdsForNotListening && feedItemIdsForNotListening.length > 0
+ ? data.filter(
+ (item) =>
+ !feedItemIdsForNotListening.includes(item.item.feedItemId),
+ )
+ : data;
+ setNewItemsBatches((currentItems) => [...currentItems, finalData]);
+ };
+
+ useEffect(() => {
+ (async () => {
+ try {
+ const { firstDocTimestamp: startAt, lastDocTimestamp: endAt } =
+ inboxItems;
+
+ if (!userId || !startAt || !endAt) {
+ return;
+ }
+
+ const [chatChannels, feedItemFollows] = await Promise.all([
+ ChatService.getChatChannels({
+ participantId: userId,
+ startAt,
+ endAt,
+ }),
+ FeedItemFollowsService.getFollowFeedItems({
+ userId,
+ startAt,
+ endAt,
+ }),
+ ]);
+
+ if (!isMounted()) {
+ return;
+ }
+
+ addNewChatChannels(
+ chatChannels.map((chatChannel) => ({
+ chatChannel,
+ statuses: {
+ isAdded: false,
+ isRemoved: false,
+ },
+ })),
+ );
+ addNewFollowFeedItems(
+ feedItemFollows.map((item) => ({
+ item,
+ statuses: {
+ isAdded: false,
+ isRemoved: false,
+ },
+ })),
+ );
+ } catch (err) {
+ Logger.error(err);
+ }
+ })();
+ }, []);
+
useEffect(() => {
if (!inboxItems.firstDocTimestamp || !userId) {
return;
@@ -144,30 +258,7 @@ export const useInboxItems = (
userId,
inboxItems.firstDocTimestamp,
(data) => {
- const finalData =
- feedItemIdsForNotListening && feedItemIdsForNotListening.length > 0
- ? data.filter(
- (item) =>
- !feedItemIdsForNotListening.includes(item.chatChannel.id),
- )
- : data;
-
- if (finalData.length === 0) {
- return;
- }
-
- dispatch(
- inboxActions.addNewInboxItems(
- finalData.map((item) => ({
- item: {
- itemId: item.chatChannel.id,
- type: InboxItemType.ChatChannel,
- chatChannel: item.chatChannel,
- },
- statuses: item.statuses,
- })),
- ),
- );
+ addNewChatChannels(data);
},
);
@@ -184,18 +275,7 @@ export const useInboxItems = (
userId,
inboxItems.firstDocTimestamp,
(data) => {
- if (data.length === 0) {
- return;
- }
-
- const finalData =
- feedItemIdsForNotListening && feedItemIdsForNotListening.length > 0
- ? data.filter(
- (item) =>
- !feedItemIdsForNotListening.includes(item.item.feedItemId),
- )
- : data;
- setNewItemsBatches((currentItems) => [...currentItems, finalData]);
+ addNewFollowFeedItems(data);
},
);
diff --git a/src/shared/interfaces/feedLayout.ts b/src/shared/interfaces/feedLayout.ts
index 48407f0d3e..284d03781a 100644
--- a/src/shared/interfaces/feedLayout.ts
+++ b/src/shared/interfaces/feedLayout.ts
@@ -5,6 +5,7 @@ import {
CommonFeed,
FeedItemFollowWithMetadata,
} from "@/shared/models";
+import { convertObjectDatesToFirestoreTimestamps } from "@/shared/utils";
export interface FeedLayoutRef {
setExpandedFeedItemId: (feedItemId: string | null) => void;
@@ -67,3 +68,45 @@ export type FeedLayoutItemChangeDataWithType = FeedLayoutItemChangeData &
commonId: string;
}
);
+
+export const deserializeFeedItemFollowLayoutItem = <
+ T extends FeedItemFollowLayoutItem | FeedItemFollowLayoutItemWithFollowData,
+>(
+ item: T,
+): T => ({
+ ...item,
+ feedItem: convertObjectDatesToFirestoreTimestamps(item.feedItem),
+ feedItemFollowWithMetadata: item.feedItemFollowWithMetadata && {
+ ...convertObjectDatesToFirestoreTimestamps(
+ item.feedItemFollowWithMetadata,
+ ["lastSeen", "lastActivity"],
+ ),
+ feedItem: convertObjectDatesToFirestoreTimestamps(
+ item.feedItemFollowWithMetadata.feedItem,
+ ),
+ },
+});
+
+export const deserializeChatChannelLayoutItem = (
+ item: ChatChannelLayoutItem,
+): ChatChannelLayoutItem => ({
+ ...item,
+ chatChannel: convertObjectDatesToFirestoreTimestamps(
+ item.chatChannel,
+ ["lastMessage.createdAt"],
+ ),
+});
+
+export const deserializeFeedLayoutItem = (
+ item: FeedLayoutItem,
+): FeedLayoutItem =>
+ checkIsChatChannelLayoutItem(item)
+ ? deserializeChatChannelLayoutItem(item)
+ : deserializeFeedItemFollowLayoutItem(item);
+
+export const deserializeFeedLayoutItemWithFollowData = (
+ item: FeedLayoutItemWithFollowData,
+): FeedLayoutItemWithFollowData =>
+ checkIsChatChannelLayoutItem(item)
+ ? deserializeChatChannelLayoutItem(item)
+ : deserializeFeedItemFollowLayoutItem(item);
diff --git a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsData.ts b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsData.ts
index 3e7b0fb903..24fdce0ec0 100644
--- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsData.ts
+++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsData.ts
@@ -104,22 +104,16 @@ export const useProjectsData = (): Return => {
}, [isAuthenticated]);
useEffect(() => {
- if (areCommonsLoading) {
- return;
- }
if (!areCommonsFetched) {
dispatch(commonLayoutActions.getCommons.request(activeItemId));
}
- }, [areCommonsLoading, areCommonsFetched]);
+ }, [areCommonsFetched]);
useEffect(() => {
- if (areProjectsLoading || !currentCommonId) {
- return;
- }
- if (!areProjectsFetched) {
+ if (currentCommonId && !areProjectsFetched) {
dispatch(commonLayoutActions.getProjects.request(currentCommonId));
}
- }, [areProjectsLoading, areProjectsFetched, currentCommonId]);
+ }, [areProjectsFetched, currentCommonId]);
useEffect(() => {
if (
diff --git a/src/shared/utils/checkIsSynchronizedDate.ts b/src/shared/utils/checkIsSynchronizedDate.ts
new file mode 100644
index 0000000000..b06ed1f63d
--- /dev/null
+++ b/src/shared/utils/checkIsSynchronizedDate.ts
@@ -0,0 +1,4 @@
+import { SynchronizedDate } from "@/shared/interfaces";
+
+export const checkIsSynchronizedDate = (date: any): date is SynchronizedDate =>
+ Boolean(date && date._seconds);
diff --git a/src/shared/utils/convertDatesToFirestoreTimestamps.ts b/src/shared/utils/convertDatesToFirestoreTimestamps.ts
index 4398fed138..ed5022b2f1 100644
--- a/src/shared/utils/convertDatesToFirestoreTimestamps.ts
+++ b/src/shared/utils/convertDatesToFirestoreTimestamps.ts
@@ -1,11 +1,14 @@
-import firebase from "firebase";
import { get, set } from "lodash";
import { SynchronizedDate } from "@/shared/interfaces";
+import { Timestamp } from "@/shared/models";
+import { checkIsSynchronizedDate } from "@/shared/utils";
export const convertToTimestamp = (
- date: SynchronizedDate,
-): firebase.firestore.Timestamp =>
- new firebase.firestore.Timestamp(date._seconds, date._nanoseconds);
+ date: SynchronizedDate | Timestamp,
+): Timestamp =>
+ checkIsSynchronizedDate(date)
+ ? new Timestamp(date._seconds, date._nanoseconds)
+ : new Timestamp(date.seconds, date.nanoseconds);
const convertDateInObject = (
data: Record,
diff --git a/src/shared/utils/index.tsx b/src/shared/utils/index.tsx
index 74e159b1d9..c0151ea0ec 100755
--- a/src/shared/utils/index.tsx
+++ b/src/shared/utils/index.tsx
@@ -25,6 +25,7 @@ export * from "./routes";
export * from "./checkIsAutomaticJoin";
export * from "./checkIsIFrame";
export * from "./checkIsProject";
+export * from "./checkIsSynchronizedDate";
export * from "./checkIsURL";
export * from "./circles";
export * from "./notifications";
diff --git a/src/store/states/cache/actions.ts b/src/store/states/cache/actions.ts
index c46f98db89..24f00c60e3 100644
--- a/src/store/states/cache/actions.ts
+++ b/src/store/states/cache/actions.ts
@@ -10,6 +10,7 @@ import {
User,
} from "@/shared/models";
import { CacheActionType } from "./constants";
+import { FeedState } from "./types";
export const getUserStateById = createAsyncAction(
CacheActionType.GET_USER_STATE_BY_ID,
@@ -131,6 +132,21 @@ export const updateProposalStateById = createStandardAction(
state: LoadingState;
}>();
+export const copyFeedStateByCommonId = createStandardAction(
+ CacheActionType.COPY_FEED_STATE_BY_COMMON_ID,
+)();
+
+export const updateFeedStateByCommonId = createStandardAction(
+ CacheActionType.UPDATE_FEED_STATE_BY_COMMON_ID,
+)<{
+ commonId: string;
+ state: FeedState;
+}>();
+
+export const resetFeedStates = createStandardAction(
+ CacheActionType.RESET_FEED_STATES,
+)();
+
export const getFeedItemUserMetadata = createAsyncAction(
CacheActionType.GET_FEED_ITEM_USER_METADATA,
CacheActionType.GET_FEED_ITEM_USER_METADATA_SUCCESS,
diff --git a/src/store/states/cache/constants.ts b/src/store/states/cache/constants.ts
index 042ff0840c..cfa3c62449 100644
--- a/src/store/states/cache/constants.ts
+++ b/src/store/states/cache/constants.ts
@@ -30,6 +30,10 @@ export enum CacheActionType {
UPDATE_PROPOSAL_STATE_BY_ID = "@CACHE/UPDATE_PROPOSAL_STATE_BY_ID",
+ COPY_FEED_STATE_BY_COMMON_ID = "@CACHE/COPY_FEED_STATE_BY_COMMON_ID",
+ UPDATE_FEED_STATE_BY_COMMON_ID = "@CACHE/UPDATE_FEED_STATE_BY_COMMON_ID",
+ RESET_FEED_STATES = "@CACHE/RESET_FEED_STATES",
+
GET_FEED_ITEM_USER_METADATA = "@CACHE/GET_FEED_ITEM_USER_METADATA",
GET_FEED_ITEM_USER_METADATA_SUCCESS = "@CACHE/GET_FEED_ITEM_USER_METADATA_SUCCESS",
GET_FEED_ITEM_USER_METADATA_FAILURE = "@CACHE/GET_FEED_ITEM_USER_METADATA_FAILURE",
diff --git a/src/store/states/cache/reducer.tsx b/src/store/states/cache/reducer.tsx
index 95df165190..0a1a60cefa 100644
--- a/src/store/states/cache/reducer.tsx
+++ b/src/store/states/cache/reducer.tsx
@@ -13,6 +13,7 @@ const initialState: CacheState = {
discussionStates: {},
discussionMessagesStates: {},
proposalStates: {},
+ feedByCommonIdStates: {},
feedItemUserMetadataStates: {},
chatChannelUserStatusStates: {},
};
@@ -97,6 +98,18 @@ export const reducer = createReducer(initialState)
nextState.proposalStates[proposalId] = { ...state };
}),
)
+ .handleAction(actions.updateFeedStateByCommonId, (state, { payload }) =>
+ produce(state, (nextState) => {
+ const { commonId, state } = payload;
+
+ nextState.feedByCommonIdStates[commonId] = { ...state };
+ }),
+ )
+ .handleAction(actions.resetFeedStates, (state) =>
+ produce(state, (nextState) => {
+ nextState.feedByCommonIdStates = {};
+ }),
+ )
.handleAction(actions.updateFeedItemUserMetadata, (state, { payload }) =>
produce(state, (nextState) => {
const { commonId, userId, feedObjectId, state } = payload;
diff --git a/src/store/states/cache/saga/copyFeedStateByCommonId.ts b/src/store/states/cache/saga/copyFeedStateByCommonId.ts
new file mode 100644
index 0000000000..ab3405ba70
--- /dev/null
+++ b/src/store/states/cache/saga/copyFeedStateByCommonId.ts
@@ -0,0 +1,35 @@
+import { put, select } from "redux-saga/effects";
+import { getFeedLayoutItemDateForSorting } from "@/store/states/inbox/utils";
+import { selectCommonState, CommonState } from "../../common";
+import * as actions from "../actions";
+
+export function* copyFeedStateByCommonId({
+ payload: commonId,
+}: ReturnType) {
+ const commonState = (yield select(selectCommonState)) as CommonState;
+ const data =
+ commonState.feedItems.data && commonState.feedItems.data.slice(0, 30);
+ const feedItems = {
+ ...commonState.feedItems,
+ data,
+ loading: false,
+ hasMore: true,
+ firstDocTimestamp: data?.[0]
+ ? getFeedLayoutItemDateForSorting(data[0])
+ : null,
+ lastDocTimestamp: data?.[data.length - 1]
+ ? getFeedLayoutItemDateForSorting(data[data.length - 1])
+ : null,
+ };
+
+ yield put(
+ actions.updateFeedStateByCommonId({
+ commonId,
+ state: {
+ feedItems,
+ pinnedFeedItems: commonState.pinnedFeedItems,
+ sharedFeedItem: commonState.sharedFeedItem,
+ },
+ }),
+ );
+}
diff --git a/src/store/states/cache/saga/index.ts b/src/store/states/cache/saga/index.ts
index 802ac87ead..0eeba6aa13 100644
--- a/src/store/states/cache/saga/index.ts
+++ b/src/store/states/cache/saga/index.ts
@@ -1,6 +1,8 @@
+import { takeLatest } from "redux-saga/effects";
import { getFeedItemUserMetadataKey } from "@/shared/constants/getFeedItemUserMetadataKey";
import { takeLeadingByIdentifier } from "@/shared/utils/saga";
import * as actions from "../actions";
+import { copyFeedStateByCommonId } from "./copyFeedStateByCommonId";
import { getDiscussionStateById } from "./getDiscussionStateById";
import { getFeedItemUserMetadataState } from "./getFeedItemUserMetadataState";
import { getGovernanceStateByCommonId } from "./getGovernanceStateByCommonId";
@@ -33,4 +35,5 @@ export function* mainSaga() {
({ payload: { payload } }) => getFeedItemUserMetadataKey(payload),
getFeedItemUserMetadataState,
);
+ yield takeLatest(actions.copyFeedStateByCommonId, copyFeedStateByCommonId);
}
diff --git a/src/store/states/cache/selectors.ts b/src/store/states/cache/selectors.ts
index 60ec9ed317..3bfb83d902 100644
--- a/src/store/states/cache/selectors.ts
+++ b/src/store/states/cache/selectors.ts
@@ -21,6 +21,10 @@ export const selectDiscussionMessagesStateByDiscussionId =
(discussionId: string) => (state: AppState) =>
state.cache.discussionMessagesStates[discussionId] || null;
+export const selectFeedStateByCommonId =
+ (commonId: string) => (state: AppState) =>
+ state.cache.feedByCommonIdStates[commonId] || null;
+
export const selectFeedItemUserMetadata =
(info: { commonId: string; userId: string; feedObjectId: string }) =>
(state: AppState) =>
diff --git a/src/store/states/cache/types.ts b/src/store/states/cache/types.ts
index 17bf5b0e6f..4d99eec351 100644
--- a/src/store/states/cache/types.ts
+++ b/src/store/states/cache/types.ts
@@ -8,6 +8,12 @@ import {
Proposal,
User,
} from "@/shared/models";
+import { CommonState } from "../common";
+
+export type FeedState = Pick<
+ CommonState,
+ "feedItems" | "pinnedFeedItems" | "sharedFeedItem"
+>;
export interface CacheState {
userStates: Record>;
@@ -18,6 +24,7 @@ export interface CacheState {
string,
LoadingState
>;
+ feedByCommonIdStates: Record;
// key: {commonId}_{userId}_{feedObjectId}
feedItemUserMetadataStates: Record<
string,
diff --git a/src/store/states/common/actions.ts b/src/store/states/common/actions.ts
index a83093fe8e..e71f074cb5 100644
--- a/src/store/states/common/actions.ts
+++ b/src/store/states/common/actions.ts
@@ -20,7 +20,7 @@ import {
Proposal,
} from "@/shared/models";
import { CommonActionType } from "./constants";
-import { FeedItems, PinnedFeedItems } from "./types";
+import { CommonState, FeedItems, PinnedFeedItems } from "./types";
export const resetCommon = createStandardAction(
CommonActionType.RESET_COMMON,
@@ -116,6 +116,7 @@ export const getFeedItems = createAsyncAction(
)<
{
commonId: string;
+ sharedFeedItemId?: string | null;
feedItemId?: string;
limit?: number;
},
@@ -138,6 +139,13 @@ export const getPinnedFeedItems = createAsyncAction(
string
>();
+export const setFeedState = createStandardAction(
+ CommonActionType.SET_FEED_STATE,
+)<{
+ data: Pick;
+ sharedFeedItemId?: string | null;
+}>();
+
export const addNewFeedItems = createStandardAction(
CommonActionType.ADD_NEW_FEED_ITEMS,
)<
diff --git a/src/store/states/common/constants.ts b/src/store/states/common/constants.ts
index bbb9cdb0dc..a0e1f79e8a 100644
--- a/src/store/states/common/constants.ts
+++ b/src/store/states/common/constants.ts
@@ -26,6 +26,8 @@ export enum CommonActionType {
GET_PINNED_FEED_ITEMS_FAILURE = "@COMMON/GET_PINNED_FEED_ITEMS_FAILURE",
GET_PINNED_FEED_ITEMS_CANCEL = "@COMMON/GET_PINNED_FEED_ITEMS_CANCEL",
+ SET_FEED_STATE = "@COMMON/SET_FEED_STATE",
+
ADD_NEW_FEED_ITEMS = "@COMMON/ADD_NEW_FEED_ITEMS",
ADD_NEW_PINNED_FEED_ITEMS = "@COMMON/ADD_NEW_PINNED_FEED_ITEMS",
diff --git a/src/store/states/common/reducer.ts b/src/store/states/common/reducer.ts
index 5e4b5fbd07..94bf974e80 100644
--- a/src/store/states/common/reducer.ts
+++ b/src/store/states/common/reducer.ts
@@ -2,8 +2,12 @@ import produce from "immer";
import { WritableDraft } from "immer/dist/types/types-external";
import { ActionType, createReducer } from "typesafe-actions";
import { InboxItemType } from "@/shared/constants";
-import { FeedItemFollowLayoutItem } from "@/shared/interfaces";
+import {
+ deserializeFeedItemFollowLayoutItem,
+ FeedItemFollowLayoutItem,
+} from "@/shared/interfaces";
import { CommonFeed } from "@/shared/models";
+import { convertToTimestamp } from "@/shared/utils";
import * as actions from "./actions";
import { CommonState, FeedItems, PinnedFeedItems } from "./types";
@@ -47,8 +51,7 @@ const initialState: CommonState = {
const sortFeedItems = (data: FeedItemFollowLayoutItem[]): void => {
data.sort(
(prevItem, nextItem) =>
- nextItem.feedItem.updatedAt.toMillis() -
- prevItem.feedItem.updatedAt.toMillis(),
+ nextItem.feedItem.updatedAt.seconds - prevItem.feedItem.updatedAt.seconds,
);
};
@@ -469,6 +472,62 @@ export const reducer = createReducer(initialState)
};
}),
)
+ .handleAction(actions.setFeedState, (state, { payload }) =>
+ produce(state, (nextState) => {
+ const {
+ data: { feedItems, pinnedFeedItems, sharedFeedItem },
+ sharedFeedItemId,
+ } = payload;
+ nextState.feedItems = {
+ ...feedItems,
+ data:
+ feedItems.data &&
+ feedItems.data.map(deserializeFeedItemFollowLayoutItem),
+ firstDocTimestamp:
+ (feedItems.firstDocTimestamp &&
+ convertToTimestamp(feedItems.firstDocTimestamp)) ||
+ null,
+ lastDocTimestamp:
+ (feedItems.lastDocTimestamp &&
+ convertToTimestamp(feedItems.lastDocTimestamp)) ||
+ null,
+ hasMore: true,
+ };
+ nextState.pinnedFeedItems = {
+ ...pinnedFeedItems,
+ data:
+ pinnedFeedItems.data &&
+ pinnedFeedItems.data.map(deserializeFeedItemFollowLayoutItem),
+ };
+
+ if (sharedFeedItem && sharedFeedItem.itemId === sharedFeedItemId) {
+ return;
+ }
+ if (
+ sharedFeedItem &&
+ !pinnedFeedItems.data?.some(
+ (item) => item.itemId === sharedFeedItem.itemId,
+ ) &&
+ !feedItems.data?.some((item) => item.itemId === sharedFeedItem.itemId)
+ ) {
+ const data = [sharedFeedItem, ...(feedItems.data || [])];
+ sortFeedItems(data);
+ nextState.feedItems.data = data;
+ }
+ if (sharedFeedItemId) {
+ nextState.feedItems.data =
+ nextState.feedItems.data &&
+ nextState.feedItems.data.filter(
+ (item) => item.itemId !== sharedFeedItemId,
+ );
+ nextState.pinnedFeedItems.data =
+ nextState.pinnedFeedItems.data &&
+ nextState.pinnedFeedItems.data.filter(
+ (item) => item.itemId !== sharedFeedItemId,
+ );
+ }
+ }),
+ )
.handleAction(actions.addNewFeedItems, (state, { payload }) =>
produce(state, (nextState) => {
addNewFeedItems(nextState, payload);
diff --git a/src/store/states/common/saga/getFeedItems.ts b/src/store/states/common/saga/getFeedItems.ts
index 3ab56ccb61..5077852c33 100644
--- a/src/store/states/common/saga/getFeedItems.ts
+++ b/src/store/states/common/saga/getFeedItems.ts
@@ -3,6 +3,7 @@ import { CommonFeedService } from "@/services";
import { InboxItemType } from "@/shared/constants";
import { Awaited, FeedItemFollowLayoutItem } from "@/shared/interfaces";
import { isError } from "@/shared/utils";
+import { selectFeedStateByCommonId } from "@/store/states";
import * as actions from "../actions";
import { selectFeedItems } from "../selectors";
import { FeedItems } from "../types";
@@ -11,11 +12,23 @@ export function* getFeedItems(
action: ReturnType,
) {
const {
- payload: { commonId, feedItemId, limit },
+ payload: { commonId, sharedFeedItemId, feedItemId, limit },
} = action;
try {
const currentFeedItems = (yield select(selectFeedItems)) as FeedItems;
+ const cachedFeedState = yield select(selectFeedStateByCommonId(commonId));
+
+ if (!currentFeedItems.data && !feedItemId && cachedFeedState) {
+ yield put(
+ actions.setFeedState({
+ data: cachedFeedState,
+ sharedFeedItemId,
+ }),
+ );
+ return;
+ }
+
const isFirstRequest = !currentFeedItems.lastDocTimestamp;
const { data, firstDocTimestamp, lastDocTimestamp, hasMore } = (yield call(
CommonFeedService.getCommonFeedItemsByUpdatedAt,
diff --git a/src/store/states/common/selectors.ts b/src/store/states/common/selectors.ts
index ba61e5c62d..5b1005b293 100644
--- a/src/store/states/common/selectors.ts
+++ b/src/store/states/common/selectors.ts
@@ -1,5 +1,7 @@
import { AppState } from "@/shared/interfaces";
+export const selectCommonState = (state: AppState) => state.common;
+
export const selectCommonAction = (state: AppState) =>
state.common.commonAction;
diff --git a/src/store/states/inbox/reducer.ts b/src/store/states/inbox/reducer.ts
index 55d625cfa3..87dca602b7 100644
--- a/src/store/states/inbox/reducer.ts
+++ b/src/store/states/inbox/reducer.ts
@@ -34,8 +34,8 @@ const initialState: InboxState = {
const sortInboxItems = (data: FeedLayoutItemWithFollowData[]): void => {
data.sort(
(prevItem, nextItem) =>
- getFeedLayoutItemDateForSorting(nextItem).toMillis() -
- getFeedLayoutItemDateForSorting(prevItem).toMillis(),
+ getFeedLayoutItemDateForSorting(nextItem).seconds -
+ getFeedLayoutItemDateForSorting(prevItem).seconds,
);
};
diff --git a/src/store/store.tsx b/src/store/store.tsx
index 9cb6565f86..3ed1582821 100644
--- a/src/store/store.tsx
+++ b/src/store/store.tsx
@@ -1,3 +1,5 @@
+import { routerMiddleware } from "connected-react-router";
+import { History } from "history";
import {
createStore,
applyMiddleware,
@@ -6,14 +8,32 @@ import {
Dispatch,
Store,
} from "redux";
-import { History } from "history";
-import { routerMiddleware } from "connected-react-router";
import { composeWithDevTools } from "redux-devtools-extension";
import freeze from "redux-freeze";
+import { persistStore, persistReducer, PersistConfig } from "redux-persist";
+import autoMergeLevel2 from "redux-persist/lib/stateReconciler/autoMergeLevel2";
+import storage from "redux-persist/lib/storage";
import createSagaMiddleware from "redux-saga";
-import { AppState } from '@/shared/interfaces';
-import appSagas from "./saga";
+import { AppState } from "@/shared/interfaces";
import rootReducer from "./reducer";
+import appSagas from "./saga";
+import { inboxTransform } from "./transforms";
+
+const persistConfig: PersistConfig = {
+ key: "root",
+ storage,
+ whitelist: [
+ "projects",
+ "commonLayout",
+ "commonFeedFollows",
+ "cache",
+ "chat",
+ "inbox",
+ "multipleSpacesLayout",
+ ],
+ stateReconciler: autoMergeLevel2,
+ transforms: [inboxTransform],
+};
const sagaMiddleware = createSagaMiddleware();
let middleware: Array;
@@ -28,52 +48,65 @@ if (process.env.NODE_ENV === "development") {
composer = compose;
}
-const errorHandlerMiddleware: Middleware = () => (next: Dispatch) => (action) => {
- if (action.type.includes("FAILURE")) {
- // next(
- // showNotification({
- // message: action.payload.error || action.payload.message,
- // appearance: "error",
- // }),
- // );
+const errorHandlerMiddleware: Middleware =
+ () => (next: Dispatch) => (action) => {
+ if (action.type.includes("FAILURE")) {
+ // next(
+ // showNotification({
+ // message: action.payload.error || action.payload.message,
+ // appearance: "error",
+ // }),
+ // );
- if (action.payload && (action.payload.code === 401 || action.payload.code === 403)) {
- localStorage.clear();
+ if (
+ action.payload &&
+ (action.payload.code === 401 || action.payload.code === 403)
+ ) {
+ localStorage.clear();
+ }
}
- }
- if (action.type.includes("SUCCESS") && action.payload && action.payload.message) {
- // next(
- // showNotification({
- // message: action.payload.message,
- // appearance: "success",
- // }),
- // );
- }
+ if (
+ action.type.includes("SUCCESS") &&
+ action.payload &&
+ action.payload.message
+ ) {
+ // next(
+ // showNotification({
+ // message: action.payload.message,
+ // appearance: "success",
+ // }),
+ // );
+ }
- return next(action);
-};
+ return next(action);
+ };
+// defaults to localStorage for web
export default function configureStore(history: History) {
+ const persistedReducer = persistReducer(persistConfig, rootReducer(history));
const store: Store = createStore(
- rootReducer(history),
+ persistedReducer,
undefined,
composer(
applyMiddleware(
...middleware,
routerMiddleware(history),
- errorHandlerMiddleware
- )
- )
+ errorHandlerMiddleware,
+ ),
+ ),
);
+ const persistor = persistStore(store);
sagaMiddleware.run(appSagas);
// eslint-disable-next-line
if ((module as any).hot) {
// eslint-disable-next-line
- (module as any).hot.accept(() => store.replaceReducer(rootReducer(history)));
+ (module as any).hot.accept(() =>
+ store.replaceReducer(persistedReducer as any),
+ );
}
- return { store };
+ return { store, persistor };
}
diff --git a/src/store/transforms.ts b/src/store/transforms.ts
new file mode 100644
index 0000000000..98efc666da
--- /dev/null
+++ b/src/store/transforms.ts
@@ -0,0 +1,45 @@
+import { createTransform } from "redux-persist";
+import { deserializeFeedLayoutItemWithFollowData } from "@/shared/interfaces";
+import { convertObjectDatesToFirestoreTimestamps } from "@/shared/utils";
+import { getFeedLayoutItemDateForSorting } from "@/store/states/inbox/utils";
+import { InboxItems, InboxState } from "./states/inbox";
+
+export const inboxTransform = createTransform(
+ (inboundState: InboxState) => {
+ const data =
+ inboundState.items.data && inboundState.items.data.slice(0, 30);
+
+ return {
+ ...inboundState,
+ items: {
+ ...inboundState.items,
+ data,
+ loading: false,
+ hasMore: true,
+ firstDocTimestamp: data?.[0]
+ ? getFeedLayoutItemDateForSorting(data[0])
+ : null,
+ lastDocTimestamp: data?.[data.length - 1]
+ ? getFeedLayoutItemDateForSorting(data[data.length - 1])
+ : null,
+ },
+ };
+ },
+ (outboundState: InboxState) => ({
+ ...outboundState,
+ sharedItem:
+ outboundState.sharedItem &&
+ deserializeFeedLayoutItemWithFollowData(outboundState.sharedItem),
+ chatChannelItems: [],
+ items: {
+ ...convertObjectDatesToFirestoreTimestamps(
+ outboundState.items,
+ ["firstDocTimestamp", "lastDocTimestamp"],
+ ),
+ data:
+ outboundState.items.data &&
+ outboundState.items.data.map(deserializeFeedLayoutItemWithFollowData),
+ },
+ }),
+ { whitelist: ["inbox"] },
+);
diff --git a/yarn.lock b/yarn.lock
index e4e8ed095d..a0cc55ab8e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -17145,6 +17145,11 @@ redux-freeze@^0.1.7:
dependencies:
deep-freeze-strict "1.1.1"
+redux-persist@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
+ integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==
+
redux-saga@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.1.3.tgz#9f3e6aebd3c994bbc0f6901a625f9a42b51d1112"