From 541c18493984ab13b7a7795da974649ef11f7356 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 14:47:20 +0400 Subject: [PATCH 01/42] add `link to` menu item --- .../DiscussionFeedCard/hooks/useMenuItems.tsx | 8 ++++ .../utils/getAllowedItems.ts | 4 ++ .../FeedItem/constants/feedItemMenuItem.ts | 1 + src/shared/icons/index.ts | 1 + src/shared/icons/link4.icon.tsx | 44 +++++++++++++++++++ 5 files changed, 58 insertions(+) create mode 100644 src/shared/icons/link4.icon.tsx diff --git a/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx b/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx index 1ce6e71779..a352525818 100644 --- a/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx +++ b/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx @@ -12,10 +12,12 @@ import { Trash2Icon, UnfollowIcon, UnpinIcon, + Link4Icon as LinkIcon, Message3Icon, } from "@/shared/icons"; import { ContextMenuItem as Item, UploadFile } from "@/shared/interfaces"; import { parseStringToTextEditorValue } from "@/shared/ui-kit"; +import { emptyFunction } from "@/shared/utils"; import { notEmpty } from "@/shared/utils/notEmpty"; import { commonActions } from "@/store/states"; import { FeedItemMenuItem, GetAllowedItemsOptions } from "../../FeedItem"; @@ -145,6 +147,12 @@ export const useMenuItems = ( feedItemFollow.onFollowToggle(FollowFeedItemAction.Unfollow), icon: , }, + { + id: FeedItemMenuItem.LinkTo, + text: "Link to...", + onClick: () => emptyFunction, + icon: , + }, remove ? { id: FeedItemMenuItem.Remove, diff --git a/src/pages/common/components/DiscussionFeedCard/utils/getAllowedItems.ts b/src/pages/common/components/DiscussionFeedCard/utils/getAllowedItems.ts index c253616b6c..4936ff5c21 100644 --- a/src/pages/common/components/DiscussionFeedCard/utils/getAllowedItems.ts +++ b/src/pages/common/components/DiscussionFeedCard/utils/getAllowedItems.ts @@ -38,6 +38,9 @@ const MENU_ITEM_TO_CHECK_FUNCTION_MAP: Record< return Boolean(count) || !seen; }, + [FeedItemMenuItem.LinkTo]: ({ feedItemUserMetadata }) => { + return true; + }, }; export const getAllowedItems = ( @@ -53,6 +56,7 @@ export const getAllowedItems = ( FeedItemMenuItem.MarkRead, FeedItemMenuItem.Report, FeedItemMenuItem.Edit, + FeedItemMenuItem.LinkTo, FeedItemMenuItem.Remove, ]; const nonAllowedItems = diff --git a/src/pages/common/components/FeedItem/constants/feedItemMenuItem.ts b/src/pages/common/components/FeedItem/constants/feedItemMenuItem.ts index 2e6995333b..aa26d398d8 100644 --- a/src/pages/common/components/FeedItem/constants/feedItemMenuItem.ts +++ b/src/pages/common/components/FeedItem/constants/feedItemMenuItem.ts @@ -9,4 +9,5 @@ export enum FeedItemMenuItem { Unfollow = "unfollow", MarkUnread = "markUnread", MarkRead = "markRead", + LinkTo = "linkTo", } diff --git a/src/shared/icons/index.ts b/src/shared/icons/index.ts index 5fe92d3c25..952fe64312 100644 --- a/src/shared/icons/index.ts +++ b/src/shared/icons/index.ts @@ -27,6 +27,7 @@ export { default as InfoIcon } from "./info.icon"; export { default as LeftArrowIcon } from "./leftArrow.icon"; export { default as Link2Icon } from "./link2.icon"; export { default as Link3Icon } from "./link3.icon"; +export { default as Link4Icon } from "./link4.icon"; export { default as ListMarkIcon } from "./listMark.icon"; export { default as LogoutIcon } from "./logout.icon"; export { default as LongLeftArrowIcon } from "./longLeftArrow.icon"; diff --git a/src/shared/icons/link4.icon.tsx b/src/shared/icons/link4.icon.tsx new file mode 100644 index 0000000000..e455630c59 --- /dev/null +++ b/src/shared/icons/link4.icon.tsx @@ -0,0 +1,44 @@ +import React, { FC } from "react"; + +interface Link4IconProps { + className?: string; +} + +const Link4Icon: FC = ({ className }) => { + const color = "currentColor"; + + return ( + + + + + + ); +}; + +export default Link4Icon; From 5f1a694104f8f377ad0fb8c8c654b901514f259d Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 15:03:44 +0400 Subject: [PATCH 02/42] create permissions check for `link to` option --- .../DiscussionFeedCard/DiscussionFeedCard.tsx | 3 ++- .../DiscussionFeedCard/hooks/useMenuItems.tsx | 17 ++++++++++------- .../utils/checkIsLinkToAllowed.ts | 19 +++++++++++++++++++ .../utils/getAllowedItems.ts | 5 ++--- .../constants/governance/GovernanceActions.ts | 5 +++++ 5 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index e8be9b8a28..f397a087bd 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -29,7 +29,7 @@ import { PredefinedTypes, } from "@/shared/models"; import { TextEditorValue } from "@/shared/ui-kit"; -import { StaticLinkType, getUserName } from "@/shared/utils"; +import { StaticLinkType, getUserName, emptyFunction } from "@/shared/utils"; import { useChatContext } from "../ChatComponent"; import { FeedCard, @@ -145,6 +145,7 @@ const DiscussionFeedCard = forwardRef( report: onReportModalOpen, share: () => onShareModalOpen(), remove: onDeleteModalOpen, + linkTo: emptyFunction, }, ); const user = useSelector(selectUser()); diff --git a/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx b/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx index a352525818..c1528f807d 100644 --- a/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx +++ b/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx @@ -27,6 +27,7 @@ interface Actions { report: () => void; share: () => void; remove?: () => void; + linkTo?: () => void; } export const useMenuItems = ( @@ -41,7 +42,7 @@ export const useMenuItems = ( feedItemFollow, feedItemUserMetadata, } = options; - const { report, share, remove } = actions; + const { report, share, remove, linkTo } = actions; const allowedMenuItems = getAllowedItems({ ...options, feedItemFollow }); const items: Item[] = [ { @@ -147,12 +148,14 @@ export const useMenuItems = ( feedItemFollow.onFollowToggle(FollowFeedItemAction.Unfollow), icon: , }, - { - id: FeedItemMenuItem.LinkTo, - text: "Link to...", - onClick: () => emptyFunction, - icon: , - }, + linkTo + ? { + id: FeedItemMenuItem.LinkTo, + text: "Link to...", + onClick: () => emptyFunction, + icon: , + } + : undefined, remove ? { id: FeedItemMenuItem.Remove, diff --git a/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts b/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts new file mode 100644 index 0000000000..eabda62d52 --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts @@ -0,0 +1,19 @@ +import { GovernanceActions } from "@/shared/constants"; +import { hasPermission } from "@/shared/utils"; +import { GetAllowedItemsOptions } from "../../FeedItem"; + +export const checkIsLinkToAllowed = ( + options: GetAllowedItemsOptions, +): boolean => { + if (!options.commonMember) { + return false; + } + + return hasPermission({ + commonMember: options.commonMember, + governance: { + circles: options.governanceCircles || {}, + }, + key: GovernanceActions.LINK_FROM_HERE, + }); +}; diff --git a/src/pages/common/components/DiscussionFeedCard/utils/getAllowedItems.ts b/src/pages/common/components/DiscussionFeedCard/utils/getAllowedItems.ts index 4936ff5c21..f4872af0ca 100644 --- a/src/pages/common/components/DiscussionFeedCard/utils/getAllowedItems.ts +++ b/src/pages/common/components/DiscussionFeedCard/utils/getAllowedItems.ts @@ -3,6 +3,7 @@ import { notEmpty } from "@/shared/utils/notEmpty"; import { FeedItemMenuItem, FeedItemPinAction } from "../../FeedItem/constants"; import { GetAllowedItemsOptions } from "../../FeedItem/types"; import { checkIsEditItemAllowed } from "./checkIsEditItemAllowed"; +import { checkIsLinkToAllowed } from "./checkIsLinkToAllowed"; import { checkIsPinUnpinAllowed } from "./checkIsPinUnpinAllowed"; import { checkIsRemoveDiscussionAllowed } from "./checkIsRemoveDiscussionAllowed"; @@ -38,9 +39,7 @@ const MENU_ITEM_TO_CHECK_FUNCTION_MAP: Record< return Boolean(count) || !seen; }, - [FeedItemMenuItem.LinkTo]: ({ feedItemUserMetadata }) => { - return true; - }, + [FeedItemMenuItem.LinkTo]: checkIsLinkToAllowed, }; export const getAllowedItems = ( diff --git a/src/shared/constants/governance/GovernanceActions.ts b/src/shared/constants/governance/GovernanceActions.ts index b1352f03b6..f0295e312c 100644 --- a/src/shared/constants/governance/GovernanceActions.ts +++ b/src/shared/constants/governance/GovernanceActions.ts @@ -41,4 +41,9 @@ export enum GovernanceActions { LEAVE_CIRCLE = "LEAVE_CIRCLE", PIN_OR_UNPIN_FEED_ITEMS = "PIN_OR_UNPIN_FEED_ITEMS", + + MOVE_TO_HERE = "MOVE_TO_HERE", + LINK_TO_HERE = "LINK_TO_HERE", + MOVE_FROM_HERE = "MOVE_FROM_HERE", + LINK_FROM_HERE = "LINK_FROM_HERE", } From 913fdfae363c2b57246682507e04694867f07747 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 15:23:22 +0400 Subject: [PATCH 03/42] add linkedCommonIds field to Discussion model --- src/shared/converters/ChatChannelToDiscussionConverter.ts | 1 + src/shared/models/Discussion.tsx | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/shared/converters/ChatChannelToDiscussionConverter.ts b/src/shared/converters/ChatChannelToDiscussionConverter.ts index 31e0b7fbc9..bfa74d7830 100644 --- a/src/shared/converters/ChatChannelToDiscussionConverter.ts +++ b/src/shared/converters/ChatChannelToDiscussionConverter.ts @@ -17,6 +17,7 @@ class ChatChannelToDiscussionConverter extends Converter< followers: [], messageCount: chatChannel.messageCount, discussionMessages: [], + linkedCommonIds: [], isDeleted: false, createdAt: chatChannel.createdAt, updatedAt: chatChannel.updatedAt, diff --git a/src/shared/models/Discussion.tsx b/src/shared/models/Discussion.tsx index c8e0434ea8..819873dd8b 100644 --- a/src/shared/models/Discussion.tsx +++ b/src/shared/models/Discussion.tsx @@ -26,6 +26,11 @@ export interface Discussion extends BaseEntity, SoftDeleteEntity { predefinedType?: PredefinedTypes; notion?: DiscussionNotion; + /** + * List of common IDs that are have linked this discussion + */ + linkedCommonIds: string[]; + /** * A discussion can be linked to a proposal, if it does - proposalId will exist. */ From 3e62a6672918562ec88e9854b3cae6715be2498c Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 15:31:35 +0400 Subject: [PATCH 04/42] create basic link space modal --- .../DiscussionFeedCard/DiscussionFeedCard.tsx | 15 +- .../LinkSpaceModal/LinkSpaceModal.module.scss | 84 +++++++ .../LinkSpaceModal/LinkSpaceModal.tsx | 222 ++++++++++++++++++ .../components/LinkSpaceModal/hooks/index.ts | 1 + .../LinkSpaceModal/hooks/useDMUsers.ts | 55 +++++ .../components/LinkSpaceModal/index.ts | 1 + .../DiscussionFeedCard/components/index.ts | 1 + .../DiscussionFeedCard/hooks/useMenuItems.tsx | 8 +- 8 files changed, 381 insertions(+), 6 deletions(-) create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/hooks/index.ts create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/hooks/useDMUsers.ts create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/index.ts create mode 100644 src/pages/common/components/DiscussionFeedCard/components/index.ts diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index f397a087bd..4fc1d6008a 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -29,7 +29,7 @@ import { PredefinedTypes, } from "@/shared/models"; import { TextEditorValue } from "@/shared/ui-kit"; -import { StaticLinkType, getUserName, emptyFunction } from "@/shared/utils"; +import { StaticLinkType, getUserName } from "@/shared/utils"; import { useChatContext } from "../ChatComponent"; import { FeedCard, @@ -44,6 +44,7 @@ import { GetLastMessageOptions, GetNonAllowedItemsOptions, } from "../FeedItem"; +import { LinkSpaceModal } from "./components"; import { useMenuItems } from "./hooks"; interface DiscussionFeedCardProps { @@ -111,6 +112,11 @@ const DiscussionFeedCard = forwardRef( onOpen: onDeleteModalOpen, onClose: onDeleteModalClose, } = useModal(false); + const { + isShowing: isLinkSpaceModalOpen, + onOpen: onLinkSpaceModalOpen, + onClose: onLinkSpaceModalClose, + } = useModal(false); const [isDeletingInProgress, setDeletingInProgress] = useState(false); const { fetchUser: fetchDiscussionCreator, @@ -145,7 +151,7 @@ const DiscussionFeedCard = forwardRef( report: onReportModalOpen, share: () => onShareModalOpen(), remove: onDeleteModalOpen, - linkTo: emptyFunction, + linkSpace: onLinkSpaceModalOpen, }, ); const user = useSelector(selectUser()); @@ -376,6 +382,11 @@ const DiscussionFeedCard = forwardRef( /> )} + ); }, diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss new file mode 100644 index 0000000000..63a75b7cc2 --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss @@ -0,0 +1,84 @@ +@import "../../../../../../constants"; +@import "../../../../../../styles/sizes"; + +.modal { + max-width: 36.125rem; + width: 100%; + max-height: 29.25rem; + border-radius: 0; + box-shadow: 0 0.25rem 0.9375rem var(--drop-shadow); + + :global(.modal__header-wrapper--with-modal-padding) { + .modalHeader { + padding: 0.875rem 0 0.5rem; + justify-content: flex-start; + } + } + + .modalHeaderWrapper { + padding: 0 1.5rem; + } + + .modalContent { + padding: 0; + } + + .modalCloseWrapper { + top: 0.8rem; + margin: 0; + } + + @include tablet { + max-width: unset; + max-height: unset; + } +} + +.modalTitleWrapper { + width: 100%; + display: flex; + flex-direction: column; + color: var(--primary-text); +} + +.modalTitle { + margin: 0; + font-family: PoppinsSans, sans-serif; + font-weight: 600; + font-size: 1.25rem; + word-break: break-word; +} + +.content { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.infoText { + margin: 1rem 0; + padding: 0 1rem; + font-size: $mobile-title; + text-align: center; + color: var(--primary-text); +} + +.itemList { + width: 100%; + margin: 0; + padding: 0; + list-style: none; +} + +.item { + border-bottom: 0.0625rem solid $c-gray-20; + + &:last-child { + border-bottom: 0; + } +} + +.userItem { + padding: 0.375rem 1.75rem; +} diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx new file mode 100644 index 0000000000..c25a81a264 --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx @@ -0,0 +1,222 @@ +import React, { + FC, + ReactElement, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useDispatch } from "react-redux"; +import { Modal } from "@/shared/components"; +import { KeyboardKeys } from "@/shared/constants"; +import { useDMUserChatChannel } from "@/shared/hooks/useCases"; +import { DMUser } from "@/shared/interfaces"; +import { Loader } from "@/shared/ui-kit"; +import { emptyFunction } from "@/shared/utils"; +import { inboxActions } from "@/store/states"; +import { useDMUsers } from "./hooks"; +import styles from "./LinkSpaceModal.module.scss"; + +interface DirectMessageModalProps { + isOpen: boolean; + onClose: () => void; + title: string; +} + +const LinkSpaceModal: FC = (props) => { + const { isOpen, onClose, title } = props; + const dispatch = useDispatch(); + const listRef = useRef(null); + const [searchText, setSearchText] = useState(""); + const [activeItemIndex, setActiveItemIndex] = useState(null); + const { + loading: areDMUsersLoading, + dmUsers, + fetchDMUsers, + error: dmUsersFetchError, + } = useDMUsers(); + const { + loading: isChannelLoading, + dmUserChatChannel, + fetchDMUserChatChannel, + resetDMUserChatChannel, + } = useDMUserChatChannel(); + const filteredDMUsers = useMemo(() => { + if (!searchText) { + return dmUsers; + } + + const lowerCasedSearchText = searchText.toLowerCase(); + + return dmUsers.filter((item) => + item.userName.toLowerCase().startsWith(lowerCasedSearchText), + ); + }, [dmUsers, searchText]); + const totalUsersAmount = filteredDMUsers.length; + + const handleArrowUp = useCallback(() => { + setActiveItemIndex((currentIndex) => { + if (totalUsersAmount === 0) { + return null; + } + if (currentIndex === null) { + return totalUsersAmount - 1; + } + + const nextIndex = currentIndex - 1; + + return nextIndex < 0 ? totalUsersAmount - 1 : nextIndex; + }); + }, [totalUsersAmount]); + + const handleArrowDown = useCallback(() => { + setActiveItemIndex((currentIndex) => { + if (totalUsersAmount === 0) { + return null; + } + if (currentIndex === null) { + return 0; + } + + const nextIndex = currentIndex + 1; + + return nextIndex >= totalUsersAmount ? 0 : nextIndex; + }); + }, [totalUsersAmount]); + + const handleUserItemClick = (item: DMUser) => { + fetchDMUserChatChannel(item.uid); + }; + + useEffect(() => { + if (isOpen) { + fetchDMUsers(); + return; + } + + setActiveItemIndex(null); + setSearchText(""); + resetDMUserChatChannel(); + }, [isOpen]); + + useEffect(() => { + setActiveItemIndex(null); + }, [searchText]); + + useEffect(() => { + if (activeItemIndex !== null) { + const listElement = listRef.current?.children.item(activeItemIndex); + listElement?.scrollIntoView(false); + } + }, [activeItemIndex]); + + useEffect(() => { + if (!isOpen) { + return; + } + + const handler = (event: KeyboardEvent) => { + const key = event.key as KeyboardKeys; + + switch (key) { + case KeyboardKeys.ArrowUp: + handleArrowUp(); + break; + case KeyboardKeys.ArrowDown: + handleArrowDown(); + break; + default: + break; + } + }; + + window.addEventListener("keyup", handler); + + return () => { + window.removeEventListener("keyup", handler); + }; + }, [isOpen, handleArrowUp, handleArrowDown]); + + useEffect(() => { + if (!isOpen) { + return; + } + + const handler = (event: KeyboardEvent) => { + const key = event.key as KeyboardKeys; + + if (key !== KeyboardKeys.Enter) { + return; + } + + const item = filteredDMUsers.find( + (dmUser, index) => index === activeItemIndex, + ); + + if (item) { + handleUserItemClick(item); + } + }; + + window.addEventListener("keyup", handler); + + return () => { + window.removeEventListener("keyup", handler); + }; + }, [isOpen, activeItemIndex, filteredDMUsers]); + + useEffect(() => { + if (dmUserChatChannel) { + dispatch(inboxActions.addChatChannelItem(dmUserChatChannel)); + onClose(); + } + }, [dmUserChatChannel]); + + const renderContent = (): ReactElement => { + if (areDMUsersLoading || isChannelLoading) { + return ; + } + + if (dmUsersFetchError) { + return ( +

+ Oops! Something went wrong while loading the user list. Please try + again later. +

+ ); + } + + if (totalUsersAmount === 0) { + return

No users found

; + } + + return <>empty; + }; + + return ( + +

Link “{title}”

+ + } + isHeaderSticky + hideCloseButton={isChannelLoading} + mobileFullScreen + styles={{ + headerWrapper: styles.modalHeaderWrapper, + header: styles.modalHeader, + content: styles.modalContent, + closeWrapper: styles.modalCloseWrapper, + }} + > +
{renderContent()}
+
+ ); +}; + +export default LinkSpaceModal; diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/hooks/index.ts b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/hooks/index.ts new file mode 100644 index 0000000000..dd732fc3f9 --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useDMUsers"; diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/hooks/useDMUsers.ts b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/hooks/useDMUsers.ts new file mode 100644 index 0000000000..4a41060d9c --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/hooks/useDMUsers.ts @@ -0,0 +1,55 @@ +import { useCallback, useState } from "react"; +import { ChatService } from "@/services"; +import { useIsMounted, useLoadingState } from "@/shared/hooks"; +import { DMUser } from "@/shared/interfaces"; + +interface Return { + loading: boolean; + dmUsers: DMUser[]; + fetchDMUsers: (force?: boolean) => void; + error?: boolean; +} + +export const useDMUsers = (): Return => { + const isMounted = useIsMounted(); + const [state, setState] = useLoadingState([]); + const [error, setError] = useState(false); + + const fetchDMUsers = useCallback( + async (force = true) => { + if (!force && (state.loading || state.fetched)) { + return; + } + + setState({ + loading: true, + fetched: false, + data: [], + }); + + let dmUsers: DMUser[] = []; + + try { + dmUsers = await ChatService.getDMUsers(); + } catch (error) { + setError(true); + } finally { + if (isMounted()) { + setState({ + loading: false, + fetched: true, + data: dmUsers, + }); + } + } + }, + [state], + ); + + return { + fetchDMUsers, + loading: state.loading || !state.fetched, + dmUsers: state.data, + error: error, + }; +}; diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/index.ts b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/index.ts new file mode 100644 index 0000000000..8412e8f4fb --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/index.ts @@ -0,0 +1 @@ +export { default as LinkSpaceModal } from "./LinkSpaceModal"; diff --git a/src/pages/common/components/DiscussionFeedCard/components/index.ts b/src/pages/common/components/DiscussionFeedCard/components/index.ts new file mode 100644 index 0000000000..f96e662f0b --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/index.ts @@ -0,0 +1 @@ +export * from "./LinkSpaceModal"; diff --git a/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx b/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx index c1528f807d..4ff375df50 100644 --- a/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx +++ b/src/pages/common/components/DiscussionFeedCard/hooks/useMenuItems.tsx @@ -27,7 +27,7 @@ interface Actions { report: () => void; share: () => void; remove?: () => void; - linkTo?: () => void; + linkSpace?: () => void; } export const useMenuItems = ( @@ -42,7 +42,7 @@ export const useMenuItems = ( feedItemFollow, feedItemUserMetadata, } = options; - const { report, share, remove, linkTo } = actions; + const { report, share, remove, linkSpace } = actions; const allowedMenuItems = getAllowedItems({ ...options, feedItemFollow }); const items: Item[] = [ { @@ -148,11 +148,11 @@ export const useMenuItems = ( feedItemFollow.onFollowToggle(FollowFeedItemAction.Unfollow), icon: , }, - linkTo + linkSpace ? { id: FeedItemMenuItem.LinkTo, text: "Link to...", - onClick: () => emptyFunction, + onClick: linkSpace, icon: , } : undefined, From 61c3c42ad1c0ced9aae4c2c4183de1da054daed3 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 16:31:18 +0400 Subject: [PATCH 05/42] update title of link stream modal --- .../LinkSpaceModal/LinkSpaceModal.module.scss | 30 +++++++------------ .../LinkSpaceModal/LinkSpaceModal.tsx | 8 ++--- src/shared/components/Modal/Modal.tsx | 2 +- src/shared/interfaces/ModalProps.tsx | 1 + 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss index 63a75b7cc2..d53b35a209 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss @@ -10,13 +10,18 @@ :global(.modal__header-wrapper--with-modal-padding) { .modalHeader { - padding: 0.875rem 0 0.5rem; justify-content: flex-start; } - } - .modalHeaderWrapper { - padding: 0 1.5rem; + .modalTitle { + margin: 0; + font-family: PoppinsSans, sans-serif; + font-weight: 600; + font-size: 1.25rem; + color: var(--primary-text); + text-align: left; + word-break: break-word; + } } .modalContent { @@ -24,7 +29,7 @@ } .modalCloseWrapper { - top: 0.8rem; + top: 1.7rem; margin: 0; } @@ -34,21 +39,6 @@ } } -.modalTitleWrapper { - width: 100%; - display: flex; - flex-direction: column; - color: var(--primary-text); -} - -.modalTitle { - margin: 0; - font-family: PoppinsSans, sans-serif; - font-weight: 600; - font-size: 1.25rem; - word-break: break-word; -} - .content { width: 100%; display: flex; diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx index c25a81a264..678c816b78 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx @@ -199,17 +199,13 @@ const LinkSpaceModal: FC = (props) => { className={styles.modal} isShowing={isOpen} onClose={isChannelLoading ? emptyFunction : onClose} - title={ -
-

Link “{title}”

-
- } + title={`Link ${title}`} isHeaderSticky hideCloseButton={isChannelLoading} mobileFullScreen styles={{ - headerWrapper: styles.modalHeaderWrapper, header: styles.modalHeader, + title: styles.modalTitle, content: styles.modalContent, closeWrapper: styles.modalCloseWrapper, }} diff --git a/src/shared/components/Modal/Modal.tsx b/src/shared/components/Modal/Modal.tsx index e55331fc1a..f1f586d5e0 100644 --- a/src/shared/components/Modal/Modal.tsx +++ b/src/shared/components/Modal/Modal.tsx @@ -180,7 +180,7 @@ const Modal: ForwardRefRenderFunction = ( )} {typeof title === "string" ? ( -

{title}

+

{title}

) : ( title )} diff --git a/src/shared/interfaces/ModalProps.tsx b/src/shared/interfaces/ModalProps.tsx index 5f09737534..1bc02719bb 100644 --- a/src/shared/interfaces/ModalProps.tsx +++ b/src/shared/interfaces/ModalProps.tsx @@ -28,6 +28,7 @@ export interface ModalProps { modalOverlay?: string; headerWrapper?: string; header?: string; + title?: string; closeWrapper?: string; content?: string; }; From c6365e34a56e5fc1acae5932f48264aac26742c8 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 17:13:57 +0400 Subject: [PATCH 06/42] hide link to option in inbox --- src/pages/inbox/utils/getNonAllowedItems.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/inbox/utils/getNonAllowedItems.ts b/src/pages/inbox/utils/getNonAllowedItems.ts index 8fb89fd88f..7e3b63af7c 100644 --- a/src/pages/inbox/utils/getNonAllowedItems.ts +++ b/src/pages/inbox/utils/getNonAllowedItems.ts @@ -5,4 +5,5 @@ export const getNonAllowedItems: GetNonAllowedItemsOptions = () => [ FeedItemMenuItem.Unpin, FeedItemMenuItem.Edit, FeedItemMenuItem.Remove, + FeedItemMenuItem.LinkTo, ]; From a82179a91a1b274613020dba524a0df8f4cb36a7 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 17:30:49 +0400 Subject: [PATCH 07/42] check lowest circle ids in discussion ids for linking streams --- .../utils/checkIsLinkToAllowed.ts | 28 +++++++++++++------ .../ChatChannelToDiscussionConverter.ts | 1 + src/shared/models/Discussion.tsx | 6 ++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts b/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts index eabda62d52..2de51007d2 100644 --- a/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts +++ b/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts @@ -1,5 +1,5 @@ import { GovernanceActions } from "@/shared/constants"; -import { hasPermission } from "@/shared/utils"; +import { getCirclesWithLowestTier, hasPermission } from "@/shared/utils"; import { GetAllowedItemsOptions } from "../../FeedItem"; export const checkIsLinkToAllowed = ( @@ -9,11 +9,23 @@ export const checkIsLinkToAllowed = ( return false; } - return hasPermission({ - commonMember: options.commonMember, - governance: { - circles: options.governanceCircles || {}, - }, - key: GovernanceActions.LINK_FROM_HERE, - }); + const circlesWithLowestTier = getCirclesWithLowestTier( + Object.values(options.governanceCircles || {}), + ); + const discussionCircleVisibility = + options.discussion?.circleVisibilityByCommon?.[options.commonId || ""] || + []; + + return ( + discussionCircleVisibility.some((circleId) => + circlesWithLowestTier.some((circle) => circle.id === circleId), + ) && + hasPermission({ + commonMember: options.commonMember, + governance: { + circles: options.governanceCircles || {}, + }, + key: GovernanceActions.LINK_FROM_HERE, + }) + ); }; diff --git a/src/shared/converters/ChatChannelToDiscussionConverter.ts b/src/shared/converters/ChatChannelToDiscussionConverter.ts index bfa74d7830..4ff91eedb8 100644 --- a/src/shared/converters/ChatChannelToDiscussionConverter.ts +++ b/src/shared/converters/ChatChannelToDiscussionConverter.ts @@ -18,6 +18,7 @@ class ChatChannelToDiscussionConverter extends Converter< messageCount: chatChannel.messageCount, discussionMessages: [], linkedCommonIds: [], + circleVisibilityByCommon: {}, isDeleted: false, createdAt: chatChannel.createdAt, updatedAt: chatChannel.updatedAt, diff --git a/src/shared/models/Discussion.tsx b/src/shared/models/Discussion.tsx index 819873dd8b..a061a0aa84 100644 --- a/src/shared/models/Discussion.tsx +++ b/src/shared/models/Discussion.tsx @@ -41,6 +41,12 @@ export interface Discussion extends BaseEntity, SoftDeleteEntity { * If discussion is attached to a proposal, this field will be not exist. */ circleVisibility?: string[]; + + /** + * If array is empty, everyone in common can view. + * If discussion is attached to a proposal, this field is null. + */ + circleVisibilityByCommon: Record | null; } export interface DiscussionWithOwnerInfo extends Discussion { From 90a77e625f52849c27722772594f901e61a5f54a Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 18:55:13 +0400 Subject: [PATCH 08/42] create util for projects fetching --- .../states/commonLayout/saga/getProjects.ts | 53 +++---------------- .../commonLayout/saga/utils/getProjects.ts | 42 +++++++++++++++ .../states/commonLayout/saga/utils/index.ts | 1 + 3 files changed, 49 insertions(+), 47 deletions(-) create mode 100644 src/store/states/commonLayout/saga/utils/getProjects.ts diff --git a/src/store/states/commonLayout/saga/getProjects.ts b/src/store/states/commonLayout/saga/getProjects.ts index cada89f37e..c046c69c24 100644 --- a/src/store/states/commonLayout/saga/getProjects.ts +++ b/src/store/states/commonLayout/saga/getProjects.ts @@ -1,12 +1,10 @@ import { call, put, select } from "redux-saga/effects"; import { selectUser } from "@/pages/Auth/store/selectors"; -import { CommonService, GovernanceService, ProjectService } from "@/services"; import { Awaited } from "@/shared/interfaces"; import { User } from "@/shared/models"; import { isError } from "@/shared/utils"; -import { ProjectsStateItem } from "../../projects"; import * as actions from "../actions"; -import { getPermissionsDataByAllUserCommonMemberInfo } from "./utils"; +import { getProjects as getProjectsUtil } from "./utils"; export function* getProjects( action: ReturnType, @@ -16,50 +14,11 @@ export function* getProjects( try { const user = (yield select(selectUser())) as User | null; const userId = user?.uid; - - const commonsWithSubCommons = (yield call( - CommonService.getCommonsWithSubCommons, - [commonId], - )) as Awaited>; - const commonsWithoutMainParentCommon = commonsWithSubCommons.filter( - (common) => common.id !== commonId, - ); - const allUserCommonMemberInfo = userId - ? ((yield call( - CommonService.getAllUserCommonMemberInfo, - userId, - )) as Awaited< - ReturnType - >) - : []; - const userCommonIds = allUserCommonMemberInfo.map((item) => item.commonId); - const governanceList = (yield call( - GovernanceService.getGovernanceListByCommonIds, - userCommonIds, - )) as Awaited< - ReturnType - >; - const permissionsData = getPermissionsDataByAllUserCommonMemberInfo( - allUserCommonMemberInfo, - governanceList, - ); - const data = ProjectService.parseDataToProjectsInfo( - commonsWithoutMainParentCommon, - userCommonIds, - permissionsData, - ); - const projectsData: ProjectsStateItem[] = data.map( - ({ common, hasMembership, hasPermissionToAddProject }) => ({ - commonId: common.id, - image: common.image, - name: common.name, - directParent: common.directParent, - rootCommonId: common.rootCommonId, - hasMembership, - hasPermissionToAddProject, - notificationsAmount: 0, - }), - ); + const projectsData = (yield call( + getProjectsUtil, + commonId, + userId, + )) as Awaited>; yield put(actions.getProjects.success(projectsData)); } catch (error) { diff --git a/src/store/states/commonLayout/saga/utils/getProjects.ts b/src/store/states/commonLayout/saga/utils/getProjects.ts new file mode 100644 index 0000000000..f5abb27db3 --- /dev/null +++ b/src/store/states/commonLayout/saga/utils/getProjects.ts @@ -0,0 +1,42 @@ +import { CommonService, GovernanceService, ProjectService } from "@/services"; +import { ProjectsStateItem } from "../../../projects"; +import { getPermissionsDataByAllUserCommonMemberInfo } from "./getPermissionsDataByAllUserCommonMemberInfo"; + +export const getProjects = async ( + commonId: string, + userId?: string, +): Promise => { + const commonsWithSubCommons = await CommonService.getCommonsWithSubCommons([ + commonId, + ]); + const commonsWithoutMainParentCommon = commonsWithSubCommons.filter( + (common) => common.id !== commonId, + ); + const allUserCommonMemberInfo = userId + ? await CommonService.getAllUserCommonMemberInfo(userId) + : []; + const userCommonIds = allUserCommonMemberInfo.map((item) => item.commonId); + const governanceList = await GovernanceService.getGovernanceListByCommonIds( + userCommonIds, + ); + const permissionsData = getPermissionsDataByAllUserCommonMemberInfo( + allUserCommonMemberInfo, + governanceList, + ); + const data = ProjectService.parseDataToProjectsInfo( + commonsWithoutMainParentCommon, + userCommonIds, + permissionsData, + ); + + return data.map(({ common, hasMembership, hasPermissionToAddProject }) => ({ + commonId: common.id, + image: common.image, + name: common.name, + directParent: common.directParent, + rootCommonId: common.rootCommonId, + hasMembership, + hasPermissionToAddProject, + notificationsAmount: 0, + })); +}; diff --git a/src/store/states/commonLayout/saga/utils/index.ts b/src/store/states/commonLayout/saga/utils/index.ts index a6b2ea834d..d929c1f843 100644 --- a/src/store/states/commonLayout/saga/utils/index.ts +++ b/src/store/states/commonLayout/saga/utils/index.ts @@ -1 +1,2 @@ export * from "./getPermissionsDataByAllUserCommonMemberInfo"; +export * from "./getProjects"; From 4ca6df3711787c7f2aedcc13a376e831f6e5251d Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 19:13:32 +0400 Subject: [PATCH 09/42] create projects component for link space modal --- .../DiscussionFeedCard/DiscussionFeedCard.tsx | 16 +- .../LinkSpaceModal/LinkSpaceModal.module.scss | 27 --- .../LinkSpaceModal/LinkSpaceModal.tsx | 189 +----------------- .../components/Projects/Projects.module.scss | 49 +++++ .../components/Projects/Projects.tsx | 65 ++++++ .../components/Projects/hooks/index.ts | 1 + .../Projects/hooks/useProjectsData.ts | 126 ++++++++++++ .../components/Projects/index.ts | 1 + .../LinkSpaceModal/components/index.ts | 1 + .../common/components/FeedItem/FeedItem.tsx | 3 + .../components/FeedLayout/FeedLayout.tsx | 1 + .../components/ProjectsTree/ProjectsTree.tsx | 2 +- 12 files changed, 269 insertions(+), 212 deletions(-) create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/index.ts create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/useProjectsData.ts create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/index.ts create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/index.ts diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index 4fc1d6008a..7e162c5e3c 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -66,6 +66,7 @@ interface DiscussionFeedCardProps { getNonAllowedItems?: GetNonAllowedItemsOptions; onActiveItemDataChange?: (data: FeedLayoutItemChangeData) => void; directParent?: DirectParent | null; + rootCommonId?: string; feedItemFollow: FeedItemFollowState; onUserSelect?: (userId: string, commonId?: string) => void; } @@ -94,6 +95,7 @@ const DiscussionFeedCard = forwardRef( getNonAllowedItems, onActiveItemDataChange, directParent, + rootCommonId, feedItemFollow, onUserSelect, } = props; @@ -382,11 +384,15 @@ const DiscussionFeedCard = forwardRef( /> )} - + {commonId && ( + + )} ); }, diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss index d53b35a209..475f05baae 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss @@ -45,30 +45,3 @@ flex-direction: column; align-items: center; } - -.infoText { - margin: 1rem 0; - padding: 0 1rem; - font-size: $mobile-title; - text-align: center; - color: var(--primary-text); -} - -.itemList { - width: 100%; - margin: 0; - padding: 0; - list-style: none; -} - -.item { - border-bottom: 0.0625rem solid $c-gray-20; - - &:last-child { - border-bottom: 0; - } -} - -.userItem { - padding: 0.375rem 1.75rem; -} diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx index 678c816b78..0eb27e9768 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx @@ -1,207 +1,38 @@ -import React, { - FC, - ReactElement, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import { useDispatch } from "react-redux"; +import React, { FC, ReactElement } from "react"; import { Modal } from "@/shared/components"; -import { KeyboardKeys } from "@/shared/constants"; -import { useDMUserChatChannel } from "@/shared/hooks/useCases"; -import { DMUser } from "@/shared/interfaces"; import { Loader } from "@/shared/ui-kit"; import { emptyFunction } from "@/shared/utils"; -import { inboxActions } from "@/store/states"; -import { useDMUsers } from "./hooks"; +import { Projects } from "./components"; import styles from "./LinkSpaceModal.module.scss"; interface DirectMessageModalProps { isOpen: boolean; onClose: () => void; title: string; + rootCommonId: string; + commonId: string; } const LinkSpaceModal: FC = (props) => { - const { isOpen, onClose, title } = props; - const dispatch = useDispatch(); - const listRef = useRef(null); - const [searchText, setSearchText] = useState(""); - const [activeItemIndex, setActiveItemIndex] = useState(null); - const { - loading: areDMUsersLoading, - dmUsers, - fetchDMUsers, - error: dmUsersFetchError, - } = useDMUsers(); - const { - loading: isChannelLoading, - dmUserChatChannel, - fetchDMUserChatChannel, - resetDMUserChatChannel, - } = useDMUserChatChannel(); - const filteredDMUsers = useMemo(() => { - if (!searchText) { - return dmUsers; - } - - const lowerCasedSearchText = searchText.toLowerCase(); - - return dmUsers.filter((item) => - item.userName.toLowerCase().startsWith(lowerCasedSearchText), - ); - }, [dmUsers, searchText]); - const totalUsersAmount = filteredDMUsers.length; - - const handleArrowUp = useCallback(() => { - setActiveItemIndex((currentIndex) => { - if (totalUsersAmount === 0) { - return null; - } - if (currentIndex === null) { - return totalUsersAmount - 1; - } - - const nextIndex = currentIndex - 1; - - return nextIndex < 0 ? totalUsersAmount - 1 : nextIndex; - }); - }, [totalUsersAmount]); - - const handleArrowDown = useCallback(() => { - setActiveItemIndex((currentIndex) => { - if (totalUsersAmount === 0) { - return null; - } - if (currentIndex === null) { - return 0; - } - - const nextIndex = currentIndex + 1; - - return nextIndex >= totalUsersAmount ? 0 : nextIndex; - }); - }, [totalUsersAmount]); - - const handleUserItemClick = (item: DMUser) => { - fetchDMUserChatChannel(item.uid); - }; - - useEffect(() => { - if (isOpen) { - fetchDMUsers(); - return; - } - - setActiveItemIndex(null); - setSearchText(""); - resetDMUserChatChannel(); - }, [isOpen]); - - useEffect(() => { - setActiveItemIndex(null); - }, [searchText]); - - useEffect(() => { - if (activeItemIndex !== null) { - const listElement = listRef.current?.children.item(activeItemIndex); - listElement?.scrollIntoView(false); - } - }, [activeItemIndex]); - - useEffect(() => { - if (!isOpen) { - return; - } - - const handler = (event: KeyboardEvent) => { - const key = event.key as KeyboardKeys; - - switch (key) { - case KeyboardKeys.ArrowUp: - handleArrowUp(); - break; - case KeyboardKeys.ArrowDown: - handleArrowDown(); - break; - default: - break; - } - }; - - window.addEventListener("keyup", handler); - - return () => { - window.removeEventListener("keyup", handler); - }; - }, [isOpen, handleArrowUp, handleArrowDown]); - - useEffect(() => { - if (!isOpen) { - return; - } - - const handler = (event: KeyboardEvent) => { - const key = event.key as KeyboardKeys; - - if (key !== KeyboardKeys.Enter) { - return; - } - - const item = filteredDMUsers.find( - (dmUser, index) => index === activeItemIndex, - ); - - if (item) { - handleUserItemClick(item); - } - }; - - window.addEventListener("keyup", handler); - - return () => { - window.removeEventListener("keyup", handler); - }; - }, [isOpen, activeItemIndex, filteredDMUsers]); - - useEffect(() => { - if (dmUserChatChannel) { - dispatch(inboxActions.addChatChannelItem(dmUserChatChannel)); - onClose(); - } - }, [dmUserChatChannel]); + const { isOpen, onClose, title, rootCommonId, commonId } = props; + const isSpaceLinkingLoading = false; const renderContent = (): ReactElement => { - if (areDMUsersLoading || isChannelLoading) { + if (isSpaceLinkingLoading) { return ; } - if (dmUsersFetchError) { - return ( -

- Oops! Something went wrong while loading the user list. Please try - again later. -

- ); - } - - if (totalUsersAmount === 0) { - return

No users found

; - } - - return <>empty; + return ; }; return ( ReactNode; +} + +const Projects: FC = (props) => { + const { renderNoItemsInfo } = props; + const [currentCommonId, setCurrentCommonId] = useState(props.rootCommonId); + const [activeItemId, setActiveItemId] = useState(props.commonId); + const { + parentItem, + areCommonsLoading, + areProjectsLoading, + commons, + items, + activeItem, + parentItemIds, + } = useProjectsData({ currentCommonId, activeItemId }); + const treeItemTriggerStyles = useMemo( + () => ({ + container: styles.projectsTreeItemTriggerClassName, + containerActive: styles.projectsTreeItemTriggerActiveClassName, + name: styles.projectsTreeItemTriggerNameClassName, + image: styles.projectsTreeItemTriggerImageClassName, + imageNonRounded: styles.projectsTreeItemTriggerImageNonRoundedClassName, + }), + [], + ); + + if (!parentItem) { + return areCommonsLoading ? ( + + ) : ( + <>{renderNoItemsInfo?.() || null} + ); + } + + return ( + + ); +}; + +export default Projects; diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/index.ts b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/index.ts new file mode 100644 index 0000000000..ced5d7bb9c --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useProjectsData"; diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/useProjectsData.ts b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/useProjectsData.ts new file mode 100644 index 0000000000..36ed5b7507 --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/useProjectsData.ts @@ -0,0 +1,126 @@ +import { useEffect, useMemo, useRef } from "react"; +import { useSelector } from "react-redux"; +import { selectUser } from "@/pages/Auth/store/selectors"; +import { useLoadingState } from "@/shared/hooks"; +import { + generateProjectsTreeItems, + getItemById, + getItemFromProjectsStateItem, + getParentItemIds, + Item, +} from "@/shared/layouts/SidenavLayout/components/SidenavContent/components"; +import { + ProjectsStateItem, + selectCommonLayoutCommonsState, +} from "@/store/states"; +import { getProjects as getProjectsUtil } from "@/store/states/commonLayout/saga/utils"; + +interface ProjectsInfo { + currentCommonId: string; + activeItemId: string; +} + +interface Return { + parentItem: Item | null; + areCommonsLoading: boolean; + areProjectsLoading: boolean; + commons: ProjectsStateItem[]; + items: Item[]; + activeItem: Item | null; + parentItemIds: string[]; +} + +const generateItemCommonPagePath = () => ""; + +export const useProjectsData = (projectsInfo: ProjectsInfo): Return => { + const { currentCommonId, activeItemId } = projectsInfo; + const currentCommonIdRef = useRef(currentCommonId); + currentCommonIdRef.current = currentCommonId; + const { commons, areCommonsLoading } = useSelector( + selectCommonLayoutCommonsState, + ); + const user = useSelector(selectUser()); + const userId = user?.uid; + const [ + { + data: projects, + loading: areProjectsLoading, + fetched: areProjectsFetched, + }, + setProjectsState, + ] = useLoadingState([]); + const currentCommon = commons.find( + ({ commonId }) => commonId === currentCommonId, + ); + const parentItem = useMemo( + () => + currentCommon + ? getItemFromProjectsStateItem( + currentCommon, + generateItemCommonPagePath, + ) + : null, + [currentCommon, generateItemCommonPagePath], + ); + const items = useMemo(() => { + const [item] = generateProjectsTreeItems( + currentCommon ? projects.concat(currentCommon) : projects, + generateItemCommonPagePath, + ); + + return item?.items || []; + }, [currentCommon, projects, generateItemCommonPagePath]); + const activeItem = getItemById( + activeItemId, + parentItem ? [parentItem, ...items] : items, + ); + const parentItemIds = getParentItemIds( + activeItemId, + currentCommon ? projects.concat(currentCommon) : projects, + ); + + useEffect(() => { + let isRelevantLoading = true; + + (async () => { + try { + setProjectsState({ + data: [], + loading: true, + fetched: false, + }); + const projectsData = await getProjectsUtil(currentCommonId, userId); + + if (isRelevantLoading) { + setProjectsState({ + data: projectsData, + loading: false, + fetched: true, + }); + } + } catch (err) { + if (isRelevantLoading) { + setProjectsState({ + data: [], + loading: false, + fetched: true, + }); + } + } + })(); + + return () => { + isRelevantLoading = false; + }; + }, [currentCommonId]); + + return { + parentItem, + areCommonsLoading, + areProjectsLoading, + commons, + items, + activeItem, + parentItemIds, + }; +}; diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/index.ts b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/index.ts new file mode 100644 index 0000000000..b8190505b7 --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/index.ts @@ -0,0 +1 @@ +export { default as Projects } from "./Projects"; diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/index.ts b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/index.ts new file mode 100644 index 0000000000..a72f1e6536 --- /dev/null +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/index.ts @@ -0,0 +1 @@ +export * from "./Projects"; diff --git a/src/pages/common/components/FeedItem/FeedItem.tsx b/src/pages/common/components/FeedItem/FeedItem.tsx index 946782d22e..765210d10a 100644 --- a/src/pages/common/components/FeedItem/FeedItem.tsx +++ b/src/pages/common/components/FeedItem/FeedItem.tsx @@ -43,6 +43,7 @@ interface FeedItemProps { commonId?: string, ) => void; directParent?: DirectParent | null; + rootCommonId?: string; } const FeedItem = forwardRef((props, ref) => { @@ -67,6 +68,7 @@ const FeedItem = forwardRef((props, ref) => { shouldCheckItemVisibility = true, onActiveItemDataChange, directParent, + rootCommonId, } = props; const { onFeedItemUpdate, @@ -129,6 +131,7 @@ const FeedItem = forwardRef((props, ref) => { isMobileVersion, onActiveItemDataChange: handleActiveItemDataChange, directParent, + rootCommonId, feedItemFollow, onUserSelect, }; diff --git a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx index c26fe9a6e4..97ed0cb5c2 100644 --- a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx +++ b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx @@ -718,6 +718,7 @@ const FeedLayout: ForwardRefRenderFunction = ( } onActiveItemDataChange={handleActiveFeedItemDataChange} directParent={outerCommon?.directParent} + rootCommonId={outerCommon?.rootCommonId} /> ); } 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 8457761173..20e7a16271 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx @@ -22,7 +22,7 @@ interface ProjectsTreeProps extends BaseProjectsTreeProps { currentCommonId?: string | null; onCommonClick: (commonId: string) => void; onCommonCreationClick: () => void; - onAddProjectClick: (commonId: string) => void; + onAddProjectClick?: (commonId: string) => void; isLoading?: boolean; } From a52b8c198c2f0a2726b512517da943ff7cdbf671 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 19:31:02 +0400 Subject: [PATCH 10/42] make onCommonCreationClick optional in ProjectsTree --- .../components/Projects/Projects.tsx | 2 -- .../components/ProjectsTree/ProjectsTree.tsx | 2 +- .../components/ProjectsTree/hooks/useMenuItems.ts | 15 ++++++++------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx index 9ed7bf3988..c979f6148e 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx @@ -3,7 +3,6 @@ import { LOADER_APPEARANCE_DELAY } from "@/shared/constants"; import { TreeItemTriggerStyles } from "@/shared/layouts"; import { ProjectsTree } from "@/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree"; import { Loader } from "@/shared/ui-kit"; -import { emptyFunction } from "@/shared/utils"; import { useProjectsData } from "./hooks"; import styles from "./Projects.module.scss"; @@ -56,7 +55,6 @@ const Projects: FC = (props) => { parentItemIds={parentItemIds} currentCommonId={currentCommonId} onCommonClick={setCurrentCommonId} - onCommonCreationClick={emptyFunction} isLoading={areProjectsLoading} /> ); 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 20e7a16271..2dffa8f1e3 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx @@ -21,7 +21,7 @@ interface ProjectsTreeProps extends BaseProjectsTreeProps { commons: ProjectsStateItem[]; currentCommonId?: string | null; onCommonClick: (commonId: string) => void; - onCommonCreationClick: () => void; + onCommonCreationClick?: () => void; onAddProjectClick?: (commonId: string) => void; isLoading?: boolean; } diff --git a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/hooks/useMenuItems.ts b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/hooks/useMenuItems.ts index da65bfff9e..94905faa7c 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/hooks/useMenuItems.ts +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/hooks/useMenuItems.ts @@ -6,7 +6,7 @@ interface Options { stateItems: ProjectsStateItem[]; activeStateItemId?: string | null; onCommonClick: (commonId: string) => void; - onCommonCreationClick: () => void; + onCommonCreationClick?: () => void; } export const useMenuItems = (options: Options): MenuItem[] => { @@ -33,12 +33,13 @@ export const useMenuItems = (options: Options): MenuItem[] => { } return 0; - }) - .concat({ - id: CREATE_COMMON_ITEM_ID, - text: "Create a common", - onClick: onCommonCreationClick, }); - return items; + return onCommonCreationClick + ? items.concat({ + id: CREATE_COMMON_ITEM_ID, + text: "Create a common", + onClick: onCommonCreationClick, + }) + : items; }; From adb41a37cc8a43d9f62ce4e419ca307946580215 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 10:25:56 +0400 Subject: [PATCH 11/42] change modal styles to display tree correctly --- .../LinkSpaceModal/LinkSpaceModal.module.scss | 16 +++++++--------- .../components/LinkSpaceModal/LinkSpaceModal.tsx | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss index 475f05baae..d77bdbea1d 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss @@ -2,9 +2,10 @@ @import "../../../../../../styles/sizes"; .modal { - max-width: 36.125rem; + max-width: 31.875rem; width: 100%; - max-height: 29.25rem; + max-height: 33.75rem; + height: 100%; border-radius: 0; box-shadow: 0 0.25rem 0.9375rem var(--drop-shadow); @@ -25,7 +26,11 @@ } .modalContent { + width: 100%; padding: 0; + display: flex; + flex-direction: column; + overflow: hidden; } .modalCloseWrapper { @@ -38,10 +43,3 @@ max-height: unset; } } - -.content { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; -} diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx index 0eb27e9768..a91da307a0 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx @@ -41,7 +41,7 @@ const LinkSpaceModal: FC = (props) => { closeWrapper: styles.modalCloseWrapper, }} > -
{renderContent()}
+ {renderContent()}
); }; From f9c1e4827932fe340627bfce0df4f251e7cab7a1 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 10:48:22 +0400 Subject: [PATCH 12/42] add Apply button to modal --- .../LinkSpaceModal/LinkSpaceModal.module.scss | 9 ++++++++- .../components/LinkSpaceModal/LinkSpaceModal.tsx | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss index d77bdbea1d..f96e7ddb44 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss @@ -27,10 +27,10 @@ .modalContent { width: 100%; - padding: 0; display: flex; flex-direction: column; overflow: hidden; + box-sizing: border-box; } .modalCloseWrapper { @@ -43,3 +43,10 @@ max-height: unset; } } + +.submitButton { + --btn-w: 100%; + + max-width: 9.75rem; + align-self: flex-end; +} diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx index a91da307a0..4d4b51d06f 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx @@ -1,6 +1,6 @@ import React, { FC, ReactElement } from "react"; import { Modal } from "@/shared/components"; -import { Loader } from "@/shared/ui-kit"; +import { Button, ButtonSize, ButtonVariant, Loader } from "@/shared/ui-kit"; import { emptyFunction } from "@/shared/utils"; import { Projects } from "./components"; import styles from "./LinkSpaceModal.module.scss"; @@ -22,7 +22,18 @@ const LinkSpaceModal: FC = (props) => { return ; } - return ; + return ( + <> + + + + ); }; return ( From f3ced2c53cdfe59b818e113cd2f2519a96bc6817 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 10:55:38 +0400 Subject: [PATCH 13/42] update modal's height --- .../LinkSpaceModal/LinkSpaceModal.module.scss | 3 +- .../components/Projects/Projects.module.scss | 4 ++ .../components/Projects/Projects.tsx | 2 + .../CommonDropdown/CommonDropdown.tsx | 4 +- .../DesktopCommonDropdown.tsx | 5 ++- .../components/ProjectsTree/ProjectsTree.tsx | 39 ++++++++++++------- 6 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss index f96e7ddb44..fbf1bcb78b 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.module.scss @@ -5,7 +5,7 @@ max-width: 31.875rem; width: 100%; max-height: 33.75rem; - height: 100%; + min-height: 24rem; border-radius: 0; box-shadow: 0 0.25rem 0.9375rem var(--drop-shadow); @@ -48,5 +48,6 @@ --btn-w: 100%; max-width: 9.75rem; + margin-top: auto; align-self: flex-end; } diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss index b80cffadbf..b63bc8125f 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss @@ -47,3 +47,7 @@ padding-left: 2.125rem; padding-right: 0.875rem; } + +.commonsMenuClassName { + max-height: 15rem; +} diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx index c979f6148e..654d80cc65 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx @@ -56,6 +56,8 @@ const Projects: FC = (props) => { currentCommonId={currentCommonId} onCommonClick={setCurrentCommonId} isLoading={areProjectsLoading} + withScrollbar={false} + commonsMenuClassName={styles.commonsMenuClassName} /> ); }; diff --git a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/CommonDropdown/CommonDropdown.tsx b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/CommonDropdown/CommonDropdown.tsx index d8e5f3eeee..e1990af2fc 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/CommonDropdown/CommonDropdown.tsx +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/CommonDropdown/CommonDropdown.tsx @@ -6,16 +6,18 @@ interface CommonDropdownProps { items: MenuItem[]; activeItemId?: string | null; isActive: boolean; + menuItemsClassName?: string; } const CommonDropdown: FC = (props) => { - const { items, activeItemId, isActive } = props; + const { items, activeItemId, isActive, menuItemsClassName } = props; return ( ); }; diff --git a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/CommonDropdown/components/DesktopCommonDropdown/DesktopCommonDropdown.tsx b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/CommonDropdown/components/DesktopCommonDropdown/DesktopCommonDropdown.tsx index af92338041..b74bbf19c2 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/CommonDropdown/components/DesktopCommonDropdown/DesktopCommonDropdown.tsx +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/CommonDropdown/components/DesktopCommonDropdown/DesktopCommonDropdown.tsx @@ -11,13 +11,14 @@ import { CREATE_COMMON_ITEM_ID } from "../../../ProjectsTree"; import styles from "./DesktopCommonDropdown.module.scss"; interface DesktopCommonDropdownProps { + menuItemsClassName?: string; items: MenuItem[]; activeItemId?: string | null; isActive: boolean; } const DesktopCommonDropdown: FC = (props) => { - const { items, activeItemId, isActive } = props; + const { menuItemsClassName, items, activeItemId, isActive } = props; const [menuRerenderHack, setMenuRerenderHack] = useState(false); const screenSize = useSelector(getScreenSize()); const isMobileView = screenSize === ScreenSize.Mobile; @@ -77,7 +78,7 @@ const DesktopCommonDropdown: FC = (props) => {
void; onAddProjectClick?: (commonId: string) => void; isLoading?: boolean; + withScrollbar?: boolean; + commonsMenuClassName?: string; } const ProjectsTree: FC = (props) => { @@ -41,6 +43,8 @@ const ProjectsTree: FC = (props) => { onCommonCreationClick, onAddProjectClick, isLoading = false, + withScrollbar = true, + commonsMenuClassName, } = props; const menuItems = useMenuItems({ stateItems: commons, @@ -70,6 +74,24 @@ const ProjectsTree: FC = (props) => { ], ); + const itemsEl = ( + <> + + {isLoading && ( + + )} + + ); + return ( = (props) => { items={menuItems} activeItemId={currentCommonId} isActive={isParentItemActive} + menuItemsClassName={commonsMenuClassName} /> ), }} isActive={isParentItemActive} /> - - - {isLoading && ( - - )} - + {withScrollbar ? {itemsEl} : itemsEl} ); }; From 74de2708ed0978dc8ef0ce4b5f10688a94f9e8ca Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 10:58:18 +0400 Subject: [PATCH 14/42] add loaderDelay prop to projects tree --- .../LinkSpaceModal/components/Projects/Projects.tsx | 1 + .../SidenavContent/components/ProjectsTree/ProjectsTree.tsx | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx index 654d80cc65..e5d878b189 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx @@ -58,6 +58,7 @@ const Projects: FC = (props) => { isLoading={areProjectsLoading} withScrollbar={false} commonsMenuClassName={styles.commonsMenuClassName} + loaderDelay={0} /> ); }; 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 e24d920e6a..3f2364b007 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx @@ -26,6 +26,7 @@ interface ProjectsTreeProps extends BaseProjectsTreeProps { isLoading?: boolean; withScrollbar?: boolean; commonsMenuClassName?: string; + loaderDelay?: number; } const ProjectsTree: FC = (props) => { @@ -45,6 +46,7 @@ const ProjectsTree: FC = (props) => { isLoading = false, withScrollbar = true, commonsMenuClassName, + loaderDelay = LOADER_APPEARANCE_DELAY, } = props; const menuItems = useMenuItems({ stateItems: commons, @@ -86,9 +88,7 @@ const ProjectsTree: FC = (props) => { } level={INITIAL_TREE_ITEMS_LEVEL} /> - {isLoading && ( - - )} + {isLoading && } ); From 73a0fb2bb018dd6e2347bd01a181c8d60133fac1 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 11:15:55 +0400 Subject: [PATCH 15/42] fix checkIsLinkToAllowed for empty discussion circle visibility --- .../DiscussionFeedCard/utils/checkIsLinkToAllowed.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts b/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts index 2de51007d2..0c44a1540e 100644 --- a/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts +++ b/src/pages/common/components/DiscussionFeedCard/utils/checkIsLinkToAllowed.ts @@ -17,9 +17,10 @@ export const checkIsLinkToAllowed = ( []; return ( - discussionCircleVisibility.some((circleId) => - circlesWithLowestTier.some((circle) => circle.id === circleId), - ) && + (discussionCircleVisibility.length === 0 || + discussionCircleVisibility.some((circleId) => + circlesWithLowestTier.some((circle) => circle.id === circleId), + )) && hasPermission({ commonMember: options.commonMember, governance: { From da36c855cdcd4515cbde27bad6235c59e4abaf91 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 11:16:33 +0400 Subject: [PATCH 16/42] add quotes to the modal title --- .../components/LinkSpaceModal/LinkSpaceModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx index 4d4b51d06f..740c64f18b 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx @@ -41,7 +41,7 @@ const LinkSpaceModal: FC = (props) => { className={styles.modal} isShowing={isOpen} onClose={isSpaceLinkingLoading ? emptyFunction : onClose} - title={`Link ${title}`} + title={`Link “${title}“`} isHeaderSticky hideCloseButton={isSpaceLinkingLoading} mobileFullScreen From 4e5aeb48b2676616fad1e06e6ecf86ed442e07d4 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 11:41:17 +0400 Subject: [PATCH 17/42] add onItemClick to projects tree --- .../components/Projects/Projects.tsx | 3 +- .../components/ProjectsTree/ProjectsTree.tsx | 4 ++ .../TreeItemTrigger/TreeItemTrigger.tsx | 61 +++++++++++++------ .../TreeRecursive/TreeRecursive.tsx | 2 +- .../components/ProjectsTree/context.ts | 1 + 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx index e5d878b189..0b954682fb 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx @@ -15,7 +15,7 @@ interface ProjectsProps { const Projects: FC = (props) => { const { renderNoItemsInfo } = props; const [currentCommonId, setCurrentCommonId] = useState(props.rootCommonId); - const [activeItemId, setActiveItemId] = useState(props.commonId); + const [activeItemId, setActiveItemId] = useState(""); const { parentItem, areCommonsLoading, @@ -59,6 +59,7 @@ const Projects: FC = (props) => { withScrollbar={false} commonsMenuClassName={styles.commonsMenuClassName} loaderDelay={0} + onItemClick={setActiveItemId} /> ); }; 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 3f2364b007..f1c0550a66 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree/ProjectsTree.tsx @@ -23,6 +23,7 @@ interface ProjectsTreeProps extends BaseProjectsTreeProps { onCommonClick: (commonId: string) => void; onCommonCreationClick?: () => void; onAddProjectClick?: (commonId: string) => void; + onItemClick?: (itemId: string) => void; isLoading?: boolean; withScrollbar?: boolean; commonsMenuClassName?: string; @@ -43,6 +44,7 @@ const ProjectsTree: FC = (props) => { onCommonClick, onCommonCreationClick, onAddProjectClick, + onItemClick, isLoading = false, withScrollbar = true, commonsMenuClassName, @@ -65,6 +67,7 @@ const ProjectsTree: FC = (props) => { treeItemTriggerStyles, parentItemIds, onAddProjectClick, + onItemClick, }), [ activeItem?.id, @@ -73,6 +76,7 @@ const ProjectsTree: FC = (props) => { treeItemTriggerStyles, parentItemIds, onAddProjectClick, + onItemClick, ], ); diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.tsx b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.tsx index e81eb77c18..b3d5872b6c 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.tsx +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.tsx @@ -19,7 +19,7 @@ interface TreeItemTriggerProps { const TreeItemTrigger: FC = (props) => { const { className, item, level, isActive, isOpen, onToggle } = props; - const { treeItemTriggerStyles } = useTreeContext(); + const { treeItemTriggerStyles, onItemClick } = useTreeContext(); const { hasMembership = true } = item; const handleToggle: MouseEventHandler = (event) => { @@ -30,24 +30,22 @@ const TreeItemTrigger: FC = (props) => { } }; - return ( - + const handleItemClick: MouseEventHandler = () => { + onItemClick?.(item.id); + }; + + const wrapperClassName = classNames( + styles.item, + { + [classNames(styles.itemActive, treeItemTriggerStyles?.containerActive)]: + isActive, + [styles.itemWithoutMembership]: !hasMembership, + }, + className, + treeItemTriggerStyles?.container, + ); + const contentEl = ( + <> = (props) => { {item.notificationsAmount} )} + + ); + + if (onItemClick) { + return ( +
+ {contentEl} +
+ ); + } + + return ( + + {contentEl} ); }; 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 13eb943bd4..1b857a459d 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 @@ -69,7 +69,7 @@ const TreeRecursive: FC = (props) => { > {(item.items && item.items.length > 0) || item.id === itemIdWithNewProjectCreation || - hasPermissionToAddProject ? ( + (hasPermissionToAddProject && onAddProjectClick) ? ( void; + onItemClick?: (itemId: string) => void; } export const TreeContext = React.createContext({ From f0e807dee5f45ab08308ac141a319c3360eb7716 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 11:59:17 +0400 Subject: [PATCH 18/42] update tree item trigger name's color set up --- .../components/TreeItemTrigger/TreeItemTrigger.module.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.module.scss b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.module.scss index 4ff097ff33..523a950535 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.module.scss +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.module.scss @@ -1,7 +1,7 @@ @import "../../../../../../../../../constants"; .item { - --bg-color: --var(--primary-background); + --bg-color: var(--primary-background); --item-pl-per-level: 1.125rem; --item-arrow-pl: var(--item-pl-per-level); --item-image-mr: 0.5rem; @@ -25,7 +25,6 @@ } .itemActive { --bg-color: #{$c-primary-200}99; - --item-text-color: #{$white}; &:hover { --bg-color: var(--secondary-hover-fill); @@ -71,7 +70,7 @@ font-family: PoppinsSans, sans-serif; font-weight: 600; font-size: $small; - color: var(--primary-text); + color: var(--item-text-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; From c98ee87d182b7c0826aa79559c316fd1a1cc1542 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 14:29:37 +0400 Subject: [PATCH 19/42] change button's size --- .../components/LinkSpaceModal/LinkSpaceModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx index 740c64f18b..fd84ad92cb 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx @@ -1,6 +1,6 @@ import React, { FC, ReactElement } from "react"; import { Modal } from "@/shared/components"; -import { Button, ButtonSize, ButtonVariant, Loader } from "@/shared/ui-kit"; +import { Button, ButtonVariant, Loader } from "@/shared/ui-kit"; import { emptyFunction } from "@/shared/utils"; import { Projects } from "./components"; import styles from "./LinkSpaceModal.module.scss"; @@ -28,7 +28,6 @@ const LinkSpaceModal: FC = (props) => { From 40cd3af13185dea9bd047e5f6e0e18f514a01b7b Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 14:36:43 +0400 Subject: [PATCH 20/42] clear active item id on common change --- .../LinkSpaceModal/components/Projects/Projects.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx index 0b954682fb..6d0714290e 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx @@ -1,4 +1,4 @@ -import React, { FC, ReactNode, useMemo, useState } from "react"; +import React, { FC, ReactNode, useEffect, useMemo, useState } from "react"; import { LOADER_APPEARANCE_DELAY } from "@/shared/constants"; import { TreeItemTriggerStyles } from "@/shared/layouts"; import { ProjectsTree } from "@/shared/layouts/CommonSidenavLayout/components/SidenavContent/components/ProjectsTree"; @@ -36,6 +36,10 @@ const Projects: FC = (props) => { [], ); + useEffect(() => { + setActiveItemId(""); + }, [currentCommonId]); + if (!parentItem) { return areCommonsLoading ? ( From cdc0d468ca95234ae37156c82a40da0690f7eeec Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 15:01:14 +0400 Subject: [PATCH 21/42] add disabled field to tree item --- .../Projects/hooks/useProjectsData.ts | 13 +++++++-- src/services/Project.ts | 7 +++++ .../utils/generateProjectsStateItems.ts | 9 ++++++ .../TreeItemTrigger.module.scss | 3 ++ .../TreeItemTrigger/TreeItemTrigger.tsx | 11 ++++++-- .../components/ProjectsTree/context.ts | 1 + .../components/ProjectsTree/types.ts | 1 + .../commonLayout/saga/utils/getProjects.ts | 28 ++++++++++++------- src/store/states/projects/types.ts | 1 + 9 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/useProjectsData.ts b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/useProjectsData.ts index 36ed5b7507..aa350b8ae9 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/useProjectsData.ts +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/useProjectsData.ts @@ -32,6 +32,12 @@ interface Return { const generateItemCommonPagePath = () => ""; +const getAdditionalItemData = ( + projectsStateItem: ProjectsStateItem, +): Partial => ({ + disabled: !projectsStateItem.hasPermissionToLinkToHere, +}); + export const useProjectsData = (projectsInfo: ProjectsInfo): Return => { const { currentCommonId, activeItemId } = projectsInfo; const currentCommonIdRef = useRef(currentCommonId); @@ -58,18 +64,21 @@ export const useProjectsData = (projectsInfo: ProjectsInfo): Return => { ? getItemFromProjectsStateItem( currentCommon, generateItemCommonPagePath, + undefined, + getAdditionalItemData, ) : null, - [currentCommon, generateItemCommonPagePath], + [currentCommon], ); const items = useMemo(() => { const [item] = generateProjectsTreeItems( currentCommon ? projects.concat(currentCommon) : projects, generateItemCommonPagePath, + getAdditionalItemData, ); return item?.items || []; - }, [currentCommon, projects, generateItemCommonPagePath]); + }, [currentCommon, projects]); const activeItem = getItemById( activeItemId, parentItem ? [parentItem, ...items] : items, diff --git a/src/services/Project.ts b/src/services/Project.ts index f992fc6ddc..9101213312 100644 --- a/src/services/Project.ts +++ b/src/services/Project.ts @@ -20,6 +20,7 @@ class ProjectService { common: Common; hasMembership: boolean; hasPermissionToAddProject?: boolean; + hasPermissionToLinkToHere?: boolean; }[] => commons .filter((common) => common.state === CommonState.ACTIVE) @@ -39,6 +40,12 @@ class ProjectService { permissionsItem.governance.circles, permissionsItem.commonMemberCircleIds, ).allowedActions[GovernanceActions.CREATE_PROJECT], + hasPermissionToLinkToHere: + permissionsItem && + generateCirclesDataForCommonMember( + permissionsItem.governance.circles, + permissionsItem.commonMemberCircleIds, + ).allowedActions[GovernanceActions.LINK_TO_HERE], }; }); diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/generateProjectsStateItems.ts b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/generateProjectsStateItems.ts index 917802cfa9..007a356a0c 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/generateProjectsStateItems.ts +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/Projects/utils/generateProjectsStateItems.ts @@ -5,6 +5,9 @@ export const getItemFromProjectsStateItem = ( projectsStateItem: ProjectsStateItem, generatePath: (projectsStateItem: ProjectsStateItem) => string, itemsGroupedByCommonParentId?: Map, + getAdditionalItemData?: ( + projectsStateItem: ProjectsStateItem, + ) => Partial, ): Item => { const items = itemsGroupedByCommonParentId ? (itemsGroupedByCommonParentId.get(projectsStateItem.commonId) || []).map( @@ -13,6 +16,7 @@ export const getItemFromProjectsStateItem = ( subCommon, generatePath, itemsGroupedByCommonParentId, + getAdditionalItemData, ), ) : []; @@ -26,12 +30,16 @@ export const getItemFromProjectsStateItem = ( hasPermissionToAddProject: projectsStateItem.hasPermissionToAddProject, notificationsAmount: projectsStateItem.notificationsAmount, items, + ...(getAdditionalItemData?.(projectsStateItem) || {}), }; }; export const generateProjectsTreeItems = ( data: ProjectsStateItem[], generatePath: (projectsStateItem: ProjectsStateItem) => string, + getAdditionalItemData?: ( + projectsStateItem: ProjectsStateItem, + ) => Partial, ): Item[] => { const itemsGroupedByCommonParentId = data.reduce((map, item) => { const commonId = item.directParent?.commonId || null; @@ -50,6 +58,7 @@ export const generateProjectsTreeItems = ( item, generatePath, itemsGroupedByCommonParentId, + getAdditionalItemData, ), ), [], diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.module.scss b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.module.scss index 523a950535..96865cd7f0 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.module.scss +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.module.scss @@ -33,6 +33,9 @@ .itemWithoutMembership { --item-text-color: #{$c-neutrals-300}; } +.itemDisabled { + cursor: not-allowed; +} .arrowIconButton { padding: 0.75rem 1.25rem 0.75rem var(--item-arrow-pl); diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.tsx b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.tsx index b3d5872b6c..3a00ad0d4a 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.tsx +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/components/TreeItemTrigger/TreeItemTrigger.tsx @@ -31,7 +31,9 @@ const TreeItemTrigger: FC = (props) => { }; const handleItemClick: MouseEventHandler = () => { - onItemClick?.(item.id); + if (!item.disabled) { + onItemClick?.(item.id); + } }; const wrapperClassName = classNames( @@ -40,6 +42,10 @@ const TreeItemTrigger: FC = (props) => { [classNames(styles.itemActive, treeItemTriggerStyles?.containerActive)]: isActive, [styles.itemWithoutMembership]: !hasMembership, + [classNames( + styles.itemDisabled, + treeItemTriggerStyles?.containerDisabled, + )]: item.disabled, }, className, treeItemTriggerStyles?.container, @@ -89,12 +95,13 @@ const TreeItemTrigger: FC = (props) => { ); - if (onItemClick) { + if (onItemClick || item.disabled) { return (
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 5093289f4a..4c1f7ba22f 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/context.ts +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/context.ts @@ -3,6 +3,7 @@ import React, { useContext } from "react"; export interface TreeItemTriggerStyles { container?: string; containerActive?: string; + containerDisabled?: string; name?: string; image?: string; imageNonRounded?: string; diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/types.ts b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/types.ts index 1b6df24eac..5658cb20f5 100644 --- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/types.ts +++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/ProjectsTree/types.ts @@ -10,4 +10,5 @@ export interface Item { notificationsAmount?: number; rightContent?: ReactNode; items?: Item[]; + disabled?: boolean; } diff --git a/src/store/states/commonLayout/saga/utils/getProjects.ts b/src/store/states/commonLayout/saga/utils/getProjects.ts index f5abb27db3..54733ff9c5 100644 --- a/src/store/states/commonLayout/saga/utils/getProjects.ts +++ b/src/store/states/commonLayout/saga/utils/getProjects.ts @@ -29,14 +29,22 @@ export const getProjects = async ( permissionsData, ); - return data.map(({ common, hasMembership, hasPermissionToAddProject }) => ({ - commonId: common.id, - image: common.image, - name: common.name, - directParent: common.directParent, - rootCommonId: common.rootCommonId, - hasMembership, - hasPermissionToAddProject, - notificationsAmount: 0, - })); + return data.map( + ({ + common, + hasMembership, + hasPermissionToAddProject, + hasPermissionToLinkToHere, + }) => ({ + commonId: common.id, + image: common.image, + name: common.name, + directParent: common.directParent, + rootCommonId: common.rootCommonId, + hasMembership, + hasPermissionToAddProject, + hasPermissionToLinkToHere, + notificationsAmount: 0, + }), + ); }; diff --git a/src/store/states/projects/types.ts b/src/store/states/projects/types.ts index 2926b9ac13..0be2e530b5 100644 --- a/src/store/states/projects/types.ts +++ b/src/store/states/projects/types.ts @@ -8,6 +8,7 @@ export interface ProjectsStateItem { rootCommonId?: string; hasMembership?: boolean; hasPermissionToAddProject?: boolean; + hasPermissionToLinkToHere?: boolean; notificationsAmount?: number; } From 0908f5a3d9cafcacd84bf78d58d74b6d0d4fb84e Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 15:07:37 +0400 Subject: [PATCH 22/42] disable Apply button if no item is selected --- .../components/LinkSpaceModal/LinkSpaceModal.tsx | 11 +++++++++-- .../LinkSpaceModal/components/Projects/Projects.tsx | 9 +++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx index fd84ad92cb..d3c2e34ee3 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx @@ -1,4 +1,4 @@ -import React, { FC, ReactElement } from "react"; +import React, { FC, ReactElement, useState } from "react"; import { Modal } from "@/shared/components"; import { Button, ButtonVariant, Loader } from "@/shared/ui-kit"; import { emptyFunction } from "@/shared/utils"; @@ -15,6 +15,7 @@ interface DirectMessageModalProps { const LinkSpaceModal: FC = (props) => { const { isOpen, onClose, title, rootCommonId, commonId } = props; + const [activeItemId, setActiveItemId] = useState(""); const isSpaceLinkingLoading = false; const renderContent = (): ReactElement => { @@ -24,10 +25,16 @@ const LinkSpaceModal: FC = (props) => { return ( <> - + diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx index 6d0714290e..36ba38b959 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.tsx @@ -9,13 +9,14 @@ import styles from "./Projects.module.scss"; interface ProjectsProps { rootCommonId: string; commonId: string; + activeItemId: string; + onActiveItemId: (activeItemId: string) => void; renderNoItemsInfo?: () => ReactNode; } const Projects: FC = (props) => { - const { renderNoItemsInfo } = props; + const { activeItemId, onActiveItemId, renderNoItemsInfo } = props; const [currentCommonId, setCurrentCommonId] = useState(props.rootCommonId); - const [activeItemId, setActiveItemId] = useState(""); const { parentItem, areCommonsLoading, @@ -37,7 +38,7 @@ const Projects: FC = (props) => { ); useEffect(() => { - setActiveItemId(""); + onActiveItemId(""); }, [currentCommonId]); if (!parentItem) { @@ -63,7 +64,7 @@ const Projects: FC = (props) => { withScrollbar={false} commonsMenuClassName={styles.commonsMenuClassName} loaderDelay={0} - onItemClick={setActiveItemId} + onItemClick={onActiveItemId} /> ); }; From b927a03ecdeee202d13a992e3cdda5931a92c57b Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 15:36:27 +0400 Subject: [PATCH 23/42] add logic to link streams --- .../DiscussionFeedCard/DiscussionFeedCard.tsx | 1 + .../LinkSpaceModal/LinkSpaceModal.tsx | 41 ++++++++++-- src/services/CommonFeed.ts | 9 +++ src/shared/constants/endpoint.ts | 1 + src/shared/hooks/useCases/index.ts | 1 + src/shared/hooks/useCases/useStreamLinking.ts | 64 +++++++++++++++++++ src/shared/interfaces/LinkStreamPayload.ts | 6 ++ src/shared/interfaces/index.tsx | 1 + 8 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 src/shared/hooks/useCases/useStreamLinking.ts create mode 100644 src/shared/interfaces/LinkStreamPayload.ts diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index 7e162c5e3c..a5049b69bd 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -388,6 +388,7 @@ const DiscussionFeedCard = forwardRef( void; + feedItemId: string; title: string; rootCommonId: string; commonId: string; } const LinkSpaceModal: FC = (props) => { - const { isOpen, onClose, title, rootCommonId, commonId } = props; + const { isOpen, onClose, feedItemId, title, rootCommonId, commonId } = props; + const { notify } = useNotification(); + const { isStreamLinking, isStreamLinked, linkStream } = useStreamLinking(); const [activeItemId, setActiveItemId] = useState(""); - const isSpaceLinkingLoading = false; + const user = useSelector(selectUser()); + const userId = user?.uid; + + const handleSubmit = () => { + if (!userId) { + return; + } + + linkStream({ + userId, + feedObjectId: feedItemId, + sourceCommonId: commonId, + targetCommonId: activeItemId, + }); + }; const renderContent = (): ReactElement => { - if (isSpaceLinkingLoading) { + if (isStreamLinking) { return ; } @@ -35,6 +56,7 @@ const LinkSpaceModal: FC = (props) => { className={styles.submitButton} variant={ButtonVariant.PrimaryPink} disabled={!activeItemId} + onClick={handleSubmit} > Apply @@ -42,14 +64,21 @@ const LinkSpaceModal: FC = (props) => { ); }; + useEffect(() => { + if (isStreamLinked) { + notify("Stream is successfully linked"); + onClose(); + } + }, [isStreamLinking, isStreamLinked]); + return ( => { + const { cancelToken } = options; + await Api.post(ApiEndpoint.LinkStream, payload, { cancelToken }); + }; + public markCommonFeedItemAsUnseen = ( commonId: string, feedObjectId: string, diff --git a/src/shared/constants/endpoint.ts b/src/shared/constants/endpoint.ts index a5cc923ae7..62d925c6b3 100644 --- a/src/shared/constants/endpoint.ts +++ b/src/shared/constants/endpoint.ts @@ -8,6 +8,7 @@ export const ApiEndpoint = { CreateSubCommon: "/commons/subcommon/create", MarkFeedObjectSeenForUser: "/commons/mark-feed-object-seen-for-user", MarkFeedObjectUnseenForUser: "/commons/mark-feed-object-unseen-for-user", + LinkStream: "/commons/link-stream", AcceptRules: "/commons/accept-rules", GetCommonFeedItems: "/commons/:commonId/feed-items", GetCommonPinnedFeedItems: "/commons/:commonId/pinned-feed-items", diff --git a/src/shared/hooks/useCases/index.ts b/src/shared/hooks/useCases/index.ts index 1fda0b1ab5..b4a2b70424 100644 --- a/src/shared/hooks/useCases/index.ts +++ b/src/shared/hooks/useCases/index.ts @@ -21,6 +21,7 @@ export type { ChangePaymentMethodState } from "./usePaymentMethodChange"; export { useProjectCreation } from "./useProjectCreation"; export { useProposalById } from "./useProposalById"; export { useRootCommonMembershipIntro } from "./useRootCommonMembershipIntro"; +export { useStreamLinking } from "./useStreamLinking"; export { useSubCommons } from "./useSubCommons"; export { useSupportersData } from "./useSupportersData"; export { useLastVisitedCommon } from "./useLastVisitedCommon"; diff --git a/src/shared/hooks/useCases/useStreamLinking.ts b/src/shared/hooks/useCases/useStreamLinking.ts new file mode 100644 index 0000000000..18bcea748b --- /dev/null +++ b/src/shared/hooks/useCases/useStreamLinking.ts @@ -0,0 +1,64 @@ +import { useCallback, useRef, useState } from "react"; +import { + CancelTokenSource, + CommonFeedService, + isRequestCancelled, + getCancelTokenSource, + Logger, +} from "@/services"; +import { LinkStreamPayload } from "@/shared/interfaces"; + +interface State { + isStreamLinking: boolean; + isStreamLinked: boolean; +} + +interface Return extends State { + linkStream: (payload: LinkStreamPayload) => void; +} + +export const useStreamLinking = (): Return => { + const cancelTokenRef = useRef(null); + const [state, setState] = useState({ + isStreamLinking: false, + isStreamLinked: false, + }); + + const linkStream = useCallback(async (payload: LinkStreamPayload) => { + if (cancelTokenRef.current) { + cancelTokenRef.current.cancel(); + } + + try { + setState({ + isStreamLinking: true, + isStreamLinked: false, + }); + cancelTokenRef.current = getCancelTokenSource(); + + await CommonFeedService.linkStream(payload, { + cancelToken: cancelTokenRef.current.token, + }); + + cancelTokenRef.current = null; + setState({ + isStreamLinking: false, + isStreamLinked: true, + }); + } catch (error) { + if (!isRequestCancelled(error)) { + Logger.error(error); + cancelTokenRef.current = null; + setState({ + isStreamLinking: false, + isStreamLinked: false, + }); + } + } + }, []); + + return { + ...state, + linkStream, + }; +}; diff --git a/src/shared/interfaces/LinkStreamPayload.ts b/src/shared/interfaces/LinkStreamPayload.ts new file mode 100644 index 0000000000..210db0cec2 --- /dev/null +++ b/src/shared/interfaces/LinkStreamPayload.ts @@ -0,0 +1,6 @@ +export interface LinkStreamPayload { + feedObjectId: string; + sourceCommonId: string; + targetCommonId: string; + userId: string; +} diff --git a/src/shared/interfaces/index.tsx b/src/shared/interfaces/index.tsx index 2a828c1f8b..58d1f48b88 100644 --- a/src/shared/interfaces/index.tsx +++ b/src/shared/interfaces/index.tsx @@ -6,6 +6,7 @@ export * from "./CreateProjectPayload"; export * from "./DMUser"; export * from "./feedLayout"; export * from "./ChatChannelFeedLayoutItemProps"; +export * from "./LinkStreamPayload"; export * from "./LoadingState"; export * from "./MarkCommonFeedItemAsSeenPayload"; export * from "./MenuItem"; From 6fb7861298626c113cf138be673c7173ea3b0bc1 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 12 Dec 2023 16:08:37 +0400 Subject: [PATCH 24/42] add link icon to feed item title --- .../components/DiscussionFeedCard/DiscussionFeedCard.tsx | 8 ++++++++ src/pages/common/components/FeedCard/FeedCard.tsx | 3 +++ .../FeedItemBaseContent/FeedItemBaseContent.module.scss | 9 +++++++++ .../FeedItemBaseContent/FeedItemBaseContent.tsx | 4 +++- src/pages/common/components/FeedItem/context.ts | 1 + 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index a5049b69bd..88c72d51e7 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -168,6 +168,13 @@ const DiscussionFeedCard = forwardRef( !isFeedItemUserMetadataFetched || !commonId; const cardTitle = discussion?.title; + const linkedCommonIds = discussion?.linkedCommonIds || []; + const isLinked = Boolean( + commonId && + linkedCommonIds.length > 0 && + (linkedCommonIds.includes(commonId) || + discussion?.commonId === commonId), + ); const handleOpenChat = useCallback(() => { if (discussion) { @@ -352,6 +359,7 @@ const DiscussionFeedCard = forwardRef( isFeedItemUserMetadataFetched && feedItemUserMetadata?.hasUnseenMention } + isLinked={isLinked} > {renderContent()} diff --git a/src/pages/common/components/FeedCard/FeedCard.tsx b/src/pages/common/components/FeedCard/FeedCard.tsx index 05a97ed22c..2c7ad0f24e 100644 --- a/src/pages/common/components/FeedCard/FeedCard.tsx +++ b/src/pages/common/components/FeedCard/FeedCard.tsx @@ -48,6 +48,7 @@ type FeedCardProps = PropsWithChildren<{ hasImages?: boolean; hasUnseenMention?: boolean; notion?: CommonNotion; + isLinked?: boolean; }>; const MOBILE_HEADER_HEIGHT = 52; @@ -89,6 +90,7 @@ export const FeedCard = forwardRef((props, ref) => { hasImages, hasFiles, notion, + isLinked, } = props; const scrollTimeoutRef = useRef | null>(null); const isTabletView = useIsTabletView(); @@ -213,6 +215,7 @@ export const FeedCard = forwardRef((props, ref) => { hasImages, hasUnseenMention, notion, + isLinked, })}
)} diff --git a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.module.scss b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.module.scss index aad650aa0d..94c4c0c9d0 100644 --- a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.module.scss +++ b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.module.scss @@ -170,3 +170,12 @@ max-width: 20rem; z-index: 3; } + +.linkIcon { + width: 1rem; + height: 1rem; + + &:hover { + color: $c-pink-next-dark; + } +} diff --git a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx index 43b663ab6c..e2c1af1ae7 100644 --- a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx +++ b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx @@ -1,7 +1,7 @@ import React, { FC, MouseEventHandler, useRef, useState } from "react"; import classNames from "classnames"; import { useLongPress } from "use-long-press"; -import { NotionIcon } from "@/shared/icons"; +import { Link4Icon, NotionIcon } from "@/shared/icons"; import { checkIsTextEditorValueEmpty, ContextMenu, @@ -41,6 +41,7 @@ export const FeedItemBaseContent: FC = (props) => { shouldHideBottomContent = false, hasUnseenMention, notion, + isLinked, } = props; const contextMenuRef = useRef(null); const [isLongPressing, setIsLongPressing] = useState(false); @@ -135,6 +136,7 @@ export const FeedItemBaseContent: FC = (props) => { )} + {isLinked && }

Date: Tue, 12 Dec 2023 16:34:20 +0400 Subject: [PATCH 25/42] add markers to the linked commons or original one --- .../DiscussionFeedCard/DiscussionFeedCard.tsx | 2 + .../LinkSpaceModal/LinkSpaceModal.tsx | 15 +++++- .../components/Projects/Projects.tsx | 17 ++++++- .../NameRightContent.module.scss | 11 +++++ .../NameRightContent/NameRightContent.tsx | 25 ++++++++++ .../components/NameRightContent/index.ts | 1 + .../Projects/hooks/components/index.ts | 1 + ...useProjectsData.ts => useProjectsData.tsx} | 46 +++++++++++-------- .../TreeItemTrigger/TreeItemTrigger.tsx | 1 + .../components/ProjectsTree/types.ts | 1 + 10 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/components/NameRightContent/NameRightContent.module.scss create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/components/NameRightContent/NameRightContent.tsx create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/components/NameRightContent/index.ts create mode 100644 src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/components/index.ts rename src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/hooks/{useProjectsData.ts => useProjectsData.tsx} (73%) diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index 88c72d51e7..04c155b65d 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -400,6 +400,8 @@ const DiscussionFeedCard = forwardRef( title={cardTitle || ""} rootCommonId={rootCommonId || commonId} commonId={commonId} + originalCommonId={discussion?.commonId || ""} + linkedCommonIds={discussion?.linkedCommonIds} /> )} diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx index ba2527bbab..e55f077963 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/LinkSpaceModal.tsx @@ -16,10 +16,21 @@ interface DirectMessageModalProps { title: string; rootCommonId: string; commonId: string; + originalCommonId: string; + linkedCommonIds?: string[]; } const LinkSpaceModal: FC = (props) => { - const { isOpen, onClose, feedItemId, title, rootCommonId, commonId } = props; + const { + isOpen, + onClose, + feedItemId, + title, + rootCommonId, + commonId, + originalCommonId, + linkedCommonIds = [], + } = props; const { notify } = useNotification(); const { isStreamLinking, isStreamLinked, linkStream } = useStreamLinking(); const [activeItemId, setActiveItemId] = useState(""); @@ -51,6 +62,8 @@ const LinkSpaceModal: FC = (props) => { commonId={commonId} activeItemId={activeItemId} onActiveItemId={setActiveItemId} + originalCommonId={originalCommonId} + linkedCommonIds={linkedCommonIds} /> +

+ +
); }; diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss index b63bc8125f..3d704d4b27 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss @@ -2,6 +2,7 @@ @import "../../../../../../../../styles/sizes"; .projectsTree { + overflow-y: auto; box-sizing: border-box; } From 0f9831c36d85e19c8e778f54642147f06078fee8 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 18 Dec 2023 14:56:34 +0400 Subject: [PATCH 29/42] fix root common selection --- .../states/commonLayout/saga/getCommons.ts | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/store/states/commonLayout/saga/getCommons.ts b/src/store/states/commonLayout/saga/getCommons.ts index 78b2956aac..9609685827 100644 --- a/src/store/states/commonLayout/saga/getCommons.ts +++ b/src/store/states/commonLayout/saga/getCommons.ts @@ -98,16 +98,24 @@ export function* getCommons( .sort((prevItem, nextItem) => compareCommonsByLastActivity(prevItem.common, nextItem.common), ) - .map(({ common, hasMembership, hasPermissionToAddProject }) => ({ - commonId: common.id, - image: common.image, - name: common.name, - directParent: common.directParent, - rootCommonId: common.rootCommonId, - hasMembership, - hasPermissionToAddProject, - notificationsAmount: 0, - })); + .map( + ({ + common, + hasMembership, + hasPermissionToAddProject, + hasPermissionToLinkToHere, + }) => ({ + commonId: common.id, + image: common.image, + name: common.name, + directParent: common.directParent, + rootCommonId: common.rootCommonId, + hasMembership, + hasPermissionToAddProject, + hasPermissionToLinkToHere, + notificationsAmount: 0, + }), + ); yield put( actions.getCommons.success({ From 30d08fab69abcac0b7d421204bece31b7489fd8f Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 18 Dec 2023 15:03:17 +0400 Subject: [PATCH 30/42] fix active item text and bg colors --- .../components/Projects/Projects.module.scss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss index 3d704d4b27..46ff06846f 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss @@ -14,13 +14,16 @@ border-radius: 0; &:hover { - --bg-color: #{var(--hover-fill)}; + --bg-color: #{var(--secondary-text)}; + --item-text-color: #{$c-shades-white}; } } .projectsTreeItemTriggerActiveClassName { - --bg-color: #{var(--secondary-background)}; + --bg-color: #{var(--primary-fill)}; + --item-text-color: #{$c-shades-white}; + &:hover { - --bg-color: #{var(--hover-fill)}; + --bg-color: #{var(--primary-fill)}; } } From 03c5705aee573c8987f1532eb40661fe72d6f621 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 18 Dec 2023 16:00:50 +0400 Subject: [PATCH 31/42] create LinkedItemMark component --- .../FeedItemBaseContent.module.scss | 9 --------- .../FeedItemBaseContent/FeedItemBaseContent.tsx | 5 +++-- .../LinkedItemMark/LinkedItemMark.module.scss | 10 ++++++++++ .../components/LinkedItemMark/LinkedItemMark.tsx | 15 +++++++++++++++ .../FeedCard/components/LinkedItemMark/index.ts | 1 + 5 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss create mode 100644 src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx create mode 100644 src/pages/common/components/FeedCard/components/LinkedItemMark/index.ts diff --git a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.module.scss b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.module.scss index 94c4c0c9d0..aad650aa0d 100644 --- a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.module.scss +++ b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.module.scss @@ -170,12 +170,3 @@ max-width: 20rem; z-index: 3; } - -.linkIcon { - width: 1rem; - height: 1rem; - - &:hover { - color: $c-pink-next-dark; - } -} diff --git a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx index e2c1af1ae7..4248bedc73 100644 --- a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx +++ b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx @@ -1,7 +1,7 @@ import React, { FC, MouseEventHandler, useRef, useState } from "react"; import classNames from "classnames"; import { useLongPress } from "use-long-press"; -import { Link4Icon, NotionIcon } from "@/shared/icons"; +import { NotionIcon } from "@/shared/icons"; import { checkIsTextEditorValueEmpty, ContextMenu, @@ -14,6 +14,7 @@ import { } from "@/shared/ui-kit"; import { FeedItemBaseContentProps } from "../../../FeedItem"; import { FeedCardTags } from "../FeedCardTags"; +import { LinkedItemMark } from "../LinkedItemMark"; import styles from "./FeedItemBaseContent.module.scss"; export const FeedItemBaseContent: FC = (props) => { @@ -136,7 +137,7 @@ export const FeedItemBaseContent: FC = (props) => { )} - {isLinked && } + {isLinked && }

= (props) => { + const { a } = props; + + return ; +}; + +export default LinkedItemMark; diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/index.ts b/src/pages/common/components/FeedCard/components/LinkedItemMark/index.ts new file mode 100644 index 0000000000..068cc7e617 --- /dev/null +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/index.ts @@ -0,0 +1 @@ +export { default as LinkedItemMark } from "./LinkedItemMark"; From 987fafe60fe0b5767e76008146243de25b208a5d Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 18 Dec 2023 16:19:06 +0400 Subject: [PATCH 32/42] add basic tooltip element to linked item mark --- .../LinkedItemMark/LinkedItemMark.module.scss | 12 ++++++++++ .../LinkedItemMark/LinkedItemMark.tsx | 23 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss index e6d5ee340b..d28744fe20 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss @@ -8,3 +8,15 @@ color: $c-pink-next-dark; } } + +.tooltipContent { + max-width: 20rem; + z-index: 3; +} + +.contentTitle { + display: inline-block; + margin-bottom: 0.5rem; + font-size: 0.75rem; + color: $c-gray-40; +} diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx index 73dac7feb8..5c3b0e96d6 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx @@ -1,5 +1,7 @@ -import React, { FC } from "react"; +import React, { FC, MouseEventHandler, useState } from "react"; +import { ButtonIcon } from "@/shared/components"; import { Link4Icon } from "@/shared/icons"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui-kit"; import styles from "./LinkedItemMark.module.scss"; interface LinkedItemMarkProps { @@ -8,8 +10,25 @@ interface LinkedItemMarkProps { const LinkedItemMark: FC = (props) => { const { a } = props; + const [isOpen, setIsOpen] = useState(false); - return ; + const toggleTooltip: MouseEventHandler = (event) => { + event.stopPropagation(); + setIsOpen((v) => !v); + }; + + return ( + + + + + + + + Also appears in: + + + ); }; export default LinkedItemMark; From ce1972eda73e6e3e1df99e6aafd997d80ae65617 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 12:19:24 +0400 Subject: [PATCH 33/42] add logic to fetch common paths --- .../DiscussionFeedCard/DiscussionFeedCard.tsx | 10 +--- .../common/components/FeedCard/FeedCard.tsx | 9 ++-- .../FeedItemBaseContent.tsx | 19 ++++++- .../LinkedItemMark/LinkedItemMark.tsx | 31 ++++++++++-- .../components/LinkedItemMark/hooks/index.ts | 1 + .../LinkedItemMark/hooks/useCommonPaths.ts | 49 +++++++++++++++++++ .../common/components/FeedItem/context.ts | 3 +- src/services/Common.ts | 18 +++++++ 8 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 src/pages/common/components/FeedCard/components/LinkedItemMark/hooks/index.ts create mode 100644 src/pages/common/components/FeedCard/components/LinkedItemMark/hooks/useCommonPaths.ts diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index 04c155b65d..19d5f746c8 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -168,13 +168,6 @@ const DiscussionFeedCard = forwardRef( !isFeedItemUserMetadataFetched || !commonId; const cardTitle = discussion?.title; - const linkedCommonIds = discussion?.linkedCommonIds || []; - const isLinked = Boolean( - commonId && - linkedCommonIds.length > 0 && - (linkedCommonIds.includes(commonId) || - discussion?.commonId === commonId), - ); const handleOpenChat = useCallback(() => { if (discussion) { @@ -359,7 +352,8 @@ const DiscussionFeedCard = forwardRef( isFeedItemUserMetadataFetched && feedItemUserMetadata?.hasUnseenMention } - isLinked={isLinked} + originalCommonIdForLinking={discussion?.commonId} + linkedCommonIds={discussion?.linkedCommonIds} > {renderContent()} diff --git a/src/pages/common/components/FeedCard/FeedCard.tsx b/src/pages/common/components/FeedCard/FeedCard.tsx index 2c7ad0f24e..c413933ef5 100644 --- a/src/pages/common/components/FeedCard/FeedCard.tsx +++ b/src/pages/common/components/FeedCard/FeedCard.tsx @@ -48,7 +48,8 @@ type FeedCardProps = PropsWithChildren<{ hasImages?: boolean; hasUnseenMention?: boolean; notion?: CommonNotion; - isLinked?: boolean; + originalCommonIdForLinking?: string; + linkedCommonIds?: string[]; }>; const MOBILE_HEADER_HEIGHT = 52; @@ -90,7 +91,8 @@ export const FeedCard = forwardRef((props, ref) => { hasImages, hasFiles, notion, - isLinked, + originalCommonIdForLinking, + linkedCommonIds, } = props; const scrollTimeoutRef = useRef | null>(null); const isTabletView = useIsTabletView(); @@ -215,7 +217,8 @@ export const FeedCard = forwardRef((props, ref) => { hasImages, hasUnseenMention, notion, - isLinked, + originalCommonIdForLinking, + linkedCommonIds, })} )} diff --git a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx index 4248bedc73..1d9d191f9d 100644 --- a/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx +++ b/src/pages/common/components/FeedCard/components/FeedItemBaseContent/FeedItemBaseContent.tsx @@ -20,6 +20,7 @@ import styles from "./FeedItemBaseContent.module.scss"; export const FeedItemBaseContent: FC = (props) => { const { className, + commonId, titleWrapperClassName, lastActivity, unreadMessages, @@ -42,12 +43,20 @@ export const FeedItemBaseContent: FC = (props) => { shouldHideBottomContent = false, hasUnseenMention, notion, - isLinked, + originalCommonIdForLinking, + linkedCommonIds, } = props; const contextMenuRef = useRef(null); const [isLongPressing, setIsLongPressing] = useState(false); const [isLongPressed, setIsLongPressed] = useState(false); const isContextMenuEnabled = Boolean(menuItems && menuItems.length > 0); + const isLinked = Boolean( + commonId && + linkedCommonIds && + linkedCommonIds.length > 0 && + (linkedCommonIds.includes(commonId) || + originalCommonIdForLinking === commonId), + ); // Here we get either MouseEven, or TouchEven, but I was struggling with importing them from react // and use here to have correct types. @@ -137,7 +146,13 @@ export const FeedItemBaseContent: FC = (props) => { )} - {isLinked && } + {isLinked && ( + + )}

= (props) => { - const { a } = props; + const { + currentCommonId, + originalCommonId = "", + linkedCommonIds = [], + } = props; const [isOpen, setIsOpen] = useState(false); + const { + data: commonPaths, + loading, + fetched, + fetchCommonPaths, + } = useCommonPaths(); const toggleTooltip: MouseEventHandler = (event) => { event.stopPropagation(); setIsOpen((v) => !v); }; + useEffect(() => { + if (!isOpen || loading || fetched) { + return; + } + + const commonIds = linkedCommonIds + .concat(originalCommonId) + .filter((commonId) => commonId && commonId !== currentCommonId); + + fetchCommonPaths(commonIds); + }, [isOpen]); + return ( diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/hooks/index.ts b/src/pages/common/components/FeedCard/components/LinkedItemMark/hooks/index.ts new file mode 100644 index 0000000000..9a25620f16 --- /dev/null +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useCommonPaths"; diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/hooks/useCommonPaths.ts b/src/pages/common/components/FeedCard/components/LinkedItemMark/hooks/useCommonPaths.ts new file mode 100644 index 0000000000..88822da5c2 --- /dev/null +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/hooks/useCommonPaths.ts @@ -0,0 +1,49 @@ +import { CommonService } from "@/services"; +import { useIsMounted, useLoadingState } from "@/shared/hooks"; +import { LoadingState } from "@/shared/interfaces"; +import { Common } from "@/shared/models"; + +type Data = Common[][]; + +interface Return extends LoadingState { + fetchCommonPaths: (commonIdsForPaths: string[]) => void; +} + +export const useCommonPaths = (): Return => { + const isMounted = useIsMounted(); + const [state, setState] = useLoadingState([]); + + const fetchCommonPaths = async (commonIdsForPaths: string[]) => { + setState({ + loading: true, + fetched: false, + data: [], + }); + + let commons: Data = []; + + try { + commons = await Promise.all( + commonIdsForPaths.map((commonId) => + CommonService.getCommonAndParents(commonId), + ), + ); + commons = commons.filter((path) => path.length > 0); + } catch (err) { + commons = []; + } finally { + if (isMounted()) { + setState({ + loading: false, + fetched: true, + data: commons, + }); + } + } + }; + + return { + ...state, + fetchCommonPaths, + }; +}; diff --git a/src/pages/common/components/FeedItem/context.ts b/src/pages/common/components/FeedItem/context.ts index 74d4c2bf0d..4e95c468c3 100644 --- a/src/pages/common/components/FeedItem/context.ts +++ b/src/pages/common/components/FeedItem/context.ts @@ -47,7 +47,8 @@ export interface FeedItemBaseContentProps { dmUserId?: string; hasUnseenMention?: boolean; notion?: CommonNotion; - isLinked?: boolean; + originalCommonIdForLinking?: string; + linkedCommonIds?: string[]; } export interface GetLastMessageOptions { diff --git a/src/services/Common.ts b/src/services/Common.ts index 1ae52d0355..b80ace63b6 100644 --- a/src/services/Common.ts +++ b/src/services/Common.ts @@ -323,6 +323,24 @@ class CommonService { return finalCommons; }; + public getCommonAndParents = async ( + commonId: string, + cached = false, + ): Promise => { + const common = await this.getCommonById(commonId, cached); + + if (!common) { + return []; + } + + const parentCommons = await this.getAllParentCommonsForCommon( + common, + cached, + ); + + return [...parentCommons, common]; + }; + public getParentCommonForCommonId = async ( commonId: string, ): Promise => { From b8a0e132824625686f25af500f2e258be7518de5 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 12:29:50 +0400 Subject: [PATCH 34/42] display common paths in tooltip --- .../LinkedItemMark/LinkedItemMark.module.scss | 49 +++++++++++- .../LinkedItemMark/LinkedItemMark.tsx | 75 ++++++++++++++++++- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss index d28744fe20..274eae13db 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss @@ -10,8 +10,10 @@ } .tooltipContent { - max-width: 20rem; + max-width: 30rem; z-index: 3; + display: flex; + flex-direction: column; } .contentTitle { @@ -20,3 +22,48 @@ font-size: 0.75rem; color: $c-gray-40; } + +.loader { + margin: 0 auto; +} + +.link { + width: 100%; + margin-bottom: 0.625rem; + display: flex; + align-items: center; + font-size: 0.875rem; + text-decoration: none; + + &:hover { + color: $c-pink-primary; + } + + &:last-child { + margin-bottom: 0; + } +} + +.linkLeft, +.linkRight { + white-space: nowrap; + overflow: hidden; +} + +.linkLeft { + display: flex; + align-items: center; + text-overflow: ellipsis; +} + +.linkRight { + flex-shrink: 0; +} + +.ellipsis { + flex-shrink: 0; +} + +.arrowIcon { + flex-shrink: 0; +} diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx index 42f8f5727f..0d6bcf42fb 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx @@ -1,7 +1,21 @@ -import React, { FC, MouseEventHandler, useEffect, useState } from "react"; +import React, { + FC, + MouseEventHandler, + ReactElement, + useEffect, + useState, +} from "react"; +import { NavLink } from "react-router-dom"; import { ButtonIcon } from "@/shared/components"; -import { Link4Icon } from "@/shared/icons"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui-kit"; +import { useRoutesContext } from "@/shared/contexts"; +import { RightArrowThinIcon, Link4Icon } from "@/shared/icons"; +import { Common } from "@/shared/models"; +import { + Loader, + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/shared/ui-kit"; import { useCommonPaths } from "./hooks"; import styles from "./LinkedItemMark.module.scss"; @@ -24,6 +38,7 @@ const LinkedItemMark: FC = (props) => { fetched, fetchCommonPaths, } = useCommonPaths(); + const { getCommonPagePath } = useRoutesContext(); const toggleTooltip: MouseEventHandler = (event) => { event.stopPropagation(); @@ -42,6 +57,43 @@ const LinkedItemMark: FC = (props) => { fetchCommonPaths(commonIds); }, [isOpen]); + const renderFullPath = (commons: Common[]): ReactElement => { + return ( +

+ {commons.map((common, commonIndex) => ( + + {common.name} + {commonIndex !== commons.length - 1 && ( + + )} + + ))} +
+ ); + }; + + const renderCutPath = (commons: Common[]): ReactElement => { + const lastCommon = commons[commons.length - 1]; + const parentCommons = commons.slice(0, -1); + + return ( + <> +
+ {parentCommons.map((common, commonIndex) => ( + + {common.name} + {commonIndex !== commons.length - 1 && ( + + )} + + ))} +
+
+
{lastCommon?.name}
+ + ); + }; + return ( @@ -51,6 +103,23 @@ const LinkedItemMark: FC = (props) => { Also appears in: + {loading && } + {!loading && + commonPaths.map((commons, index) => { + const lastCommon = commons[commons.length - 1]; + const key = lastCommon?.id || String(index); + + return ( + + {renderCutPath(commons)} + + ); + })} ); From a055b5325bf9adc29f974977f3d11f3d40af1549 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 13:59:35 +0400 Subject: [PATCH 35/42] cut common paths in tooltip --- .../LinkedItemMark/LinkedItemMark.module.scss | 15 +++-- .../LinkedItemMark/LinkedItemMark.tsx | 65 +++++-------------- .../LinkedItemTooltipContent.module.scss | 24 +++++++ .../LinkedItemTooltipContent.tsx | 61 +++++++++++++++++ .../LinkedItemTooltipContent/index.ts | 1 + .../LinkedItemMark/components/index.ts | 1 + 6 files changed, 112 insertions(+), 55 deletions(-) create mode 100644 src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.module.scss create mode 100644 src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.tsx create mode 100644 src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/index.ts create mode 100644 src/pages/common/components/FeedCard/components/LinkedItemMark/components/index.ts diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss index 274eae13db..d10f248dfa 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss @@ -1,4 +1,5 @@ @import "../../../../../../constants"; +@import "../../../../../../styles/sizes"; .linkIcon { width: 1rem; @@ -11,9 +12,13 @@ .tooltipContent { max-width: 30rem; - z-index: 3; + z-index: 1; display: flex; flex-direction: column; + + @include tablet { + max-width: 100vw; + } } .contentTitle { @@ -29,7 +34,6 @@ .link { width: 100%; - margin-bottom: 0.625rem; display: flex; align-items: center; font-size: 0.875rem; @@ -38,10 +42,9 @@ &:hover { color: $c-pink-primary; } - - &:last-child { - margin-bottom: 0; - } +} +.linkWithBottomMargin { + margin-bottom: 0.625rem; } .linkLeft, diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx index 0d6bcf42fb..c6df7d05da 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx @@ -1,21 +1,17 @@ -import React, { - FC, - MouseEventHandler, - ReactElement, - useEffect, - useState, -} from "react"; +import React, { FC, MouseEventHandler, useEffect, useState } from "react"; import { NavLink } from "react-router-dom"; +import classNames from "classnames"; import { ButtonIcon } from "@/shared/components"; import { useRoutesContext } from "@/shared/contexts"; -import { RightArrowThinIcon, Link4Icon } from "@/shared/icons"; -import { Common } from "@/shared/models"; +import { useIsTabletView } from "@/shared/hooks/viewport"; +import { Link4Icon } from "@/shared/icons"; import { Loader, Tooltip, TooltipContent, TooltipTrigger, } from "@/shared/ui-kit"; +import { LinkedItemTooltipContent } from "./components"; import { useCommonPaths } from "./hooks"; import styles from "./LinkedItemMark.module.scss"; @@ -31,6 +27,7 @@ const LinkedItemMark: FC = (props) => { originalCommonId = "", linkedCommonIds = [], } = props; + const isTabletView = useIsTabletView(); const [isOpen, setIsOpen] = useState(false); const { data: commonPaths, @@ -57,45 +54,12 @@ const LinkedItemMark: FC = (props) => { fetchCommonPaths(commonIds); }, [isOpen]); - const renderFullPath = (commons: Common[]): ReactElement => { - return ( -
- {commons.map((common, commonIndex) => ( - - {common.name} - {commonIndex !== commons.length - 1 && ( - - )} - - ))} -
- ); - }; - - const renderCutPath = (commons: Common[]): ReactElement => { - const lastCommon = commons[commons.length - 1]; - const parentCommons = commons.slice(0, -1); - - return ( - <> -
- {parentCommons.map((common, commonIndex) => ( - - {common.name} - {commonIndex !== commons.length - 1 && ( - - )} - - ))} -
-
-
{lastCommon?.name}
- - ); - }; - return ( - + @@ -112,11 +76,14 @@ const LinkedItemMark: FC = (props) => { return ( - {renderCutPath(commons)} + ); })} diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.module.scss b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.module.scss new file mode 100644 index 0000000000..e688805ad6 --- /dev/null +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.module.scss @@ -0,0 +1,24 @@ +.linkLeft, +.linkRight { + white-space: nowrap; + overflow: hidden; +} + +.linkLeft { + display: flex; + align-items: center; + text-overflow: ellipsis; +} + +.linkRight { + flex-shrink: 0; + max-width: 25rem; +} + +.ellipsis { + flex-shrink: 0; +} + +.arrowIcon { + flex-shrink: 0; +} diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.tsx b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.tsx new file mode 100644 index 0000000000..310bedb9f1 --- /dev/null +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.tsx @@ -0,0 +1,61 @@ +import React, { FC, ReactElement, useEffect, useState } from "react"; +import { RightArrowThinIcon } from "@/shared/icons"; +import { Common } from "@/shared/models"; +import styles from "./LinkedItemTooltipContent.module.scss"; + +interface LinkedItemTooltipContentProps { + commons: Common[]; +} + +const renderCutPath = (commons: Common[]): ReactElement => { + const lastCommon = commons[commons.length - 1]; + const parentCommons = commons.slice(0, -1); + + return ( + <> +
+ {parentCommons.map((common, commonIndex) => ( + + {common.name} + {commonIndex !== commons.length - 1 && ( + + )} + + ))} +
+
+
{lastCommon?.name}
+ + ); +}; + +const LinkedItemTooltipContent: FC = (props) => { + const { commons } = props; + const [containerRef, setContainerRef] = useState(null); + const [shouldCut, setShouldCut] = useState(false); + + useEffect(() => { + if (containerRef && containerRef.scrollWidth > containerRef.clientWidth) { + setShouldCut(true); + } + }, [containerRef]); + + if (shouldCut) { + return renderCutPath(commons); + } + + return ( +
+ {commons.map((common, commonIndex) => ( + + {common.name} + {commonIndex !== commons.length - 1 && ( + + )} + + ))} +
+ ); +}; + +export default LinkedItemTooltipContent; diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/index.ts b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/index.ts new file mode 100644 index 0000000000..d7f01d8657 --- /dev/null +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/index.ts @@ -0,0 +1 @@ +export { default as LinkedItemTooltipContent } from "./LinkedItemTooltipContent"; diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/components/index.ts b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/index.ts new file mode 100644 index 0000000000..7ba868bb69 --- /dev/null +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/index.ts @@ -0,0 +1 @@ +export * from "./LinkedItemTooltipContent"; From 4c2ee15ff7afdb4e7dcb0fb7c616957848b2df9e Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 14:55:29 +0400 Subject: [PATCH 36/42] add safe polygon on hover --- .../FeedCard/components/LinkedItemMark/LinkedItemMark.tsx | 1 + src/shared/ui-kit/Tooltip/hooks/useTooltip.ts | 7 ++++++- src/shared/ui-kit/Tooltip/types.ts | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx index c6df7d05da..cf72c00b21 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx @@ -59,6 +59,7 @@ const LinkedItemMark: FC = (props) => { open={isOpen} onOpenChange={setIsOpen} placement={isTabletView ? "bottom-start" : "right"} + shouldOpenOnHover={!isTabletView} > diff --git a/src/shared/ui-kit/Tooltip/hooks/useTooltip.ts b/src/shared/ui-kit/Tooltip/hooks/useTooltip.ts index 95315e5e2e..8590c8453b 100644 --- a/src/shared/ui-kit/Tooltip/hooks/useTooltip.ts +++ b/src/shared/ui-kit/Tooltip/hooks/useTooltip.ts @@ -4,6 +4,7 @@ import { autoUpdate, offset, flip, + safePolygon, shift, arrow, useHover, @@ -20,9 +21,12 @@ export const useTooltip = (options: TooltipOptions = {}) => { placement = "top", open: controlledOpen, onOpenChange: setControlledOpen, + shouldOpenOnHover = false, } = options; const [uncontrolledOpen, setUncontrolledOpen] = useState(initialOpen); const arrowRef = useRef(null); + const shouldEnableHover = + shouldOpenOnHover ?? typeof controlledOpen === "undefined"; const open = controlledOpen ?? uncontrolledOpen; const setOpen = setControlledOpen ?? setUncontrolledOpen; @@ -46,7 +50,8 @@ export const useTooltip = (options: TooltipOptions = {}) => { const hover = useHover(context, { move: false, - enabled: typeof controlledOpen === "undefined", + enabled: shouldEnableHover, + handleClose: shouldEnableHover ? safePolygon() : null, }); const focus = useFocus(context, { enabled: typeof controlledOpen === "undefined", diff --git a/src/shared/ui-kit/Tooltip/types.ts b/src/shared/ui-kit/Tooltip/types.ts index 60d7302ee2..883d3403d7 100644 --- a/src/shared/ui-kit/Tooltip/types.ts +++ b/src/shared/ui-kit/Tooltip/types.ts @@ -5,4 +5,5 @@ export interface TooltipOptions { placement?: Placement; open?: boolean; onOpenChange?: (open: boolean) => void; + shouldOpenOnHover?: boolean; } From e5e14e70937a6632c334c117e20a52ece1514a07 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 14:57:18 +0400 Subject: [PATCH 37/42] refactor loader displaying in tooltip --- .../FeedCard/components/LinkedItemMark/LinkedItemMark.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx index cf72c00b21..acea50a294 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.tsx @@ -68,8 +68,8 @@ const LinkedItemMark: FC = (props) => { Also appears in: - {loading && } - {!loading && + {!fetched && } + {fetched && commonPaths.map((commons, index) => { const lastCommon = commons[commons.length - 1]; const key = lastCommon?.id || String(index); From 051790396fd69a1227f8ccb86f14c9e533ad2379 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 17:57:33 +0400 Subject: [PATCH 38/42] add default color for common path --- .../components/LinkedItemMark/LinkedItemMark.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss index d10f248dfa..43b1419dcd 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss @@ -37,6 +37,7 @@ display: flex; align-items: center; font-size: 0.875rem; + color: $c-shades-black; text-decoration: none; &:hover { From adb29919bceaea07afa0a76166d5c41fc8b2b984 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 18:50:38 +0400 Subject: [PATCH 39/42] display arrow before last common name --- .../LinkedItemTooltipContent/LinkedItemTooltipContent.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.tsx b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.tsx index 310bedb9f1..27c8860392 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.tsx +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.tsx @@ -17,14 +17,17 @@ const renderCutPath = (commons: Common[]): ReactElement => { {parentCommons.map((common, commonIndex) => ( {common.name} - {commonIndex !== commons.length - 1 && ( + {commonIndex !== parentCommons.length - 1 && ( )} ))}
-
{lastCommon?.name}
+
+ + {lastCommon?.name} +
); }; From a4e79586be57c674b8256be66bd28db89c149318 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 18:57:41 +0400 Subject: [PATCH 40/42] fix colors of hovered tree items on mobile --- .../components/Projects/Projects.module.scss | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss index 46ff06846f..4ab141036b 100644 --- a/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss +++ b/src/pages/common/components/DiscussionFeedCard/components/LinkSpaceModal/components/Projects/Projects.module.scss @@ -14,16 +14,30 @@ border-radius: 0; &:hover { - --bg-color: #{var(--secondary-text)}; + --bg-color: var(--secondary-text); --item-text-color: #{$c-shades-white}; } + + @media (hover: none) { + &:hover { + --bg-color: var(--primary-background); + --item-text-color: var(--primary-text); + } + } } .projectsTreeItemTriggerActiveClassName { - --bg-color: #{var(--primary-fill)}; + --bg-color: var(--primary-fill); --item-text-color: #{$c-shades-white}; &:hover { - --bg-color: #{var(--primary-fill)}; + --bg-color: var(--primary-fill); + } + + @media (hover: none) { + &:hover { + --bg-color: var(--primary-fill); + --item-text-color: #{$c-shades-white}; + } } } From 366efbe1f0940f8bd92debbd1991a8765bb21b27 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 18:59:21 +0400 Subject: [PATCH 41/42] align last common name with arrow correctly --- .../LinkedItemTooltipContent.module.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.module.scss b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.module.scss index e688805ad6..7b275b858f 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.module.scss +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/components/LinkedItemTooltipContent/LinkedItemTooltipContent.module.scss @@ -13,6 +13,8 @@ .linkRight { flex-shrink: 0; max-width: 25rem; + display: flex; + align-items: center; } .ellipsis { From 33d1e93ff083620ed552d9920fc1ddd65b560bda Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 19 Dec 2023 21:06:13 +0400 Subject: [PATCH 42/42] change linked item mark text colors --- .../components/LinkedItemMark/LinkedItemMark.module.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss index 43b1419dcd..2db524b911 100644 --- a/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss +++ b/src/pages/common/components/FeedCard/components/LinkedItemMark/LinkedItemMark.module.scss @@ -37,11 +37,11 @@ display: flex; align-items: center; font-size: 0.875rem; - color: $c-shades-black; + color: var(--primary-text); text-decoration: none; &:hover { - color: $c-pink-primary; + color: var(--primary-fill); } } .linkWithBottomMargin {