Skip to content

Commit

Permalink
Merge pull request #2067 from daostack/bugfix/CW-2052-navbar-update-o…
Browse files Browse the repository at this point in the history
…n-stream-creation

Auto refresh the navbar upon creation of a new space by another user #2052
  • Loading branch information
andreymikhadyuk authored Sep 12, 2023
2 parents ee46317 + d21da19 commit fd3b06e
Show file tree
Hide file tree
Showing 17 changed files with 284 additions and 78 deletions.
6 changes: 4 additions & 2 deletions src/events/CommonEventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ export enum CommonEvent {
CommonCreated = "common-created",
CommonUpdated = "common-updated",
CommonDeleted = "common-deleted",
ProjectCreated = "project-created",
ProjectCreatedOrUpdated = "project-created-or-updated",
ProjectUpdated = "project-updated",
}

export interface CommonEventToListener {
[CommonEvent.CommonCreated]: (common: Common) => void;
[CommonEvent.CommonUpdated]: (common: Common) => void;
[CommonEvent.CommonDeleted]: (deletedCommonId: string) => void;
[CommonEvent.ProjectCreated]: (projectsStateItem: ProjectsStateItem) => void;
[CommonEvent.ProjectCreatedOrUpdated]: (
projectsStateItem: ProjectsStateItem,
) => void;
[CommonEvent.ProjectUpdated]: (
projectsStateItem: { commonId: string } & Partial<
Omit<ProjectsStateItem, "commonId">
Expand Down
23 changes: 12 additions & 11 deletions src/pages/App/handlers/CommonHandler/CommonHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,21 @@ const CommonHandler: FC = () => {
}, []);

useEffect(() => {
const handler: CommonEventToListener[CommonEvent.ProjectCreated] = (
projectsStateItem,
) => {
dispatch(
multipleSpacesLayoutActions.addProjectToBreadcrumbs(projectsStateItem),
);
dispatch(commonLayoutActions.addProject(projectsStateItem));
dispatch(projectsActions.addProject(projectsStateItem));
};
const handler: CommonEventToListener[CommonEvent.ProjectCreatedOrUpdated] =
(projectsStateItem) => {
dispatch(
multipleSpacesLayoutActions.addOrUpdateProjectInBreadcrumbs(
projectsStateItem,
),
);
dispatch(commonLayoutActions.addOrUpdateProject(projectsStateItem));
dispatch(projectsActions.addOrUpdateProject(projectsStateItem));
};

CommonEventEmitter.on(CommonEvent.ProjectCreated, handler);
CommonEventEmitter.on(CommonEvent.ProjectCreatedOrUpdated, handler);

return () => {
CommonEventEmitter.off(CommonEvent.ProjectCreated, handler);
CommonEventEmitter.off(CommonEvent.ProjectCreatedOrUpdated, handler);
};
}, []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,10 @@ export default function CreateCommonModal(props: CreateCommonModalProps) {
notificationsAmount: 0,
};

CommonEventEmitter.emit(CommonEvent.ProjectCreated, projectsStateItem);
CommonEventEmitter.emit(
CommonEvent.ProjectCreatedOrUpdated,
projectsStateItem,
);
CommonEventEmitter.emit(
CommonEvent.CommonCreated,
createdCommonData.common,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const CommonCreation: FC = () => {
(circle) => circle.allowedActions[GovernanceActions.CREATE_PROJECT],
);

CommonEventEmitter.emit(CommonEvent.ProjectCreated, {
CommonEventEmitter.emit(CommonEvent.ProjectCreatedOrUpdated, {
commonId: common.id,
image: common.image,
name: common.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ const ProjectCreation: FC<ProjectCreationProps> = (props) => {
};

dispatch(commonActions.setIsNewProjectCreated(true));
CommonEventEmitter.emit(CommonEvent.ProjectCreated, projectsStateItem);
CommonEventEmitter.emit(
CommonEvent.ProjectCreatedOrUpdated,
projectsStateItem,
);
}
CommonEventEmitter.emit(CommonEvent.CommonUpdated, createdProject);
history.push(getCommonPagePath(createdProject.id));
Expand Down
5 changes: 5 additions & 0 deletions src/services/Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "@/shared/models";
import {
convertObjectDatesToFirestoreTimestamps,
emptyFunction,
firestoreDataConverter,
transformFirebaseDataList,
} from "@/shared/utils";
Expand Down Expand Up @@ -296,6 +297,10 @@ class CommonService {
parentCommonId: string,
callback: (data: { common: Common; isRemoved: boolean }[]) => void,
): UnsubscribeFunction => {
if (!parentCommonId) {
return emptyFunction;
}

const query = firebase
.firestore()
.collection(Collection.Daos)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getParentItemIds,
Item,
} from "../../../../SidenavLayout/components/SidenavContent/components";
import { useProjectsSubscription } from "./useProjectsSubscription";

interface Return {
currentCommonId: string | null;
Expand Down Expand Up @@ -83,6 +84,12 @@ export const useProjectsData = (): Return => {
const itemIdWithNewProjectCreation = getItemIdWithNewProjectCreationByPath(
location.pathname,
);
useProjectsSubscription({
activeItemId,
areProjectsFetched,
commons,
projects,
});

useEffect(() => {
if (isAuthenticated) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { CommonEvent, CommonEventEmitter } from "@/events";
import { selectUser } from "@/pages/Auth/store/selectors";
import { CommonService, GovernanceService, Logger } from "@/services";
import { GovernanceActions } from "@/shared/constants";
import { Common } from "@/shared/models";
import { generateCirclesDataForCommonMember } from "@/shared/utils";
import { ProjectsStateItem } from "@/store/states";

interface Data {
activeItemId: string;
areProjectsFetched: boolean;
commons: ProjectsStateItem[];
projects: ProjectsStateItem[];
}

const getProjectItemFromCommon = async (
common: Common,
userId?: string,
initialItem?: ProjectsStateItem,
): Promise<ProjectsStateItem> => {
const baseItem: Omit<
ProjectsStateItem,
"hasMembership" | "hasPermissionToAddProject"
> = {
commonId: common.id,
image: common.image,
name: common.name,
directParent: common.directParent,
};

if (initialItem) {
return {
...baseItem,
hasMembership: initialItem.hasMembership,
hasPermissionToAddProject: initialItem.hasPermissionToAddProject,
};
}
if (!userId) {
return {
...baseItem,
hasMembership: false,
hasPermissionToAddProject: false,
};
}

const [governance, commonMember] = await Promise.all([
GovernanceService.getGovernanceByCommonId(common.id),
CommonService.getCommonMemberByUserId(common.id, userId),
]);

return {
...baseItem,
hasMembership: Boolean(commonMember),
hasPermissionToAddProject: Boolean(
governance &&
commonMember &&
generateCirclesDataForCommonMember(
governance.circles,
commonMember.circleIds,
).allowedActions[GovernanceActions.CREATE_PROJECT],
),
};
};

export const useProjectsSubscription = (data: Data) => {
const { activeItemId, areProjectsFetched, commons, projects } = data;
const [updatedItemsQueue, setUpdatedItemsQueue] = useState<Common[][]>([]);
const user = useSelector(selectUser());
const userId = user?.uid;
const nextUpdatedItems = updatedItemsQueue[0];

useEffect(() => {
if (!areProjectsFetched) {
return;
}

const unsubscribe = CommonService.subscribeToSubCommons(
activeItemId,
(data) => {
const commons = data.reduce<Common[]>((acc, { common, isRemoved }) => {
if (!isRemoved) {
CommonEventEmitter.emit(CommonEvent.CommonUpdated, common);
return acc.concat(common);
}

return acc;
}, []);

if (commons.length !== 0) {
setUpdatedItemsQueue((currentItems) =>
currentItems.concat([commons]),
);
}
},
);

return unsubscribe;
}, [areProjectsFetched, activeItemId]);

useEffect(() => {
if (!nextUpdatedItems) {
return;
}
if (nextUpdatedItems.length === 0) {
setUpdatedItemsQueue((currentItems) => currentItems.slice(1));
return;
}

(async () => {
try {
const items = await Promise.all(
nextUpdatedItems.map(async (nextUpdatedItem) => {
const existingItem =
commons.find((item) => item.commonId === nextUpdatedItem.id) ||
projects.find((item) => item.commonId === nextUpdatedItem.id);

return await getProjectItemFromCommon(
nextUpdatedItem,
userId,
existingItem,
);
}),
);

items.forEach((item) => {
CommonEventEmitter.emit(CommonEvent.ProjectCreatedOrUpdated, item);
});
} catch (error) {
Logger.error(error);
} finally {
setUpdatedItemsQueue((currentItems) => currentItems.slice(1));
}
})();
}, [nextUpdatedItems]);
};
4 changes: 2 additions & 2 deletions src/store/states/commonLayout/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export const getProjects = createAsyncAction(
CommonLayoutActionType.GET_PROJECTS_FAILURE,
)<string, ProjectsStateItem[], Error>();

export const addProject = createStandardAction(
CommonLayoutActionType.ADD_PROJECT,
export const addOrUpdateProject = createStandardAction(
CommonLayoutActionType.ADD_OR_UPDATE_PROJECT,
)<ProjectsStateItem>();

export const updateCommonOrProject = createStandardAction(
Expand Down
2 changes: 1 addition & 1 deletion src/store/states/commonLayout/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export enum CommonLayoutActionType {
GET_PROJECTS_SUCCESS = "@COMMON_LAYOUT/GET_PROJECTS_SUCCESS",
GET_PROJECTS_FAILURE = "@COMMON_LAYOUT/GET_PROJECTS_FAILURE",

ADD_PROJECT = "@COMMON_LAYOUT/ADD_PROJECT",
ADD_OR_UPDATE_PROJECT = "@COMMON_LAYOUT/ADD_OR_UPDATE_PROJECT",

UPDATE_COMMON_OR_PROJECT = "@COMMON_LAYOUT/UPDATE_COMMON_OR_PROJECT",

Expand Down
64 changes: 40 additions & 24 deletions src/store/states/commonLayout/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { WritableDraft } from "immer/dist/types/types-external";
import { ActionType, createReducer } from "typesafe-actions";
import { getAllNestedItems } from "../projects/utils";
import * as actions from "./actions";
import { CommonLayoutState } from "./types";
import { CommonLayoutState, ProjectsStateItem } from "./types";

type Action = ActionType<typeof actions>;

Expand All @@ -26,6 +26,37 @@ const clearData = (state: WritableDraft<CommonLayoutState>): void => {
state.areProjectsFetched = false;
};

const updateCommonOrProject = (
state: WritableDraft<CommonLayoutState>,
payload: { commonId: string } & Partial<Omit<ProjectsStateItem, "commonId">>,
): boolean => {
const commonItemIndex = state.commons.findIndex(
(item) => item.commonId === payload.commonId,
);

if (commonItemIndex > -1) {
state.commons[commonItemIndex] = {
...state.commons[commonItemIndex],
...payload,
};
return true;
}

const projectItemIndex = state.projects.findIndex(
(item) => item.commonId === payload.commonId,
);

if (projectItemIndex > -1) {
state.projects[projectItemIndex] = {
...state.projects[projectItemIndex],
...payload,
};
return true;
}

return false;
};

export const reducer = createReducer<CommonLayoutState, Action>(initialState)
.handleAction(actions.getCommons.request, (state) =>
produce(state, (nextState) => {
Expand Down Expand Up @@ -67,8 +98,14 @@ export const reducer = createReducer<CommonLayoutState, Action>(initialState)
nextState.areProjectsFetched = true;
}),
)
.handleAction(actions.addProject, (state, { payload }) =>
.handleAction(actions.addOrUpdateProject, (state, { payload }) =>
produce(state, (nextState) => {
const isUpdated = updateCommonOrProject(nextState, payload);

if (isUpdated) {
return;
}

if (!payload.directParent) {
nextState.commons.push(payload);
} else {
Expand All @@ -78,28 +115,7 @@ export const reducer = createReducer<CommonLayoutState, Action>(initialState)
)
.handleAction(actions.updateCommonOrProject, (state, { payload }) =>
produce(state, (nextState) => {
const commonItemIndex = nextState.commons.findIndex(
(item) => item.commonId === payload.commonId,
);

if (commonItemIndex > -1) {
nextState.commons[commonItemIndex] = {
...nextState.commons[commonItemIndex],
...payload,
};
return;
}

const projectItemIndex = nextState.projects.findIndex(
(item) => item.commonId === payload.commonId,
);

if (projectItemIndex > -1) {
nextState.projects[projectItemIndex] = {
...nextState.projects[projectItemIndex],
...payload,
};
}
updateCommonOrProject(nextState, payload);
}),
)
.handleAction(actions.setCurrentCommonId, (state, { payload }) =>
Expand Down
4 changes: 2 additions & 2 deletions src/store/states/multipleSpacesLayout/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export const moveBreadcrumbsToPrevious = createStandardAction(
MultipleSpacesLayoutActionType.MOVE_BREADCRUMBS_TO_PREVIOUS,
)();

export const addProjectToBreadcrumbs = createStandardAction(
MultipleSpacesLayoutActionType.ADD_PROJECT_TO_BREADCRUMBS,
export const addOrUpdateProjectInBreadcrumbs = createStandardAction(
MultipleSpacesLayoutActionType.ADD_OR_UPDATE_PROJECT_IN_BREADCRUMBS,
)<ProjectsStateItem>();

export const updateProjectInBreadcrumbs = createStandardAction(
Expand Down
2 changes: 1 addition & 1 deletion src/store/states/multipleSpacesLayout/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export enum MultipleSpacesLayoutActionType {

MOVE_BREADCRUMBS_TO_PREVIOUS = "@MULTIPLE_SPACES_LAYOUT/MOVE_BREADCRUMBS_TO_PREVIOUS",

ADD_PROJECT_TO_BREADCRUMBS = "@MULTIPLE_SPACES_LAYOUT/ADD_PROJECT_TO_BREADCRUMBS",
ADD_OR_UPDATE_PROJECT_IN_BREADCRUMBS = "@MULTIPLE_SPACES_LAYOUT/ADD_OR_UPDATE_PROJECT_IN_BREADCRUMBS",

UPDATE_PROJECT_IN_BREADCRUMBS = "@MULTIPLE_SPACES_LAYOUT/UPDATE_PROJECT_IN_BREADCRUMBS",

Expand Down
Loading

0 comments on commit fd3b06e

Please sign in to comment.