diff --git a/package.json b/package.json index 27e3cef2de..26375b9ba8 100644 --- a/package.json +++ b/package.json @@ -64,9 +64,9 @@ "redux": "^4.0.4", "redux-saga": "^1.1.3", "reselect": "^4.0.0", - "slate": "^0.87.0", - "slate-history": "^0.86.0", - "slate-react": "^0.88.0", + "slate": "^0.94.1", + "slate-history": "^0.93.0", + "slate-react": "^0.98.1", "styled-components": "^5.2.1", "swiper": "^6.5.9", "typesafe-actions": "^4.4.2", diff --git a/src/pages/Login/containers/LoginContainer/helpers.ts b/src/pages/Login/containers/LoginContainer/helpers.ts index d39167432f..6f6dd12dc2 100644 --- a/src/pages/Login/containers/LoginContainer/helpers.ts +++ b/src/pages/Login/containers/LoginContainer/helpers.ts @@ -13,6 +13,10 @@ const PAGES_WITH_ALLOWED_SIGN_UP = [ ROUTE_PATHS.V04_COMMON_ABOUT_TAB, ROUTE_PATHS.V04_COMMON_MEMBERS_TAB, ROUTE_PATHS.V04_COMMON_WALLET_TAB, + ROUTE_PATHS.COMMON, + ROUTE_PATHS.COMMON_ABOUT_TAB, + ROUTE_PATHS.COMMON_MEMBERS_TAB, + ROUTE_PATHS.COMMON_WALLET_TAB, ]; export const getAuthCode = ( diff --git a/src/pages/OldCommon/components/CommonDetailContainer/ChatComponent/ChatComponent.tsx b/src/pages/OldCommon/components/CommonDetailContainer/ChatComponent/ChatComponent.tsx index 75f77f8eea..dd3bb59c69 100644 --- a/src/pages/OldCommon/components/CommonDetailContainer/ChatComponent/ChatComponent.tsx +++ b/src/pages/OldCommon/components/CommonDetailContainer/ChatComponent/ChatComponent.tsx @@ -274,6 +274,7 @@ export default function ChatComponent({ void; user: User | null; scrollToRepliedMessage: (messageId: string) => void; + commonMember: CommonMember | null; + governanceCircles?: Circles; } const getStaticLinkByChatType = (chatType: ChatType): StaticLinkType => { @@ -38,6 +42,8 @@ export default function ChatMessage({ onMessageDropdownOpen, user, scrollToRepliedMessage, + commonMember, + governanceCircles, }: ChatMessageProps) { const [isEditMode, setEditMode] = useState(false); const createdAtDate = new Date(discussionMessage.createdAt.seconds * 1000); @@ -122,6 +128,8 @@ export default function ChatMessage({ void; governance: Governance; + commonMember: CommonMember | null; } export default function DiscussionItemComponent({ discussion, loadDiscussionDetail, governance, + commonMember, }: DiscussionItemComponentProps) { const [imageError, setImageError] = useState(false); const [circleNames, setCircleNames] = useState(""); @@ -91,6 +93,8 @@ export default function DiscussionItemComponent({ diff --git a/src/pages/OldCommon/components/CommonDetailContainer/ProposalsComponent/ProposalItemComponent.tsx b/src/pages/OldCommon/components/CommonDetailContainer/ProposalsComponent/ProposalItemComponent.tsx index 36f54c7be7..4a17a7c737 100644 --- a/src/pages/OldCommon/components/CommonDetailContainer/ProposalsComponent/ProposalItemComponent.tsx +++ b/src/pages/OldCommon/components/CommonDetailContainer/ProposalsComponent/ProposalItemComponent.tsx @@ -2,7 +2,7 @@ import React from "react"; import { useCommonMember } from "@/pages/OldCommon/hooks"; import { UserAvatar, ElementDropdown } from "@/shared/components"; import { EntityTypes, ProposalsTypes } from "@/shared/constants"; -import { Proposal } from "@/shared/models"; +import { Circles, Proposal } from "@/shared/models"; import { StaticLinkType, formatPrice, @@ -15,11 +15,13 @@ import { VotesComponent } from "../VotesComponent"; interface ProposalItemComponentProps { loadProposalDetail: (payload: Proposal) => void; proposal: Proposal; + governanceCircles?: Circles; } export default function ProposalItemComponent({ proposal, loadProposalDetail, + governanceCircles, }: ProposalItemComponentProps) { const { data: commonMember } = useCommonMember(); @@ -44,6 +46,8 @@ export default function ProposalItemComponent({
e.stopPropagation()}> ); } diff --git a/src/pages/common/components/ChatComponent/ChatComponent.module.scss b/src/pages/common/components/ChatComponent/ChatComponent.module.scss index 0603bd9547..54bcb33c17 100644 --- a/src/pages/common/components/ChatComponent/ChatComponent.module.scss +++ b/src/pages/common/components/ChatComponent/ChatComponent.module.scss @@ -64,12 +64,20 @@ align-items: flex-end; } +.loaderWrapper { + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + .permissionsText { font-size: $mobile-title; - color: $light-gray-2; + color: var(--primary-fill); margin-right: 0.3125rem; width: 100%; text-align: center; + cursor: pointer; } .messageInput { @@ -92,11 +100,8 @@ } .messageInputEmpty { - line-height: 2.625rem; + line-height: 2.25rem; padding-left: 0; - @include tablet { - line-height: 0; - } } .addFilesIcon { diff --git a/src/pages/common/components/ChatComponent/ChatComponent.tsx b/src/pages/common/components/ChatComponent/ChatComponent.tsx index 6cf38b5970..09b0438dbf 100644 --- a/src/pages/common/components/ChatComponent/ChatComponent.tsx +++ b/src/pages/common/components/ChatComponent/ChatComponent.tsx @@ -5,6 +5,7 @@ import React, { useCallback, ChangeEvent, useRef, + ReactNode, } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useDebounce, useMeasure } from "react-use"; @@ -47,6 +48,7 @@ import { TextEditorSize, removeTextEditorEmptyEndLinesValues, countTextEditorEmojiElements, + Loader, } from "@/shared/ui-kit"; import { getUserName, hasPermission, isMobile } from "@/shared/utils"; import { @@ -86,6 +88,8 @@ interface ChatComponentInterface { isHidden: boolean; onMessagesAmountChange?: (newMessagesAmount: number) => void; directParent?: DirectParent | null; + isJoinPending?: boolean; + onJoinCommon?: () => void; onUserClick?: (userId: string) => void; } @@ -124,6 +128,8 @@ export default function ChatComponent({ isCommonMemberFetched, onMessagesAmountChange, directParent, + isJoinPending = false, + onJoinCommon, onUserClick, }: ChatComponentInterface) { const dispatch = useDispatch(); @@ -555,6 +561,22 @@ export default function ChatComponent({ } }, [discussionMessages.length]); + const renderJoinCommonContent = (): ReactNode => { + if (isJoinPending) { + return ( +
+ +
+ ); + } + + return ( + + {directParent ? "Join the space" : "Join the effort"} + + ); + }; + return (
{!isChatChannel && (!commonMember || !hasAccess || isHidden) ? ( - - Only members can send messages - + renderJoinCommonContent() ) : ( <> = (props) => {
{!isMobileVersion && }
- - {pendingCircleName ? "Join circle request..." : "You are a member"} - + {Boolean(pendingCircleName) && ( + Join circle request... + )} {circleNames} {pendingCircleName ? `-> ${pendingCircleName}` : ""} diff --git a/src/pages/common/components/CommonMemberInfo/components/PopoverItem/PopoverItem.module.scss b/src/pages/common/components/CommonMemberInfo/components/PopoverItem/PopoverItem.module.scss index cee6011ced..54f7d890c4 100644 --- a/src/pages/common/components/CommonMemberInfo/components/PopoverItem/PopoverItem.module.scss +++ b/src/pages/common/components/CommonMemberInfo/components/PopoverItem/PopoverItem.module.scss @@ -8,6 +8,8 @@ display: flex; justify-content: space-between; align-items: center; + flex-wrap: wrap; + gap: 0.5rem; width: 100%; padding: 1.125rem 1.5rem; text-decoration: none; @@ -50,14 +52,6 @@ color: $light-gray-2; } -.membersCount { - display: inline-flex; - align-items: center; - font-size: $xxsmall-2; - color: $light-gray-2; - margin: 0; -} - .membersCountLoader { height: 1rem; width: 1rem; diff --git a/src/pages/common/components/CommonMemberInfo/components/PopoverItem/PopoverItem.tsx b/src/pages/common/components/CommonMemberInfo/components/PopoverItem/PopoverItem.tsx index 6b7bafb921..0bc159aee1 100644 --- a/src/pages/common/components/CommonMemberInfo/components/PopoverItem/PopoverItem.tsx +++ b/src/pages/common/components/CommonMemberInfo/components/PopoverItem/PopoverItem.tsx @@ -141,9 +141,8 @@ export const PopoverItem: FC = (props) => { [styles.disabled]: !isMember, })} > - {circleName} + {`${membersCount} ${circleName}${membersCount === 1 ? "" : "s"}`}

- {membersCount} members
diff --git a/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/CommonEntranceInfo.tsx b/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/CommonEntranceInfo.tsx index 9bc7fdecb6..86e7b2f07f 100644 --- a/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/CommonEntranceInfo.tsx +++ b/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/CommonEntranceInfo.tsx @@ -4,6 +4,7 @@ import { useIsTabletView } from "@/shared/hooks/viewport"; import { Common } from "@/shared/models"; import { MemberAdmittanceLimitations } from "@/shared/models/governance/proposals"; import { Container } from "@/shared/ui-kit"; +import { checkIsProject } from "@/shared/utils"; import { CommonCard } from "../../../../../CommonCard"; import { CommonEntranceItem, CommonEntranceJoin } from "./components"; import styles from "./CommonEntranceInfo.module.scss"; @@ -16,7 +17,7 @@ interface CommonEntranceInfoProps { const CommonEntranceInfo: FC = (props) => { const { limitations, withJoinRequest = false, common } = props; - const isProject = Boolean(common.directParent); + const isProject = checkIsProject(common); const isTabletView = useIsTabletView(); return ( diff --git a/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.tsx b/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.tsx index 3fc1d05a81..8d07136727 100644 --- a/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.tsx +++ b/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.tsx @@ -1,4 +1,5 @@ import React, { FC } from "react"; +import { useJoinProjectAutomatically } from "@/pages/common/hooks"; import { useCommonDataContext } from "@/pages/common/providers"; import { Common } from "@/shared/models"; import { Button, ButtonSize, ButtonVariant } from "@/shared/ui-kit"; @@ -12,7 +13,15 @@ interface CommonEntranceJoinProps { const CommonEntranceJoin: FC = (props) => { const { withJoinRequest = false, common, isProject } = props; - const { parentCommon, isJoinAllowed, onJoinCommon } = useCommonDataContext(); + const { parentCommon, commonMember, isJoinAllowed, onJoinCommon } = + useCommonDataContext(); + const { + canJoinProjectAutomatically, + isJoinPending, + onJoinProjectAutomatically, + } = useJoinProjectAutomatically(commonMember, common, parentCommon, { + shouldRedirectToFeed: true, + }); return ( <> @@ -23,12 +32,17 @@ const CommonEntranceJoin: FC = (props) => { join the space.

)} - {withJoinRequest && isJoinAllowed && ( + {withJoinRequest && (isJoinAllowed || isJoinPending) && ( diff --git a/src/pages/common/components/CommonTabPanels/components/FeedTab/components/NewDiscussionCreation/components/PermissionSelection/PermissionSelection.tsx b/src/pages/common/components/CommonTabPanels/components/FeedTab/components/NewDiscussionCreation/components/PermissionSelection/PermissionSelection.tsx index edd5a14ba4..e5bceabf9f 100644 --- a/src/pages/common/components/CommonTabPanels/components/FeedTab/components/NewDiscussionCreation/components/PermissionSelection/PermissionSelection.tsx +++ b/src/pages/common/components/CommonTabPanels/components/FeedTab/components/NewDiscussionCreation/components/PermissionSelection/PermissionSelection.tsx @@ -30,7 +30,7 @@ const PermissionSelection: FC = (props) => { .filter((circle) => userCircleIds?.includes(circle.id)) .map((circle) => ({ id: `${Permission.Private}_${circle.id}`, - text: circle.name, + text: `${circle.name}s`, onClick: () => onCircleSave(circle), })); @@ -44,7 +44,7 @@ const PermissionSelection: FC = (props) => { ]; const buttonEl = ( ); diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index 40d8173e7b..8381d712a2 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -6,6 +6,7 @@ import { DeletePrompt, GlobalOverlay, ReportModal } from "@/shared/components"; import { EntityTypes } from "@/shared/constants"; import { useModal, useNotification } from "@/shared/hooks"; import { + useCommon, useDiscussionById, useFeedItemFollow, useFeedItemUserMetadata, @@ -53,8 +54,6 @@ interface DiscussionFeedCardProps { getNonAllowedItems?: GetNonAllowedItemsOptions; onActiveItemDataChange?: (data: FeedLayoutItemChangeData) => void; directParent?: DirectParent | null; - commonDescription?: string; - commonGallery?: CommonLink[]; onUserSelect?: (userId: string, commonId?: string) => void; } @@ -80,8 +79,6 @@ const DiscussionFeedCard: FC = (props) => { getNonAllowedItems, onActiveItemDataChange, directParent, - commonDescription, - commonGallery, onUserSelect, } = props; const { @@ -110,11 +107,13 @@ const DiscussionFeedCard: FC = (props) => { data: discussion, fetched: isDiscussionFetched, } = useDiscussionById(); + const isHome = discussion?.predefinedType === PredefinedTypes.General; const { data: feedItemUserMetadata, fetched: isFeedItemUserMetadataFetched, fetchFeedItemUserMetadata, } = useFeedItemUserMetadata(); + const { data: common } = useCommon(isHome ? commonId : ""); const feedItemFollow = useFeedItemFollow(item.id, commonId); const menuItems = useMenuItems( { @@ -232,8 +231,6 @@ const DiscussionFeedCard: FC = (props) => { ? getVisibilityString(governanceCircles, discussion?.circleVisibility) : undefined; - const isHome = discussion?.predefinedType === PredefinedTypes.General; - return ( <> {!isHome && ( @@ -262,8 +259,8 @@ const DiscussionFeedCard: FC = (props) => { /> )} { onHover(true); diff --git a/src/pages/common/components/FeedCard/utils/getVisibilityString.ts b/src/pages/common/components/FeedCard/utils/getVisibilityString.ts index 1759e441d6..a449f92cfe 100644 --- a/src/pages/common/components/FeedCard/utils/getVisibilityString.ts +++ b/src/pages/common/components/FeedCard/utils/getVisibilityString.ts @@ -16,7 +16,7 @@ export const getVisibilityString = ( circleVisibility, ); const circleNames = getCirclesWithLowestTier(filteredByIdCircles) - .map(({ name }) => name) + .map(({ name }) => `${name}s`) .join(", "); const memberSpecific = @@ -25,7 +25,7 @@ export const getVisibilityString = ( /** * Temporary hide memberSpecific. See https://github.com/daostack/common-web/issues/1529 */ - //return circleNames ? `Private, ${circleNames} ${memberSpecific}` : "Public"; + //return circleNames ? `${circleNames} ${memberSpecific}` : "Public"; - return circleNames ? `Private, ${circleNames}` : "Public"; + return circleNames ? circleNames : "Public"; }; diff --git a/src/pages/common/components/FeedItem/FeedItem.tsx b/src/pages/common/components/FeedItem/FeedItem.tsx index f3a6934efd..e22d6d3c13 100644 --- a/src/pages/common/components/FeedItem/FeedItem.tsx +++ b/src/pages/common/components/FeedItem/FeedItem.tsx @@ -40,8 +40,6 @@ interface FeedItemProps { commonId?: string, ) => void; directParent?: DirectParent | null; - commonDescription?: string; - commonGallery?: CommonLink[]; } const FeedItem: FC = (props) => { @@ -65,8 +63,6 @@ const FeedItem: FC = (props) => { shouldCheckItemVisibility = true, onActiveItemDataChange, directParent, - commonDescription, - commonGallery, } = props; const { onFeedItemUpdate, getLastMessage, getNonAllowedItems, onUserSelect } = useFeedItemContext(); @@ -110,13 +106,7 @@ const FeedItem: FC = (props) => { }; if (item.data.type === CommonFeedType.Discussion) { - return ( - - ); + return ; } if (item.data.type === CommonFeedType.Proposal) { diff --git a/src/pages/common/components/FeedItem/components/ProjectFeedItem/ProjectFeedItem.tsx b/src/pages/common/components/FeedItem/components/ProjectFeedItem/ProjectFeedItem.tsx index 8e3ee923aa..e1b1ac96f0 100644 --- a/src/pages/common/components/FeedItem/components/ProjectFeedItem/ProjectFeedItem.tsx +++ b/src/pages/common/components/FeedItem/components/ProjectFeedItem/ProjectFeedItem.tsx @@ -1,6 +1,7 @@ import React, { FC, ReactNode, useEffect } from "react"; import { useHistory } from "react-router-dom"; import classNames from "classnames"; +import { useCommonMember } from "@/pages/OldCommon/hooks"; import { useFeedItemContext } from "@/pages/common"; import { useRoutesContext } from "@/shared/contexts"; import { useCommon } from "@/shared/hooks/useCases"; @@ -21,7 +22,21 @@ export const ProjectFeedItem: FC = (props) => { const { getCommonPagePath } = useRoutesContext(); const { renderFeedItemBaseContent } = useFeedItemContext(); const { data: common, fetched: isCommonFetched, fetchCommon } = useCommon(); + const { + fetched: isCommonMemberFetched, + data: commonMember, + fetchCommonMember, + } = useCommonMember(); const commonId = item.data.id; + const unreadStreamsCount = + commonMember?.streamsUnreadCountByProjectStream[item.id] ?? null; + const lastMessage = parseStringToTextEditorValue( + unreadStreamsCount !== null + ? `${unreadStreamsCount} updated stream${ + unreadStreamsCount === 1 ? "" : "s" + }` + : undefined, + ); const isProject = checkIsProject(common); const titleEl = ( <> @@ -50,6 +65,10 @@ export const ProjectFeedItem: FC = (props) => { fetchCommon(commonId); }, [commonId]); + useEffect(() => { + fetchCommonMember(item.commonId); + }, [fetchCommonMember, item.commonId]); + return ( ( <> @@ -57,14 +76,16 @@ export const ProjectFeedItem: FC = (props) => { className: styles.container, titleWrapperClassName: styles.titleWrapper, lastActivity: item.updatedAt.seconds * 1000, - unreadMessages: 0, + unreadMessages: + commonMember?.unreadCountByProjectStream[item.id] ?? 0, isMobileView: isMobileVersion, title: titleEl, onClick: handleClick, seenOnce: true, - isLoading: !isCommonFetched, + isLoading: !isCommonFetched || !isCommonMemberFetched, + lastMessage, renderLeftContent, - shouldHideBottomContent: true, + shouldHideBottomContent: !lastMessage, })} ) || null diff --git a/src/pages/common/components/FeedItems/FeedItems.tsx b/src/pages/common/components/FeedItems/FeedItems.tsx index fac687256f..c2119d721a 100644 --- a/src/pages/common/components/FeedItems/FeedItems.tsx +++ b/src/pages/common/components/FeedItems/FeedItems.tsx @@ -83,8 +83,6 @@ const FeedItems: FC = (props) => { userCircleIds={userCircleIds} isPreviewMode directParent={common.directParent} - commonDescription={common.description} - commonGallery={common.gallery} /> ); })} diff --git a/src/pages/common/hooks/index.ts b/src/pages/common/hooks/index.ts index ae2320207f..2e5717d23b 100644 --- a/src/pages/common/hooks/index.ts +++ b/src/pages/common/hooks/index.ts @@ -1 +1,2 @@ export * from "./useFeedItemSubscription"; +export * from "./useJoinProjectAutomatically"; diff --git a/src/pages/common/hooks/useJoinProjectAutomatically.ts b/src/pages/common/hooks/useJoinProjectAutomatically.ts new file mode 100644 index 0000000000..41c89ebfd2 --- /dev/null +++ b/src/pages/common/hooks/useJoinProjectAutomatically.ts @@ -0,0 +1,107 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useSelector } from "react-redux"; +import { useHistory } from "react-router-dom"; +import { selectUser } from "@/pages/Auth/store/selectors"; +import { ProposalService } from "@/services"; +import { ProposalsTypes } from "@/shared/constants"; +import { useNotification } from "@/shared/hooks"; +import { useGovernance } from "@/shared/hooks/useCases"; +import { Common, CommonMember } from "@/shared/models"; +import { getCommonPagePath, getUserName } from "@/shared/utils"; + +interface Return { + canJoinProjectAutomatically: boolean; + isJoinPending: boolean; + onJoinProjectAutomatically: () => void; +} + +interface Options { + shouldRedirectToFeed: boolean; +} + +export const useJoinProjectAutomatically = ( + commonMember: CommonMember | null, + common?: Common, + parentCommon?: Common, + options?: Options, +): Return => { + const history = useHistory(); + const { notify } = useNotification(); + const user = useSelector(selectUser()); + const [isJoinPending, setIsJoinPending] = useState(false); + const [ + shouldRedirectToFeedOnCommonMemberExistence, + setShouldRedirectToFeedOnCommonMemberExistence, + ] = useState(false); + const { data: parentGovernance, fetchGovernance } = useGovernance(); + const circleId = common?.directParent?.circleId; + + const canJoinProjectAutomatically = useMemo(() => { + if (!circleId || !parentGovernance) { + return false; + } + + const assignCircleProposals = + parentGovernance.proposals[ProposalsTypes.ASSIGN_CIRCLE]; + const global = + assignCircleProposals && assignCircleProposals[circleId]?.global; + const { votingDuration, minApprove, quorum } = global ?? {}; + + return votingDuration === 0 && minApprove === 0 && quorum === 0; + }, [parentGovernance?.id, circleId]); + + useEffect(() => { + if (parentCommon?.governanceId) { + fetchGovernance(parentCommon.governanceId); + } + }, [parentCommon?.governanceId]); + + useEffect(() => { + if (shouldRedirectToFeedOnCommonMemberExistence && common && commonMember) { + setIsJoinPending(false); + history.push(getCommonPagePath(common.id)); + } + }, [common?.id, shouldRedirectToFeedOnCommonMemberExistence, commonMember]); + + const onJoinProjectAutomatically = useCallback(async () => { + if (!user || !parentCommon || !parentGovernance || !circleId) { + return; + } + + const circleName = Object.values(parentGovernance.circles).find( + ({ id }) => id === circleId, + )?.name; + + setIsJoinPending(true); + + try { + await ProposalService.createAssignProposal({ + args: { + commonId: parentCommon.id, + userId: user?.uid, + circleId, + title: `Request to join ${circleName} circle by ${getUserName(user)}`, + description: "Joining space", + images: [], + files: [], + links: [], + }, + }); + + if (options?.shouldRedirectToFeed) { + setShouldRedirectToFeedOnCommonMemberExistence(true); + } + } catch (err) { + setIsJoinPending(false); + notify( + "Something went wrong. Please try again later or contact us at: support@common.io", + ); + } + }, [user?.uid, parentCommon?.id, parentGovernance?.id, circleId]); + + return { + isJoinPending, + canJoinProjectAutomatically, + onJoinProjectAutomatically, + }; +}; diff --git a/src/pages/common/providers/CommonData/CommonData.tsx b/src/pages/common/providers/CommonData/CommonData.tsx index 13007a5d41..3a7b44fb62 100644 --- a/src/pages/common/providers/CommonData/CommonData.tsx +++ b/src/pages/common/providers/CommonData/CommonData.tsx @@ -17,7 +17,7 @@ import { Governance, SupportersData, } from "@/shared/models"; -import { getProjectCreationPagePath } from "@/shared/utils"; +import { checkIsProject, getProjectCreationPagePath } from "@/shared/utils"; import { projectsActions } from "@/store/states"; import { getDefaultGovDocUrl } from "../../components/CommonTabPanels/components/AboutTab/utils"; import { JoinProjectModal } from "../../components/JoinProjectModal"; @@ -112,15 +112,15 @@ const CommonData: FC = (props) => { onOpen: onProjectJoinModalOpen, onClose: onProjectJoinModalClose, } = useAuthorizedModal(); - const isProject = Boolean(common.directParent); + const isProject = checkIsProject(common); const isJoinPending = isGlobalDataFetched && !commonMember && props.isJoinPending; const isJoinAllowed = Boolean( isGlobalDataFetched && + !isJoinPending && ((!isProject && !commonMember) || - (isProject && parentCommonMember && !commonMember)) && - !isJoinPending, + (isProject && parentCommonMember && !commonMember)), ); const handleMenuItemSelect = useCallback( @@ -215,6 +215,7 @@ const CommonData: FC = (props) => { onMenuItemSelect: handleMenuItemSelect, onProjectCreate: handleProjectCreate, common, + commonMember, governance, parentCommons, subCommons, @@ -232,6 +233,7 @@ const CommonData: FC = (props) => { handleMenuItemSelect, handleProjectCreate, common, + commonMember, governance, parentCommons, subCommons, diff --git a/src/pages/common/providers/CommonData/context.ts b/src/pages/common/providers/CommonData/context.ts index 41d9a153cc..2bd24b53a5 100644 --- a/src/pages/common/providers/CommonData/context.ts +++ b/src/pages/common/providers/CommonData/context.ts @@ -1,7 +1,14 @@ import { createContext, useContext } from "react"; import { CreateProposal } from "@/pages/OldCommon/interfaces"; import { ProposalsTypes } from "@/shared/constants"; -import { Circle, Common, Governance, SupportersData } from "@/shared/models"; +import { + Circle, + CirclesPermissions, + Common, + CommonMember, + Governance, + SupportersData, +} from "@/shared/models"; import { CommonMenuItem } from "../../constants"; import { CommonPageSettings } from "../../types"; @@ -10,6 +17,7 @@ interface Data { onMenuItemSelect: (menuItem: CommonMenuItem | null) => void; onProjectCreate: () => void; common: Common; + commonMember: (CommonMember & CirclesPermissions) | null; governance: Governance; parentCommons: Common[]; subCommons: Common[]; diff --git a/src/pages/commonFeed/CommonFeed.tsx b/src/pages/commonFeed/CommonFeed.tsx index b86962229d..6cfe02161f 100644 --- a/src/pages/commonFeed/CommonFeed.tsx +++ b/src/pages/commonFeed/CommonFeed.tsx @@ -79,6 +79,8 @@ const CommonFeedComponent: FC = (props) => { const queryParams = useQueryParams(); const dispatch = useDispatch(); const history = useHistory(); + const user = useSelector(selectUser()); + const userId = user?.uid; const recentStreamId = useSelector(selectRecentStreamId); const [feedLayoutRef, setFeedLayoutRef] = useState( null, @@ -94,8 +96,10 @@ const CommonFeedComponent: FC = (props) => { data: commonData, fetched: isCommonDataFetched, fetchCommonData, - } = useCommonData(); + } = useCommonData(userId); const parentCommonId = commonData?.common.directParent?.commonId; + const isRootCommon = !parentCommonId; + const isRootCommonMember = Boolean(commonData?.rootCommonMember); const anotherCommonId = userCommonIds[0] === commonId ? userCommonIds[1] : userCommonIds[0]; const pinnedItemIds = useMemo( @@ -138,8 +142,6 @@ const CommonFeedComponent: FC = (props) => { } = useCommonFeedItems(commonId, commonFeedItemIdsForNotListening); const sharedFeedItem = useSelector(selectSharedFeedItem); - const user = useSelector(selectUser()); - const userId = user?.uid; const topFeedItems = useMemo(() => { const items: FeedLayoutItem[] = []; const filteredPinnedItems = @@ -158,7 +160,7 @@ const CommonFeedComponent: FC = (props) => { }, [sharedFeedItem, sharedFeedItemId, commonPinnedFeedItems]); const firstItem = commonFeedItems?.[0]; const isDataFetched = isCommonDataFetched; - const hasAccessToPage = Boolean(commonMember); + const hasPublicItem = true; const fetchData = () => { fetchCommonData({ @@ -192,10 +194,21 @@ const CommonFeedComponent: FC = (props) => { ); useEffect(() => { - if (!user || (isGlobalDataFetched && !commonMember)) { + if (!isCommonDataFetched || !isGlobalDataFetched || commonMember) { + return; + } + if (!hasPublicItem && !isRootCommon && !isRootCommonMember) { history.replace(getCommonPageAboutTabPath(commonId)); } - }, [user, isGlobalDataFetched, commonMember, commonId]); + }, [ + isCommonDataFetched, + isGlobalDataFetched, + commonMember, + hasPublicItem, + isRootCommon, + isRootCommonMember, + commonId, + ]); useEffect(() => { dispatch(commonActions.setSharedFeedItemId(sharedFeedItemId)); @@ -240,8 +253,8 @@ const CommonFeedComponent: FC = (props) => { checkIsFeedItemFollowLayoutItem(firstItem) && recentStreamId === firstItem.feedItem.data.id ) { + feedLayoutRef?.setShouldAllowChatAutoOpen(true); feedLayoutRef?.setExpandedFeedItemId(firstItem.feedItem.id); - dispatch(commonActions.setRecentStreamId("")); } }, [feedLayoutRef, recentStreamId, firstItem]); @@ -340,12 +353,12 @@ const CommonFeedComponent: FC = (props) => { } common={commonData.common} + parentCommon={commonData.parentCommon} governance={commonData.governance} commonMember={commonMember} topFeedItems={topFeedItems} feedItems={commonFeedItems} - loading={areCommonFeedItemsLoading || !hasAccessToPage} - shouldHideContent={!hasAccessToPage} + loading={areCommonFeedItemsLoading} onFetchNext={fetchMoreCommonFeedItems} renderFeedItemBaseContent={renderFeedItemBaseContent} onFeedItemUpdate={handleFeedItemUpdate} diff --git a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx index 1f1bf92272..85616f1b7f 100644 --- a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx +++ b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx @@ -9,11 +9,12 @@ import React, { useMemo, useState, } from "react"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { useHistory } from "react-router-dom"; import { useWindowSize } from "react-use"; import classNames from "classnames"; import { selectUser } from "@/pages/Auth/store/selectors"; +import { MembershipRequestModal } from "@/pages/OldCommon/components"; import { useCommonMember } from "@/pages/OldCommon/hooks"; import { FeedItem, @@ -28,8 +29,10 @@ import { ChatItem, } from "@/pages/common/components/ChatComponent"; import { ChatContext } from "@/pages/common/components/ChatComponent/context"; +import { JoinProjectModal } from "@/pages/common/components/JoinProjectModal"; +import { useJoinProjectAutomatically } from "@/pages/common/hooks"; import { InboxItemType, QueryParamKey } from "@/shared/constants"; -import { useQueryParams } from "@/shared/hooks"; +import { useAuthorizedModal, useQueryParams } from "@/shared/hooks"; import { useGovernanceByCommonId } from "@/shared/hooks/useCases"; import { useIsTabletView } from "@/shared/hooks/viewport"; import { @@ -52,8 +55,13 @@ import { User, } from "@/shared/models"; import { InfiniteScroll, TextEditorValue } from "@/shared/ui-kit"; -import { addQueryParam, deleteQueryParam, getUserName } from "@/shared/utils"; -import { selectRecentStreamId } from "@/store/states"; +import { + addQueryParam, + checkIsProject, + deleteQueryParam, + getUserName, +} from "@/shared/utils"; +import { commonActions, selectRecentStreamId } from "@/store/states"; import { MIN_CHAT_WIDTH } from "../../constants"; import { DesktopChat, @@ -68,6 +76,7 @@ import { import { useUserForProfile } from "./hooks"; import { checkShouldAutoOpenPreview, + getChatChannelItemByUserIds, getDefaultSize, getItemCommonData, getSplitViewMaxSize, @@ -94,6 +103,7 @@ interface FeedLayoutProps { ) => ReactNode; topContent?: ReactNode; common?: Common; + parentCommon?: Common; governance?: Governance; commonMember: (CommonMember & CirclesPermissions) | null; feedItems: FeedLayoutItem[] | null; @@ -129,6 +139,7 @@ const FeedLayout: ForwardRefRenderFunction = ( common: outerCommon, governance: outerGovernance, commonMember: outerCommonMember, + parentCommon, feedItems, topFeedItems = [], loading, @@ -147,6 +158,7 @@ const FeedLayout: ForwardRefRenderFunction = ( outerStyles, settings, } = props; + const dispatch = useDispatch(); const { width: windowWidth } = useWindowSize(); const history = useHistory(); const queryParams = useQueryParams(); @@ -170,9 +182,29 @@ const FeedLayout: ForwardRefRenderFunction = ( } = useCommonMember({ shouldAutoReset: false, }); + const { + isModalOpen: isCommonJoinModalOpen, + onOpen: onCommonJoinModalOpen, + onClose: onCommonJoinModalClose, + } = useAuthorizedModal(); + const { + isModalOpen: isProjectJoinModalOpen, + onOpen: onProjectJoinModalOpen, + onClose: onProjectJoinModalClose, + } = useAuthorizedModal(); + const commonMember = outerCommonMember || fetchedCommonMember; + const { + canJoinProjectAutomatically, + isJoinPending, + onJoinProjectAutomatically, + } = useJoinProjectAutomatically(commonMember, outerCommon, parentCommon); + const onJoinCommon = checkIsProject(outerCommon) + ? canJoinProjectAutomatically + ? onJoinProjectAutomatically + : onProjectJoinModalOpen + : onCommonJoinModalOpen; const userForProfile = useUserForProfile(); const governance = outerGovernance || fetchedGovernance; - const commonMember = outerCommonMember || fetchedCommonMember; const maxChatSize = settings?.getSplitViewMaxSize?.(windowWidth) ?? getSplitViewMaxSize(windowWidth); @@ -195,6 +227,15 @@ const FeedLayout: ForwardRefRenderFunction = ( return items; }, [topFeedItems, feedItems]); + const chatChannelItemForProfile = useMemo( + () => + getChatChannelItemByUserIds( + allFeedItems, + userId, + userForProfile.userForProfileData?.userId, + ), + [allFeedItems, userId, userForProfile.userForProfileData?.userId], + ); const isContentEmpty = !loading && (!allFeedItems || allFeedItems.length === 0) && emptyText; const chatItemQueryParam = queryParams[QueryParamKey.ChatItem]; @@ -207,6 +248,9 @@ const FeedLayout: ForwardRefRenderFunction = ( ); const feedItemIdForAutoChatOpen = useMemo(() => { + if (chatChannelItemForProfile?.itemId) { + return chatChannelItemForProfile.itemId; + } if ( userForProfile.userForProfileData || shouldAllowChatAutoOpen === false @@ -240,6 +284,7 @@ const FeedLayout: ForwardRefRenderFunction = ( return foundItem?.itemId; }, [ + chatChannelItemForProfile?.itemId, allFeedItems, chatItem?.feedItemId, recentStreamId, @@ -392,12 +437,32 @@ const FeedLayout: ForwardRefRenderFunction = ( title: getUserName(dmUser), image: dmUser.photoURL, }); + + if (!isTabletView) { + setActiveChatItem(null); + } }; const handleProfileClose = () => { userForProfile.resetUserForProfileData(isTabletView); }; + const handleDMClick = () => { + if (checkIsChatChannelLayoutItem(selectedFeedItem)) { + handleProfileClose(); + return; + } + + setActiveChatItem(null); + setShouldAllowChatAutoOpen(true); + }; + + const onDMClick = + checkIsChatChannelLayoutItem(selectedFeedItem) || + (!isTabletView && chatChannelItemForProfile) + ? handleDMClick + : undefined; + useEffect(() => { if (!outerGovernance && selectedItemCommonData?.id) { fetchGovernance(selectedItemCommonData.id); @@ -457,16 +522,23 @@ const FeedLayout: ForwardRefRenderFunction = ( }, [isTabletView, shouldAutoExpandItem, activeFeedItemId]); useEffect(() => { - if (!isTabletView && userForProfile.userForProfileData?.chatChannel) { - setActiveChatItem(null); + if (!recentStreamId || !selectedFeedItem) { + return; } - }, [isTabletView, userForProfile.userForProfileData?.chatChannel || null]); + if ( + !checkIsFeedItemFollowLayoutItem(selectedFeedItem) || + recentStreamId === selectedFeedItem?.feedItem.data.id + ) { + dispatch(commonActions.setRecentStreamId("")); + } + }, [recentStreamId, selectedFeedItem]); useImperativeHandle( ref, () => ({ setExpandedFeedItemId, setActiveItem, + setShouldAllowChatAutoOpen, }), [setActiveItem], ); @@ -535,8 +607,6 @@ const FeedLayout: ForwardRefRenderFunction = ( } onActiveItemDataChange={handleActiveFeedItemDataChange} directParent={outerCommon?.directParent} - commonDescription={outerCommon?.description} - commonGallery={outerCommon?.gallery} /> ); } @@ -569,6 +639,8 @@ const FeedLayout: ForwardRefRenderFunction = ( titleRightContent={followFeedItemEl} onMessagesAmountChange={handleMessagesAmountChange} directParent={outerCommon?.directParent} + onJoinCommon={onJoinCommon} + isJoinPending={isJoinPending} onUserClick={handleUserClick} /> ) : ( @@ -591,6 +663,8 @@ const FeedLayout: ForwardRefRenderFunction = ( onMessagesAmountChange={handleMessagesAmountChange} directParent={outerCommon?.directParent} onClose={handleMobileChatClose} + onJoinCommon={onJoinCommon} + isJoinPending={isJoinPending} onUserClick={handleUserClick} > {selectedItemCommonData && @@ -611,16 +685,32 @@ const FeedLayout: ForwardRefRenderFunction = ( )} )} + {governance && outerCommon && ( + <> + + null} + /> + + )} {userForProfile.userForProfileData && (!isTabletView ? ( = ( userId={userForProfile.userForProfileData.userId} commonId={userForProfile.userForProfileData.commonId} chatChannel={userForProfile.userForProfileData.chatChannel} - shouldCloseOnDMClick={checkIsChatChannelLayoutItem( - selectedFeedItem, - )} + onDMClick={onDMClick} onClose={handleProfileClose} onChatChannelCreate={handleChatChannelCreate} onUserClick={handleUserClick} diff --git a/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx b/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx index 9751a37421..e9ff24767f 100644 --- a/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx +++ b/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx @@ -30,6 +30,8 @@ interface ChatProps { titleRightContent?: ReactNode; onMessagesAmountChange?: (newMessagesAmount: number) => void; directParent?: DirectParent | null; + isJoinPending?: boolean; + onJoinCommon?: () => void; onUserClick?: (userId: string) => void; } @@ -44,6 +46,8 @@ const DesktopChat: FC = (props) => { titleRightContent, onMessagesAmountChange, directParent, + isJoinPending, + onJoinCommon, onUserClick, } = props; const { @@ -115,6 +119,8 @@ const DesktopChat: FC = (props) => { isAuthorized={Boolean(user)} onMessagesAmountChange={onMessagesAmountChange} directParent={directParent} + isJoinPending={isJoinPending} + onJoinCommon={onJoinCommon} onUserClick={onUserClick} /> diff --git a/src/pages/commonFeed/components/FeedLayout/components/DesktopProfile/DesktopProfile.tsx b/src/pages/commonFeed/components/FeedLayout/components/DesktopProfile/DesktopProfile.tsx index 583593ad63..e3fa9d7760 100644 --- a/src/pages/commonFeed/components/FeedLayout/components/DesktopProfile/DesktopProfile.tsx +++ b/src/pages/commonFeed/components/FeedLayout/components/DesktopProfile/DesktopProfile.tsx @@ -3,7 +3,7 @@ import classNames from "classnames"; import { ButtonIcon } from "@/shared/components"; import { ChatChannelToDiscussionConverter } from "@/shared/converters"; import { Close2Icon } from "@/shared/icons"; -import { ChatChannel, User } from "@/shared/models"; +import { ChatChannel, Circles, User } from "@/shared/models"; import { DesktopChat } from "../DesktopChat"; import { DesktopRightPane } from "../DesktopRightPane"; import { ProfileContent } from "../ProfileContent"; @@ -13,8 +13,9 @@ interface DesktopProfileProps { className?: string; userId: string; commonId?: string; + governanceCircles?: Circles; chatChannel?: ChatChannel; - shouldCloseOnDMClick: boolean; + onDMClick?: () => void; withTitle?: boolean; onClose: () => void; onChatChannelCreate: (chatChannel: ChatChannel, dmUser: User) => void; @@ -27,8 +28,9 @@ const DesktopProfile: FC = (props) => { userId, commonId, chatChannel, - shouldCloseOnDMClick, + onDMClick, withTitle, + governanceCircles, onClose, onChatChannelCreate, onUserClick, @@ -46,6 +48,7 @@ const DesktopProfile: FC = (props) => { ChatChannelToDiscussionConverter.toTargetEntity(chatChannel), circleVisibility: [], }} + governanceCircles={governanceCircles} commonId={""} commonMember={null} withTitle={withTitle} @@ -66,8 +69,7 @@ const DesktopProfile: FC = (props) => { className={styles.content} userId={userId} commonId={commonId} - shouldCloseOnDMClick={shouldCloseOnDMClick} - onClose={onClose} + onDMClick={onDMClick} onChatChannelCreate={onChatChannelCreate} onChatChannelLoading={setIsChatChannelLoading} /> diff --git a/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx b/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx index c8d4e8482e..6149f16fc9 100644 --- a/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx +++ b/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx @@ -32,7 +32,9 @@ interface ChatProps { rightHeaderContent?: ReactNode; onMessagesAmountChange?: (newMessagesAmount: number) => void; directParent?: DirectParent | null; + isJoinPending?: boolean; onClose: () => void; + onJoinCommon?: () => void; onUserClick?: (userId: string) => void; } @@ -49,6 +51,8 @@ const MobileChat: FC = (props) => { rightHeaderContent, onMessagesAmountChange, directParent, + isJoinPending, + onJoinCommon, onClose, onUserClick, } = props; @@ -145,6 +149,8 @@ const MobileChat: FC = (props) => { lastSeenItem={chatItem.lastSeenItem} onMessagesAmountChange={onMessagesAmountChange} directParent={directParent} + isJoinPending={isJoinPending} + onJoinCommon={onJoinCommon} onUserClick={onUserClick} /> )} diff --git a/src/pages/commonFeed/components/FeedLayout/components/MobileProfile/MobileProfile.tsx b/src/pages/commonFeed/components/FeedLayout/components/MobileProfile/MobileProfile.tsx index 27439eb7d0..b90b2cc010 100644 --- a/src/pages/commonFeed/components/FeedLayout/components/MobileProfile/MobileProfile.tsx +++ b/src/pages/commonFeed/components/FeedLayout/components/MobileProfile/MobileProfile.tsx @@ -10,7 +10,7 @@ interface MobileProfileProps { userId: string; commonId?: string; chatChannel?: ChatChannel; - shouldCloseOnDMClick: boolean; + onDMClick?: () => void; onClose: () => void; onChatChannelCreate: (chatChannel: ChatChannel, dmUser: User) => void; onUserClick: (userId: string) => void; @@ -21,7 +21,7 @@ const MobileProfile: FC = (props) => { userId, commonId, chatChannel, - shouldCloseOnDMClick, + onDMClick, onClose, onChatChannelCreate, onUserClick, @@ -62,8 +62,7 @@ const MobileProfile: FC = (props) => { className={styles.content} userId={userId} commonId={commonId} - shouldCloseOnDMClick={shouldCloseOnDMClick} - onClose={onClose} + onDMClick={onDMClick} onChatChannelCreate={onChatChannelCreate} onChatChannelLoading={setIsChatChannelLoading} /> diff --git a/src/pages/commonFeed/components/FeedLayout/components/ProfileContent/ProfileContent.tsx b/src/pages/commonFeed/components/FeedLayout/components/ProfileContent/ProfileContent.tsx index e8121ea1db..3ec8fa6b37 100644 --- a/src/pages/commonFeed/components/FeedLayout/components/ProfileContent/ProfileContent.tsx +++ b/src/pages/commonFeed/components/FeedLayout/components/ProfileContent/ProfileContent.tsx @@ -25,8 +25,7 @@ interface ProfileContentProps { className?: string; userId: string; commonId?: string; - shouldCloseOnDMClick: boolean; - onClose: () => void; + onDMClick?: () => void; onChatChannelCreate: (chatChannel: ChatChannel, dmUser: User) => void; onChatChannelLoading?: (isLoading: boolean) => void; } @@ -35,8 +34,7 @@ const ProfileContent: FC = (props) => { const { userId, commonId, - shouldCloseOnDMClick, - onClose, + onDMClick, onChatChannelCreate, onChatChannelLoading, } = props; @@ -82,8 +80,8 @@ const ProfileContent: FC = (props) => { const country = countryList.find(({ value }) => value === user?.country); const handleDMButtonClick = () => { - if (shouldCloseOnDMClick) { - onClose(); + if (onDMClick) { + onDMClick(); } else { fetchDMUserChatChannel(userId); } diff --git a/src/pages/commonFeed/components/FeedLayout/utils/getChatChannelItemByUserIds.ts b/src/pages/commonFeed/components/FeedLayout/utils/getChatChannelItemByUserIds.ts new file mode 100644 index 0000000000..136f5b7903 --- /dev/null +++ b/src/pages/commonFeed/components/FeedLayout/utils/getChatChannelItemByUserIds.ts @@ -0,0 +1,25 @@ +import { + ChatChannelLayoutItem, + checkIsChatChannelLayoutItem, + FeedLayoutItem, +} from "@/shared/interfaces"; + +export const getChatChannelItemByUserIds = ( + items: FeedLayoutItem[], + currentUserId?: string, + dmUserId?: string, +): ChatChannelLayoutItem | null => { + if (!currentUserId || !dmUserId) { + return null; + } + + const isDMWithMyself = currentUserId === dmUserId; + const item = items.find( + (item): item is ChatChannelLayoutItem => + checkIsChatChannelLayoutItem(item) && + item.chatChannel.participants.includes(dmUserId) && + (!isDMWithMyself || item.chatChannel.participants.length === 1), + ); + + return item || null; +}; diff --git a/src/pages/commonFeed/components/FeedLayout/utils/index.ts b/src/pages/commonFeed/components/FeedLayout/utils/index.ts index 20cc38b54e..a6e9fe0793 100644 --- a/src/pages/commonFeed/components/FeedLayout/utils/index.ts +++ b/src/pages/commonFeed/components/FeedLayout/utils/index.ts @@ -1,5 +1,6 @@ export * from "./chatSize"; export * from "./checkShouldAutoOpenPreview"; +export * from "./getChatChannelItemByUserIds"; export * from "./getDefaultSize"; export * from "./getItemCommonData"; export * from "./getSplitViewMaxSize"; diff --git a/src/pages/commonFeed/hooks/useCommonData/index.ts b/src/pages/commonFeed/hooks/useCommonData/index.ts index 112a6eca3e..4f13b5e7c1 100644 --- a/src/pages/commonFeed/hooks/useCommonData/index.ts +++ b/src/pages/commonFeed/hooks/useCommonData/index.ts @@ -1,4 +1,5 @@ import { useCallback, useState } from "react"; +import { last } from "lodash"; import { CommonFeedService, CommonService, @@ -18,7 +19,7 @@ interface Return extends CombinedState { resetCommonData: () => void; } -export const useCommonData = (): Return => { +export const useCommonData = (userId?: string): Return => { const [state, setState] = useState({ loading: false, fetched: false, @@ -30,62 +31,74 @@ export const useCommonData = (): Return => { useCommonSubscription(setState, currentCommonId, state.data?.parentCommons); useGovernanceSubscription(setState, state.data?.governance.id); - const fetchCommonData = useCallback((options: FetchCommonDataOptions) => { - const { commonId, sharedFeedItemId } = options; - setState({ - loading: true, - fetched: false, - data: null, - }); + const fetchCommonData = useCallback( + (options: FetchCommonDataOptions) => { + const { commonId, sharedFeedItemId } = options; + setState({ + loading: true, + fetched: false, + data: null, + }); - (async () => { - try { - const [common, governance, commonMembersAmount, sharedFeedItem] = - await Promise.all([ - CommonService.getCommonById(commonId), - GovernanceService.getGovernanceByCommonId(commonId), - CommonService.getCommonMembersAmount(commonId), - sharedFeedItemId - ? CommonFeedService.getCommonFeedItemById( - commonId, - sharedFeedItemId, - ) - : null, - ]); + (async () => { + try { + const [common, governance, commonMembersAmount, sharedFeedItem] = + await Promise.all([ + CommonService.getCommonById(commonId), + GovernanceService.getGovernanceByCommonId(commonId), + CommonService.getCommonMembersAmount(commonId), + sharedFeedItemId + ? CommonFeedService.getCommonFeedItemById( + commonId, + sharedFeedItemId, + ) + : null, + ]); - if (!common) { - throw new Error(`Couldn't find common by id = ${commonId}`); - } - if (!governance) { - throw new Error(`Couldn't find governance by common id= ${commonId}`); - } + if (!common) { + throw new Error(`Couldn't find common by id = ${commonId}`); + } + if (!governance) { + throw new Error( + `Couldn't find governance by common id= ${commonId}`, + ); + } - const [parentCommons, subCommons] = await Promise.all([ - CommonService.getAllParentCommonsForCommon(common), - CommonService.getCommonsByDirectParentIds([common.id]), - ]); + const rootCommonId = common.directParent?.commonId; + const [parentCommons, subCommons, rootCommonMember] = + await Promise.all([ + CommonService.getAllParentCommonsForCommon(common), + CommonService.getCommonsByDirectParentIds([common.id]), + rootCommonId && userId + ? CommonService.getCommonMemberByUserId(rootCommonId, userId) + : null, + ]); - setState({ - loading: false, - fetched: true, - data: { - common, - governance, - parentCommons, - subCommons, - commonMembersAmount, - sharedFeedItem, - }, - }); - } catch (error) { - setState({ - loading: false, - fetched: true, - data: null, - }); - } - })(); - }, []); + setState({ + loading: false, + fetched: true, + data: { + common, + governance, + parentCommons, + subCommons, + commonMembersAmount, + sharedFeedItem, + rootCommonMember, + parentCommon: last(parentCommons), + }, + }); + } catch (error) { + setState({ + loading: false, + fetched: true, + data: null, + }); + } + })(); + }, + [userId], + ); const resetCommonData = useCallback(() => { setState({ diff --git a/src/pages/commonFeed/hooks/useCommonData/types.ts b/src/pages/commonFeed/hooks/useCommonData/types.ts index 76fe46b21d..5b68f37c7d 100644 --- a/src/pages/commonFeed/hooks/useCommonData/types.ts +++ b/src/pages/commonFeed/hooks/useCommonData/types.ts @@ -1,5 +1,5 @@ import { LoadingState } from "@/shared/interfaces"; -import { Common, CommonFeed, Governance } from "@/shared/models"; +import { Common, CommonFeed, CommonMember, Governance } from "@/shared/models"; export interface Data { common: Common; @@ -8,6 +8,8 @@ export interface Data { subCommons: Common[]; commonMembersAmount: number; sharedFeedItem: CommonFeed | null; + rootCommonMember: CommonMember | null; + parentCommon?: Common; } export type State = LoadingState; diff --git a/src/pages/inbox/components/ChatChannelItem/ChatChannelItem.tsx b/src/pages/inbox/components/ChatChannelItem/ChatChannelItem.tsx index 5ba4554f2e..3ed0393b8f 100644 --- a/src/pages/inbox/components/ChatChannelItem/ChatChannelItem.tsx +++ b/src/pages/inbox/components/ChatChannelItem/ChatChannelItem.tsx @@ -29,7 +29,8 @@ export const ChatChannelItem: FC = (props) => { fetched: isChatChannelUserStatusFetched, fetchChatChannelUserStatus, } = useChatChannelUserStatus(); - const { setChatItem, feedItemIdForAutoChatOpen } = useChatContext(); + const { setChatItem, feedItemIdForAutoChatOpen, shouldAllowChatAutoOpen } = + useChatContext(); const user = useSelector(selectUser()); const userId = user?.uid; const dmUserId = @@ -99,11 +100,12 @@ export const ChatChannelItem: FC = (props) => { if ( isChatChannelUserStatusFetched && chatChannel.id === feedItemIdForAutoChatOpen && - !isTabletView + !isTabletView && + shouldAllowChatAutoOpen !== false ) { handleOpenChat(); } - }, [isChatChannelUserStatusFetched]); + }, [isChatChannelUserStatusFetched, shouldAllowChatAutoOpen]); useEffect(() => { if (isActive && finalTitle) { diff --git a/src/services/Common.ts b/src/services/Common.ts index 1e76bcb98f..b9c88e2a57 100644 --- a/src/services/Common.ts +++ b/src/services/Common.ts @@ -162,7 +162,7 @@ class CommonService { return finalCommons; }; - // Fetch all parent commons. Order: from main parent common to lowest ones + // Fetch all parent commons. Order: from root parent common to lowest ones public getAllParentCommonsForCommon = async ( commonToCheck: Pick | string, ): Promise => { diff --git a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx index 1b26db6783..0bbd90d800 100644 --- a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx +++ b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx @@ -26,6 +26,7 @@ import { DiscussionMessage, User, DirectParent, + Circles, } from "@/shared/models"; import { FilePreview, @@ -58,6 +59,7 @@ interface ChatMessageProps { users: User[]; feedItemId: string; commonMember: CommonMember | null; + governanceCircles?: Circles; onMessageDelete?: (messageId: string) => void; directParent?: DirectParent | null; onUserClick?: (userId: string) => void; @@ -86,6 +88,7 @@ export default function ChatMessage({ users, feedItemId, commonMember, + governanceCircles, onMessageDelete, directParent, onUserClick, @@ -394,6 +397,8 @@ export default function ChatMessage({
{!isSystemMessage && ( void; + commonMember?: CommonMember | null; + governanceCircles?: Circles; } const ElementDropdown: FC = ({ @@ -95,11 +96,11 @@ const ElementDropdown: FC = ({ isControlledDropdown = true, feedItemId, onDelete, + commonMember, + governanceCircles, }) => { const dispatch = useDispatch(); const user = useSelector(selectUser()); - const commonMember = useSelector(selectCommonMember) as CommonMember; - const governance = useSelector(selectGovernance) as Governance; const { notify } = useNotification(); const [selectedItem, setSelectedItem] = useState< ElementDropdownMenuItems | unknown @@ -180,9 +181,13 @@ const ElementDropdown: FC = ({ } if ( + governanceCircles && + commonMember && hasPermission({ - commonMember, - governance, + commonMember: commonMember, + governance: { + circles: governanceCircles, + }, key: HideContentTypes[entityType], }) && !isOwner && @@ -194,8 +199,19 @@ const ElementDropdown: FC = ({ value: ElementDropdownMenuItems.Hide, }); } - - if (isOwner && (isDiscussionMessage || isChatMessage)) { + if ( + governanceCircles && + commonMember && + (isOwner || + hasPermission({ + commonMember, + governance: { + circles: governanceCircles, + }, + key: DeleteContentTypes[entityType], + })) && + (isDiscussionMessage || isChatMessage) + ) { items.push({ text: ( = ({ user, ownerId, commonMember, - governance, + governanceCircles, isHiddenElement, ]); diff --git a/src/shared/hooks/useCases/useCommon.ts b/src/shared/hooks/useCases/useCommon.ts index 95afc4d402..2a5117c70b 100644 --- a/src/shared/hooks/useCases/useCommon.ts +++ b/src/shared/hooks/useCases/useCommon.ts @@ -1,4 +1,4 @@ -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { getCommonState, @@ -21,12 +21,13 @@ const DEFAULT_STATE: State = { data: null, }; -export const useCommon = (): Return => { +export const useCommon = (outerCommonId?: string): Return => { const dispatch = useDispatch(); const [currentCommonId, setCurrentCommonId] = useState(""); const [defaultState, setDefaultState] = useState({ ...DEFAULT_STATE }); const state = - useSelector(selectCommonStateById(currentCommonId)) || defaultState; + useSelector(selectCommonStateById(outerCommonId ?? currentCommonId)) || + defaultState; const fetchCommon = useCallback( (commonId: string) => { @@ -64,6 +65,17 @@ export const useCommon = (): Return => { [dispatch], ); + useEffect(() => { + if (typeof outerCommonId === "undefined") { + return; + } + if (outerCommonId) { + fetchCommon(outerCommonId); + } else { + setCommon(null); + } + }, [outerCommonId]); + return { ...state, fetchCommon, diff --git a/src/shared/hooks/useCases/useCommonMemberWithUserInfo.ts b/src/shared/hooks/useCases/useCommonMemberWithUserInfo.ts index 7d37e12ac0..e9a89f8681 100644 --- a/src/shared/hooks/useCases/useCommonMemberWithUserInfo.ts +++ b/src/shared/hooks/useCases/useCommonMemberWithUserInfo.ts @@ -58,6 +58,8 @@ export const useCommonMemberWithUserInfo = ( joinedAt: new Timestamp(0, 0), circleIds: [], user: user, + streamsUnreadCountByProjectStream: {}, + unreadCountByProjectStream: {}, }, loading: isUserLoading, fetched: isUserFetched, @@ -72,6 +74,9 @@ export const useCommonMemberWithUserInfo = ( joinedAt: commonMember.joinedAt, circleIds: [circlesString], user: user, + streamsUnreadCountByProjectStream: + commonMember.streamsUnreadCountByProjectStream, + unreadCountByProjectStream: commonMember.unreadCountByProjectStream, }, loading: false, fetched: true, diff --git a/src/shared/interfaces/feedLayout.ts b/src/shared/interfaces/feedLayout.ts index 95d429f3d7..48407f0d3e 100644 --- a/src/shared/interfaces/feedLayout.ts +++ b/src/shared/interfaces/feedLayout.ts @@ -9,6 +9,7 @@ import { export interface FeedLayoutRef { setExpandedFeedItemId: (feedItemId: string | null) => void; setActiveItem: (item: ChatItem) => void; + setShouldAllowChatAutoOpen: (shouldAllow: boolean | null) => void; } interface BaseLayoutItem { diff --git a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/Projects/Projects.tsx b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/Projects/Projects.tsx index 252b068918..f44974e259 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/Projects/Projects.tsx +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/Projects/Projects.tsx @@ -28,6 +28,7 @@ const Projects: FC = (props) => { items, activeItem, itemIdWithNewProjectCreation, + parentItemIds, } = useProjectsData(); const { isModalOpen: isCreateCommonModalOpen, @@ -92,6 +93,7 @@ const Projects: FC = (props) => { commons={commons} items={items} activeItem={activeItem} + parentItemIds={parentItemIds} itemIdWithNewProjectCreation={itemIdWithNewProjectCreation} currentCommonId={currentCommonId} onCommonClick={handleCommonClick} diff --git a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx index 29c2604dd9..ae1d0c51f0 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx @@ -33,6 +33,7 @@ const ProjectsTree: FC = (props) => { commons, items, activeItem, + parentItemIds, currentCommonId, itemIdWithNewProjectCreation = "", onCommonClick, @@ -54,6 +55,7 @@ const ProjectsTree: FC = (props) => { itemIdWithNewProjectCreation, isActiveCheckAllowed, treeItemTriggerStyles, + parentItemIds, onAddProjectClick, }), [ @@ -61,6 +63,7 @@ const ProjectsTree: FC = (props) => { itemIdWithNewProjectCreation, isActiveCheckAllowed, treeItemTriggerStyles, + parentItemIds, onAddProjectClick, ], ); diff --git a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsData.ts b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsData.ts index 165c96e251..5633af13bf 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsData.ts +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsData.ts @@ -16,6 +16,7 @@ import { getItemById, getItemFromProjectsStateItem, getItemIdWithNewProjectCreationByPath, + getParentItemIds, Item, } from "../../../../SidenavLayout/components/SidenavContent/components"; @@ -28,13 +29,14 @@ interface Return { items: Item[]; activeItem: Item | null; itemIdWithNewProjectCreation: string; + parentItemIds: string[]; } export const useProjectsData = (): Return => { const dispatch = useDispatch(); const history = useHistory(); const location = history.location; - const { getCommonPagePath, getCommonPageAboutTabPath } = useRoutesContext(); + const { getCommonPagePath } = useRoutesContext(); const isAuthenticated = useSelector(authentificated()); const currentCommonId = useSelector(selectCommonLayoutCommonId); const { commons, areCommonsLoading, areCommonsFetched } = useSelector( @@ -48,10 +50,8 @@ export const useProjectsData = (): Return => { ); const generateItemCommonPagePath = useCallback( (projectsStateItem: ProjectsStateItem): string => - projectsStateItem.hasMembership - ? getCommonPagePath(projectsStateItem.commonId) - : getCommonPageAboutTabPath(projectsStateItem.commonId), - [getCommonPagePath, getCommonPageAboutTabPath], + getCommonPagePath(projectsStateItem.commonId), + [getCommonPagePath], ); const parentItem = useMemo( () => @@ -76,6 +76,10 @@ export const useProjectsData = (): Return => { activeItemId, parentItem ? [parentItem, ...items] : items, ); + const parentItemIds = getParentItemIds( + activeItemId, + currentCommon ? projects.concat(currentCommon) : projects, + ); const itemIdWithNewProjectCreation = getItemIdWithNewProjectCreationByPath( location.pathname, ); @@ -131,5 +135,6 @@ export const useProjectsData = (): Return => { items, activeItem, itemIdWithNewProjectCreation, + parentItemIds, }; }; diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsMenu/hooks/useMenuItems.tsx b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsMenu/hooks/useMenuItems.tsx index a120f1c20d..ac4606adc3 100644 --- a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsMenu/hooks/useMenuItems.tsx +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsMenu/hooks/useMenuItems.tsx @@ -17,21 +17,12 @@ interface Options { export const useMenuItems = (options: Options): ContextMenuItem[] => { const { items, activeItemId, commonIdToAddProject, onCommonCreate } = options; const history = useHistory(); - const { - getCommonPagePath, - getCommonPageAboutTabPath, - getProjectCreationPagePath, - } = useRoutesContext(); + const { getCommonPagePath, getProjectCreationPagePath } = useRoutesContext(); const menuItems: ContextMenuItem[] = items.map((item) => ({ id: item.commonId, text: item.name, - onClick: () => - history.push( - item.hasMembership - ? getCommonPagePath(item.commonId) - : getCommonPageAboutTabPath(item.commonId), - ), + onClick: () => history.push(getCommonPagePath(item.commonId)), className: classNames(styles.contextMenuItem, { [styles.contextMenuItemWithoutMembership]: !item.hasMembership, }), diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/Projects.tsx b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/Projects.tsx index b7d0d52b75..b1ed7dd05f 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/Projects.tsx +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/Projects.tsx @@ -22,6 +22,7 @@ import { getActiveItemIdByPath, getItemById, getItemIdWithNewProjectCreationByPath, + getParentItemIds, } from "./utils"; import styles from "./Projects.module.scss"; @@ -52,6 +53,7 @@ const Projects: FC = () => { location.pathname, ); const activeItem = getItemById(activeItemId, items); + const parentItemIds = getParentItemIds(activeItemId, projects); const isDataReady = areProjectsFetched; const handleGoToCommon = (createdCommon: Common) => { @@ -91,6 +93,7 @@ const Projects: FC = () => { className={styles.projectsTree} items={items} activeItem={activeItem} + parentItemIds={parentItemIds} itemIdWithNewProjectCreation={itemIdWithNewProjectCreation} />
diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/getParentItemIds.ts b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/getParentItemIds.ts new file mode 100644 index 0000000000..0627e0c76d --- /dev/null +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/getParentItemIds.ts @@ -0,0 +1,18 @@ +import { ProjectsStateItem } from "@/store/states"; + +export const getParentItemIds = ( + activeItemId: string, + items: ProjectsStateItem[], +): string[] => { + const parentItemIds: string[] = []; + let item = items.find(({ commonId }) => activeItemId === commonId); + + while (item?.directParent) { + const parentId = item.directParent.commonId; + parentItemIds.unshift(parentId); + + item = items.find(({ commonId }) => parentId === commonId); + } + + return parentItemIds; +}; diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/index.ts b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/index.ts index 594a8924f5..4c9e9f2512 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/index.ts +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/index.ts @@ -2,3 +2,4 @@ export * from "./generateProjectsStateItems"; export * from "./getActiveItemIdByPath"; export * from "./getItemById"; export * from "./getItemIdWithNewProjectCreationByPath"; +export * from "./getParentItemIds"; diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx index 32b368d32f..08b0ca50e9 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx @@ -13,6 +13,7 @@ export interface ProjectsTreeProps { items: Item[]; activeItem: Item | null; itemIdWithNewProjectCreation?: string; + parentItemIds: string[]; } const ProjectsTree: FC = (props) => { @@ -21,6 +22,7 @@ const ProjectsTree: FC = (props) => { treeItemTriggerStyles, items, activeItem, + parentItemIds, itemIdWithNewProjectCreation = "", } = props; const isActiveCheckAllowed = !itemIdWithNewProjectCreation; @@ -30,12 +32,14 @@ const ProjectsTree: FC = (props) => { itemIdWithNewProjectCreation, isActiveCheckAllowed, treeItemTriggerStyles, + parentItemIds, }), [ activeItem?.id, itemIdWithNewProjectCreation, isActiveCheckAllowed, treeItemTriggerStyles, + parentItemIds, ], ); diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItem/TreeItem.tsx b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItem/TreeItem.tsx index a5d2515615..8fca4c1e71 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItem/TreeItem.tsx +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItem/TreeItem.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from "react"; +import React, { FC, useEffect, useState } from "react"; import classNames from "classnames"; import { Item } from "../../types"; import { TreeItemTrigger } from "../TreeItemTrigger"; @@ -11,6 +11,7 @@ interface TreeItemProps { item: Item; level?: number; isActive?: boolean; + hasActiveChild?: boolean; } const TreeItem: FC = (props) => { @@ -20,13 +21,27 @@ const TreeItem: FC = (props) => { item, level = 1, isActive = false, + hasActiveChild = false, children, } = props; const [isOpen, setIsOpen] = useState(false); + const [isOpenedManually, setIsOpenedManually] = useState(false); const itemStyles = getItemStyles(level); const hasNestedContent = Boolean(children); + useEffect(() => { + if (isActive && hasNestedContent) { + setIsOpen(true); + return; + } + + if (!isActive && !isOpenedManually && !hasActiveChild) { + setIsOpen(false); + } + }, [isActive, hasNestedContent, isOpenedManually, hasActiveChild]); + const handleTriggerToggle = () => { + setIsOpenedManually(!isOpen); setIsOpen((value) => !value); }; diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeRecursive/TreeRecursive.tsx b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeRecursive/TreeRecursive.tsx index 93399c783b..13eb943bd4 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeRecursive/TreeRecursive.tsx +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeRecursive/TreeRecursive.tsx @@ -27,6 +27,7 @@ const TreeRecursive: FC = (props) => { } = props; const { activeItemId, + parentItemIds = [], itemIdWithNewProjectCreation, isActiveCheckAllowed, onAddProjectClick, @@ -54,11 +55,18 @@ const TreeRecursive: FC = (props) => { > {items.map((item) => { const isActive = isActiveCheckAllowed && item.id === activeItemId; + const hasActiveChild = parentItemIds.includes(item.id); const hasPermissionToAddProject = item.hasPermissionToAddProject && isActive; return ( - + {(item.items && item.items.length > 0) || item.id === itemIdWithNewProjectCreation || hasPermissionToAddProject ? ( diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/context.ts b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/context.ts index e4e186ca2e..70a65b553b 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/context.ts +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/context.ts @@ -10,6 +10,7 @@ export interface TreeItemTriggerStyles { export interface TreeContextValue { activeItemId?: string; + parentItemIds?: string[]; itemIdWithNewProjectCreation?: string; isActiveCheckAllowed: boolean; treeItemTriggerStyles?: TreeItemTriggerStyles; diff --git a/src/shared/models/Common.tsx b/src/shared/models/Common.tsx index 0925381b9f..80a69ec68c 100644 --- a/src/shared/models/Common.tsx +++ b/src/shared/models/Common.tsx @@ -144,6 +144,8 @@ export interface CommonMember { rulesAccepted?: boolean; joinedAt: firebase.firestore.Timestamp; circleIds: string[]; + streamsUnreadCountByProjectStream: Record; + unreadCountByProjectStream: Record; } export interface CirclesPermissions { diff --git a/src/shared/models/CommonFeed.tsx b/src/shared/models/CommonFeed.tsx index 219f661c18..3d264dc525 100644 --- a/src/shared/models/CommonFeed.tsx +++ b/src/shared/models/CommonFeed.tsx @@ -14,6 +14,7 @@ export enum CommonFeedType { export interface CommonFeed extends BaseEntity, SoftDeleteEntity { userId: string; + commonId: string; data: Record & { type: CommonFeedType; id: string; diff --git a/src/shared/ui-kit/TextEditor/BaseTextEditor.tsx b/src/shared/ui-kit/TextEditor/BaseTextEditor.tsx index 531d19d1c6..734db6ff39 100644 --- a/src/shared/ui-kit/TextEditor/BaseTextEditor.tsx +++ b/src/shared/ui-kit/TextEditor/BaseTextEditor.tsx @@ -220,7 +220,7 @@ const BaseTextEditor: FC = (props) => {
{ onChange && onChange(val); const { selection } = editor; diff --git a/src/shared/ui-kit/TextEditor/TextEditor.tsx b/src/shared/ui-kit/TextEditor/TextEditor.tsx index 6916449495..b17554b16c 100644 --- a/src/shared/ui-kit/TextEditor/TextEditor.tsx +++ b/src/shared/ui-kit/TextEditor/TextEditor.tsx @@ -85,7 +85,7 @@ const TextEditor: FC = (props) => { }, [editorRef, editor]); return ( - +
{ + console.error("Error enabling persistence", error); + }); if (REACT_APP_ENV === Environment.Local) { firebase.auth().useEmulator(local.firebase.authDomain); @@ -19,7 +24,7 @@ if (REACT_APP_ENV === Environment.Local) { .firestore() .useEmulator( "localhost", - Number(local.firebase.databaseURL.split(/:/g)[2]) + Number(local.firebase.databaseURL.split(/:/g)[2]), ); } diff --git a/src/store/states/common/reducer.ts b/src/store/states/common/reducer.ts index 1bb4fb7134..eab72f30c1 100644 --- a/src/store/states/common/reducer.ts +++ b/src/store/states/common/reducer.ts @@ -521,6 +521,11 @@ export const reducer = createReducer(initialState) nextState.sharedFeedItemId = payload; }), ) + .handleAction(actions.setRecentStreamId, (state, { payload }) => + produce(state, (nextState) => { + nextState.recentStreamId = payload; + }), + ) .handleAction(actions.setRecentAssignedCircle, (state, { payload }) => produce(state, (nextState) => { nextState.recentAssignedCircle = payload; diff --git a/yarn.lock b/yarn.lock index c9faac75d5..7f63942f64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3465,6 +3465,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@juggle/resize-observer@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" + integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -18035,18 +18040,19 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== -slate-history@^0.86.0: - version "0.86.0" - resolved "https://registry.yarnpkg.com/slate-history/-/slate-history-0.86.0.tgz#5554612271d2fc1018a7918be3961bb66e620c58" - integrity sha512-OxObL9tbhgwvSlnKSCpGIh7wnuaqvOj5jRExGjEyCU2Ke8ctf22HjT+jw7GEi9ttLzNTUmTEU3YIzqKGeqN+og== +slate-history@^0.93.0: + version "0.93.0" + resolved "https://registry.yarnpkg.com/slate-history/-/slate-history-0.93.0.tgz#d2fad47e4e8b262ab7c86b653f5dd6d9b6d85277" + integrity sha512-Gr1GMGPipRuxIz41jD2/rbvzPj8eyar56TVMyJBvBeIpQSSjNISssvGNDYfJlSWM8eaRqf6DAcxMKzsLCYeX6g== dependencies: is-plain-object "^5.0.0" -slate-react@^0.88.0: - version "0.88.0" - resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.88.0.tgz#161b84554ffdc3290a03379e46cd50d6ab77ca0f" - integrity sha512-hmrl7VZAAdOUlReI/EkI2GuRSLWuQFD+n9ZZaVqSUMjJR3T/MGNMPsJQAfrjgke2+xf4LIGEDGWZHKbUFK1hhw== +slate-react@^0.98.1: + version "0.98.1" + resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.98.1.tgz#a3c2876ab6953622abeaf0c436800f07e8015000" + integrity sha512-ta4TAxoHE740e5EYSjAvK2bSpvrvnTkPfwMmx7rV+z/r8sng/RaJpc5cL9Rt2sfqQonSZOnQtAIaL6g97bLgzw== dependencies: + "@juggle/resize-observer" "^3.4.0" "@types/is-hotkey" "^0.1.1" "@types/lodash" "^4.14.149" direction "^1.0.3" @@ -18056,10 +18062,10 @@ slate-react@^0.88.0: scroll-into-view-if-needed "^2.2.20" tiny-invariant "1.0.6" -slate@^0.87.0: - version "0.87.0" - resolved "https://registry.yarnpkg.com/slate/-/slate-0.87.0.tgz#ba68aef379918f7f6302d68fdacf561ab8e70178" - integrity sha512-m+IWERpdtb7Zna09MxHlUYIAoo4WVdxp2fSf7c+jSV9pDmJ4QvUTuHghX/TVrxtr+8BKGhyQCWIerZt32B1Ysg== +slate@^0.94.1: + version "0.94.1" + resolved "https://registry.yarnpkg.com/slate/-/slate-0.94.1.tgz#13b0ba7d0a7eeb0ec89a87598e9111cbbd685696" + integrity sha512-GH/yizXr1ceBoZ9P9uebIaHe3dC/g6Plpf9nlUwnvoyf6V1UOYrRwkabtOCd3ZfIGxomY4P7lfgLr7FPH8/BKA== dependencies: immer "^9.0.6" is-plain-object "^5.0.0"