diff --git a/public/assets/images/apps-qr.svg b/public/assets/images/apps-qr.svg deleted file mode 100644 index ebd37a7520..0000000000 --- a/public/assets/images/apps-qr.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/assets/images/iphone-full-1.png b/public/assets/images/iphone-full-1.png deleted file mode 100644 index 2a0de2caef..0000000000 Binary files a/public/assets/images/iphone-full-1.png and /dev/null differ diff --git a/public/assets/images/iphone-full-2.png b/public/assets/images/iphone-full-2.png deleted file mode 100644 index 12cea8bfb1..0000000000 Binary files a/public/assets/images/iphone-full-2.png and /dev/null differ diff --git a/public/assets/images/iphone-full-3.png b/public/assets/images/iphone-full-3.png deleted file mode 100644 index 7ef7660f76..0000000000 Binary files a/public/assets/images/iphone-full-3.png and /dev/null differ diff --git a/public/assets/images/iphone-half-1.png b/public/assets/images/iphone-half-1.png deleted file mode 100644 index ebbd48fd8b..0000000000 Binary files a/public/assets/images/iphone-half-1.png and /dev/null differ diff --git a/public/assets/images/iphone-half-2.png b/public/assets/images/iphone-half-2.png deleted file mode 100644 index 8ef9694099..0000000000 Binary files a/public/assets/images/iphone-half-2.png and /dev/null differ diff --git a/public/assets/images/iphone-half-3.png b/public/assets/images/iphone-half-3.png deleted file mode 100644 index 8e6eac8056..0000000000 Binary files a/public/assets/images/iphone-half-3.png and /dev/null differ diff --git a/public/assets/images/iphone-half-desktop.png b/public/assets/images/iphone-half-desktop.png deleted file mode 100644 index ee432e0044..0000000000 Binary files a/public/assets/images/iphone-half-desktop.png and /dev/null differ diff --git a/public/assets/images/iphone-half-mobile.png b/public/assets/images/iphone-half-mobile.png deleted file mode 100644 index 177786a72e..0000000000 Binary files a/public/assets/images/iphone-half-mobile.png and /dev/null differ diff --git a/public/assets/images/join-mobile.jpg b/public/assets/images/join-mobile.jpg deleted file mode 100644 index 32554d1c41..0000000000 Binary files a/public/assets/images/join-mobile.jpg and /dev/null differ diff --git a/public/assets/images/join.jpg b/public/assets/images/join.jpg deleted file mode 100644 index d9ac3d0fcd..0000000000 Binary files a/public/assets/images/join.jpg and /dev/null differ diff --git a/public/assets/images/landing-mobile-poster.jpg b/public/assets/images/landing-mobile-poster.jpg new file mode 100644 index 0000000000..4ff0d06e68 Binary files /dev/null and b/public/assets/images/landing-mobile-poster.jpg differ diff --git a/public/assets/images/main-landing-image-1.jpg b/public/assets/images/main-landing-image-1.jpg deleted file mode 100644 index 8381909e27..0000000000 Binary files a/public/assets/images/main-landing-image-1.jpg and /dev/null differ diff --git a/public/assets/images/main-landing-image-2.jpg b/public/assets/images/main-landing-image-2.jpg deleted file mode 100644 index 5bc5a55ae9..0000000000 Binary files a/public/assets/images/main-landing-image-2.jpg and /dev/null differ diff --git a/public/assets/images/main-landing-image-3.jpg b/public/assets/images/main-landing-image-3.jpg deleted file mode 100644 index c0330c3283..0000000000 Binary files a/public/assets/images/main-landing-image-3.jpg and /dev/null differ diff --git a/src/pages/Landing/components/LandingContainer/VideoSection/VideoSection.tsx b/src/pages/Landing/components/LandingContainer/VideoSection/VideoSection.tsx index 00b6a0f053..4852e714b1 100644 --- a/src/pages/Landing/components/LandingContainer/VideoSection/VideoSection.tsx +++ b/src/pages/Landing/components/LandingContainer/VideoSection/VideoSection.tsx @@ -1,7 +1,8 @@ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; -import landingVideoPosterSrc from "@/shared/assets/images/landing-video-poster.jpeg"; +import landingVideoPosterSrc from "@/shared/assets/images/landing-video-poster.jpg"; import landingVideoSrc from "@/shared/assets/videos/landing-video.mp4"; +import { useIsBigPhoneView } from "@/shared/hooks/viewport"; import { Button, ButtonVariant } from "@/shared/ui-kit"; import "./index.scss"; @@ -13,21 +14,24 @@ const VideoSection: FC = ({ onLaunchClick }) => { const { t } = useTranslation("translation", { keyPrefix: "landing", }); + const isBigPhoneView = useIsBigPhoneView(); return (
- + {!isBigPhoneView && ( + + )}

diff --git a/src/pages/Landing/components/LandingContainer/VideoSection/index.scss b/src/pages/Landing/components/LandingContainer/VideoSection/index.scss index 1488912fa1..fead75a6ac 100644 --- a/src/pages/Landing/components/LandingContainer/VideoSection/index.scss +++ b/src/pages/Landing/components/LandingContainer/VideoSection/index.scss @@ -6,6 +6,13 @@ $viewport-breakpoint: 1000px; .landing-video-section { position: relative; + + @include big-phone { + background-image: url("/assets/images/landing-mobile-poster.jpg"); + background-size: cover; + background-position: center; + height: 37.125rem; + } } .landing-video-section__video-wrapper { diff --git a/src/pages/MyAccount/components/Profile/components/UserDetails/UserDetails.module.scss b/src/pages/MyAccount/components/Profile/components/UserDetails/UserDetails.module.scss index da7141e309..9af7df8f38 100644 --- a/src/pages/MyAccount/components/Profile/components/UserDetails/UserDetails.module.scss +++ b/src/pages/MyAccount/components/Profile/components/UserDetails/UserDetails.module.scss @@ -83,15 +83,6 @@ } } -.editProfileButton { - --btn-h: 1.5rem; - --btn-pl: 0.25rem; - --btn-pr: 0.25rem; - - margin-top: 0.25rem; - font-weight: 500; -} - .userPhoto { width: 6.25rem; height: 6.25rem; diff --git a/src/pages/MyAccount/components/Profile/components/UserDetails/UserDetails.tsx b/src/pages/MyAccount/components/Profile/components/UserDetails/UserDetails.tsx index 57dc568219..a2f8c92335 100644 --- a/src/pages/MyAccount/components/Profile/components/UserDetails/UserDetails.tsx +++ b/src/pages/MyAccount/components/Profile/components/UserDetails/UserDetails.tsx @@ -289,23 +289,11 @@ const UserDetails: ForwardRefRenderFunction< />

{!isEditing && ( - <> - - {isMobileView && ( - - )} - + )} {!isEditing && user.intro && ( diff --git a/src/pages/OldCommon/components/CommonDetailContainer/MembershipRequestModal/MembershipRequestIntroduce.tsx b/src/pages/OldCommon/components/CommonDetailContainer/MembershipRequestModal/MembershipRequestIntroduce.tsx index 56e4cc4274..9c06712769 100644 --- a/src/pages/OldCommon/components/CommonDetailContainer/MembershipRequestModal/MembershipRequestIntroduce.tsx +++ b/src/pages/OldCommon/components/CommonDetailContainer/MembershipRequestModal/MembershipRequestIntroduce.tsx @@ -1,11 +1,9 @@ import React, { useCallback } from "react"; -import { useSelector } from "react-redux"; import { Formik, FormikConfig } from "formik"; -import { Button } from "@/shared/components"; import { Form, TextField, LinksArray } from "@/shared/components/Form/Formik"; -import { ScreenSize, MAX_LINK_TITLE_LENGTH } from "@/shared/constants"; +import { MAX_LINK_TITLE_LENGTH } from "@/shared/constants"; import { CommonLink } from "@/shared/models"; -import { getScreenSize } from "@/shared/store/selectors"; +import { Button, ButtonVariant } from "@/shared/ui-kit"; import { parseLinksForSubmission } from "@/shared/utils"; import { IStageProps } from "./MembershipRequestModal"; import { MembershipRequestStage } from "./constants"; @@ -25,8 +23,6 @@ const getInitialValues = (data: IStageProps["userData"]): FormValues => ({ export default function MembershipRequestIntroduce(props: IStageProps) { const { userData, setUserData, governance } = props; - const screenSize = useSelector(getScreenSize()); - const isMobileView = screenSize === ScreenSize.Mobile; const handleSubmit = useCallback["onSubmit"]>( (values) => { @@ -95,7 +91,7 @@ export default function MembershipRequestIntroduce(props: IStageProps) { className="membership-request-introduce__submit-button" type="submit" disabled={!isValid} - shouldUseFullWidth={isMobileView} + variant={ButtonVariant.PrimaryPink} > Continue diff --git a/src/pages/OldCommon/components/CommonListContainer/CreateCommonModal/CreateCommonModal.tsx b/src/pages/OldCommon/components/CommonListContainer/CreateCommonModal/CreateCommonModal.tsx index 7548b94acb..2167e9e57a 100644 --- a/src/pages/OldCommon/components/CommonListContainer/CreateCommonModal/CreateCommonModal.tsx +++ b/src/pages/OldCommon/components/CommonListContainer/CreateCommonModal/CreateCommonModal.tsx @@ -284,6 +284,7 @@ export default function CreateCommonModal(props: CreateCommonModalProps) { image: createdCommonData.common.image, name: createdCommonData.common.name, directParent: createdCommonData.common.directParent, + rootCommonId: createdCommonData.common.rootCommonId, hasMembership: true, hasPermissionToAddProject, notificationsAmount: 0, diff --git a/src/pages/common/components/ChatComponent/ChatComponent.tsx b/src/pages/common/components/ChatComponent/ChatComponent.tsx index 6b086e0edf..0abf8d3382 100644 --- a/src/pages/common/components/ChatComponent/ChatComponent.tsx +++ b/src/pages/common/components/ChatComponent/ChatComponent.tsx @@ -11,7 +11,7 @@ import { useDispatch, useSelector } from "react-redux"; import { useDebounce, useMeasure } from "react-use"; import classNames from "classnames"; import isHotkey from "is-hotkey"; -import { delay, omit } from "lodash"; +import { debounce, delay, omit } from "lodash"; import { v4 as uuidv4 } from "uuid"; import { selectUser } from "@/pages/Auth/store/selectors"; import { ChatService, DiscussionMessageService, FileService } from "@/services"; @@ -59,6 +59,7 @@ import { selectFilesPreview, FileInfo, } from "@/store/states"; +import { ChatContentContext, ChatContentData } from "../CommonContent/context"; import { ChatContent, ChatContentRef, @@ -181,6 +182,15 @@ export default function ChatComponent({ const chatContentRef = useRef(null); const chatWrapperId = useMemo(() => `chat-wrapper-${uuidv4()}`, []); const chatInputWrapperRef = useRef(null); + const chatContainerRef = useRef(null); + const [isScrolling, setScrolling] = useState(false); + const chatContentContextValue: ChatContentData = useMemo( + () => ({ + isScrolling, + chatContentRect: chatContainerRef.current?.getBoundingClientRect(), + }), + [isScrolling], + ); const [message, setMessage] = useState( parseStringToTextEditorValue(), @@ -591,6 +601,23 @@ export default function ChatComponent({ }; }, []); + useEffect(() => { + const deactivateScrollingFlag = debounce(() => { + setScrolling(false); + }, 300); + + function handleScroll() { + setScrolling(true); + deactivateScrollingFlag(); + } + + chatContainerRef.current?.addEventListener("scroll", handleScroll); + + return () => { + chatContainerRef.current?.removeEventListener("scroll", handleScroll); + }; + }, []); + const renderChatInput = (): ReactNode => { const shouldHideChatInput = !isChatChannel && (!hasAccess || isHidden); @@ -666,32 +693,35 @@ export default function ChatComponent({ [styles.emptyChat]: !dateList.length, })} id={chatWrapperId} + ref={chatContainerRef} > - + + +
diff --git a/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx b/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx index 17c3d6c60a..e047d3c936 100644 --- a/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx +++ b/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx @@ -127,20 +127,6 @@ const ChatContent: ForwardRefRenderFunction< [chatWrapperId], ); - const scrollMore = useCallback( - (toY: number) => - setTimeout( - () => - animateScroll.scrollMore(toY, { - containerId: chatWrapperId, - smooth: true, - delay: 0, - }), - 0, - ), - [chatWrapperId], - ); - const dateListReverse = useMemo(() => [...dateList].reverse(), [dateList]); useEffect(() => { @@ -241,17 +227,6 @@ const ChatContent: ForwardRefRenderFunction< scrollToRepliedMessage={scrollToRepliedMessage} highlighted={message.id === highlightedMessageId} hasPermissionToHide={hasPermissionToHide} - onMessageDropdownOpen={(isOpen, messageTopPosition = 0) => { - const dropdownHeight = 240; - const visibleDropdownHeight = - window.innerHeight - messageTopPosition; - const hasEnoughSpaceForMenu = - visibleDropdownHeight >= dropdownHeight; - - if (isOpen && !hasEnoughSpaceForMenu) { - scrollMore(dropdownHeight - visibleDropdownHeight + 20); - } - }} users={users} feedItemId={feedItemId} commonMember={commonMember} diff --git a/src/pages/common/components/CommonContent/context.ts b/src/pages/common/components/CommonContent/context.ts new file mode 100644 index 0000000000..8d8716534c --- /dev/null +++ b/src/pages/common/components/CommonContent/context.ts @@ -0,0 +1,13 @@ +import React, { useContext } from "react"; + +export interface ChatContentData { + isScrolling: boolean; + chatContentRect?: DOMRect; +} + +export const ChatContentContext = React.createContext({ + isScrolling: false, +}); + +export const useChatContentContext = (): ChatContentData => + useContext(ChatContentContext); diff --git a/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.module.scss b/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.module.scss index a7bc347d2c..4ce189cea1 100644 --- a/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.module.scss +++ b/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.module.scss @@ -2,9 +2,9 @@ @import "../../../../../../../../../../styles/sizes"; .joinButton { - max-width: 8.5rem; margin-top: 1.75rem; padding: 0 0.875rem; + width: 100%; @include tablet { margin-top: 1.625rem; diff --git a/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.tsx b/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.tsx index db5c6179d9..95d06595b2 100644 --- a/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.tsx +++ b/src/pages/common/components/CommonTabPanels/components/AboutTab/components/CommonEntranceInfo/components/CommonEntranceJoin/CommonEntranceJoin.tsx @@ -1,5 +1,5 @@ import React, { FC } from "react"; -import { NavLink } from "react-router-dom"; +import { NavLink, useHistory } from "react-router-dom"; import { useJoinProjectAutomatically } from "@/pages/common/hooks"; import { useCommonDataContext } from "@/pages/common/providers"; import { useRoutesContext } from "@/shared/contexts"; @@ -14,7 +14,8 @@ interface CommonEntranceJoinProps { } const CommonEntranceJoin: FC = (props) => { - const { withJoinRequest = false, common, isProject } = props; + const { withJoinRequest, common, isProject } = props; + const history = useHistory(); const { parentCommon, parentCommonMember, @@ -47,13 +48,13 @@ const CommonEntranceJoin: FC = (props) => {

)} {!commonMember && rootCommon && !rootCommonMember && ( -

- Join via{" "} - - {rootCommon.name} - {" "} - page -

+ )} {!commonMember && rootCommonMember && @@ -67,7 +68,7 @@ const CommonEntranceJoin: FC = (props) => { page

)} - {withJoinRequest && (isJoinAllowed || isJoinPending) && ( + {withJoinRequest && !isProject && (isJoinAllowed || isJoinPending) && (
diff --git a/src/shared/components/Dropdown/Dropdown.tsx b/src/shared/components/Dropdown/Dropdown.tsx index 68e0b0d4a0..8614194041 100644 --- a/src/shared/components/Dropdown/Dropdown.tsx +++ b/src/shared/components/Dropdown/Dropdown.tsx @@ -7,7 +7,6 @@ import React, { CSSProperties, ForwardRefRenderFunction, ReactNode, - RefObject, useEffect, } from "react"; import { @@ -21,9 +20,11 @@ import { } from "react-aria-menubutton"; import classNames from "classnames"; import { v4 as uuidv4 } from "uuid"; +import { useChatContentContext } from "@/pages/common/components/CommonContent/context"; import { Loader } from "@/shared/components"; import RightArrowIcon from "../../icons/rightArrow.icon"; import { GlobalOverlay } from "../GlobalOverlay"; +import { getMenuStyles } from "./helpers"; import "./index.scss"; export interface Styles { @@ -79,53 +80,6 @@ export interface DropdownProps { disabled?: boolean; } -const getFixedMenuStyles = ( - ref: RefObject, - menuRef: HTMLUListElement | null, -): CSSProperties | undefined => { - if (!ref.current || !menuRef) { - return; - } - - const { top, left, height } = ref.current.getBoundingClientRect(); - const menuRect = menuRef.getBoundingClientRect(); - const bottom = top + height + menuRect.height; - const styles: CSSProperties = { - left, - top: top + height, - }; - - if (window.innerHeight < bottom) { - styles.top = top - menuRect.height; - } - if (styles.top && styles.top < 0) { - styles.top = 0; - styles.bottom = 0; - styles.maxHeight = "100%"; - } - - return styles; -}; - -const getMenuStyles = ( - ref: RefObject, - menuRef: HTMLUListElement | null, - shouldBeFixed?: boolean, -): CSSProperties | undefined => { - if (!menuRef) { - return; - } - if (shouldBeFixed) { - return getFixedMenuStyles(ref, menuRef); - } - - const { right } = menuRef.getBoundingClientRect(); - - if (window.innerWidth < right) { - return { right: 0 }; - } -}; - const Dropdown: ForwardRefRenderFunction = ( props, dropdownRef, @@ -153,6 +107,8 @@ const Dropdown: ForwardRefRenderFunction = ( const [isOpen, setIsOpen] = useState(false); const selectedOption = options.find((option) => option.value === value); const dropdownId = useMemo(() => `dropdown-${uuidv4()}`, []); + const { isScrolling: isChatScrolling, chatContentRect } = + useChatContentContext(); const handleSelection: MenuWrapperProps["onSelection"] = ( value, @@ -179,9 +135,16 @@ const Dropdown: ForwardRefRenderFunction = ( } }; + useEffect(() => { + if (isMenuOpen && isChatScrolling) { + handleMenuToggle({ isOpen: false }); + closeMenu(dropdownId); + } + }, [isMenuOpen, isChatScrolling]); + const menuStyles = useMemo( - () => getMenuStyles(menuButtonRef, menuRef, shouldBeFixed), - [menuRef, shouldBeFixed], + () => getMenuStyles(menuButtonRef, menuRef, chatContentRect, shouldBeFixed), + [menuRef, shouldBeFixed, chatContentRect], ); useImperativeHandle( diff --git a/src/shared/components/Dropdown/helpers.ts b/src/shared/components/Dropdown/helpers.ts new file mode 100644 index 0000000000..a0fe50ee04 --- /dev/null +++ b/src/shared/components/Dropdown/helpers.ts @@ -0,0 +1,62 @@ +import { CSSProperties, RefObject } from "react"; + +const getFixedMenuStyles = ( + ref: RefObject, + menuRef: HTMLUListElement | null, +): CSSProperties | undefined => { + if (!ref.current || !menuRef) { + return; + } + + const { top, left, height } = ref.current.getBoundingClientRect(); + const menuRect = menuRef.getBoundingClientRect(); + const bottom = top + height + menuRect.height; + const styles: CSSProperties = { + left, + top: top + height, + }; + + if (window.innerHeight < bottom) { + styles.top = top - menuRect.height; + } + if (styles.top && Number(styles.top) < 0) { + styles.top = 0; + styles.bottom = 0; + styles.maxHeight = "100%"; + } + + return styles; +}; + +export const getMenuStyles = ( + ref: RefObject, + menuRef: HTMLUListElement | null, + chatContentRect?: DOMRect, + shouldBeFixed?: boolean, +): CSSProperties | undefined => { + if (!menuRef) { + return; + } + if (shouldBeFixed) { + return getFixedMenuStyles(ref, menuRef); + } + + const styles: CSSProperties = {}; + const { right, height } = menuRef.getBoundingClientRect(); + + if (window.innerWidth < right) { + styles.right = 0; + } + + if (ref.current && chatContentRect) { + const { bottom: menuButtonBottom, height: menuButtonHeight } = + ref.current.getBoundingClientRect(); + const menuBottom = menuButtonBottom + height; + + if (chatContentRect.bottom < menuBottom) { + styles.bottom = menuButtonHeight; + } + } + + return styles; +}; diff --git a/src/shared/constants/viewportBreakpoint.ts b/src/shared/constants/viewportBreakpoint.ts index 6b693b5a54..b6b3d8b8d6 100644 --- a/src/shared/constants/viewportBreakpoint.ts +++ b/src/shared/constants/viewportBreakpoint.ts @@ -3,6 +3,7 @@ export enum ViewportBreakpoint { Desktop = 1560, Laptop = 1152, Tablet = 768, + BigPhone = 450, Phone = 390, } diff --git a/src/shared/hooks/useCases/index.ts b/src/shared/hooks/useCases/index.ts index 41dc1342fb..8be24f937c 100644 --- a/src/shared/hooks/useCases/index.ts +++ b/src/shared/hooks/useCases/index.ts @@ -1,9 +1,11 @@ +export { useAllUserCommonMemberInfo } from "./useAllUserCommonMemberInfo"; export { useChatChannelUserStatus } from "./useChatChannelUserStatus"; export { useChatMessages } from "./useChatMessages"; export { useCommon } from "./useCommon"; export { useCommonFeedItems } from "./useCommonFeedItems"; export { useCommonMembersWithCircleIdsAmount } from "./useCommonMembersWithCircleIdsAmount"; export { useCommonRulesAcceptance } from "./useCommonRulesAcceptance"; +export { useCommonsByDirectParentId } from "./useCommonsByDirectParentId"; export { useDiscussionById } from "./useDiscussionById"; export { useDMUserChatChannel } from "./useDMUserChatChannel"; export { useFeedItemUserMetadata } from "./useFeedItemUserMetadata"; @@ -28,6 +30,7 @@ export { useUserFeedItemFollowData } from "./useUserFeedItemFollowData"; export { useFeedItemFollow } from "./useFeedItemFollow"; export type { FeedItemFollowState } from "./useFeedItemFollow"; export { useGovernance } from "./useGovernance"; +export { useGovernanceListByCommonIds } from "./useGovernanceListByCommonIds"; export { useGovernanceByCommonId } from "./useGovernanceByCommonId"; export { useUserInfoAboutMemberships } from "./useUserInfoAboutMemberships"; export { useBankAccountDetails } from "./useBankAccountDetails"; diff --git a/src/shared/hooks/useCases/useAllUserCommonMemberInfo.ts b/src/shared/hooks/useCases/useAllUserCommonMemberInfo.ts new file mode 100644 index 0000000000..73007a6343 --- /dev/null +++ b/src/shared/hooks/useCases/useAllUserCommonMemberInfo.ts @@ -0,0 +1,87 @@ +import { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; +import { selectUser } from "@/pages/Auth/store/selectors"; +import { CommonService } from "@/services"; +import { Awaited, LoadingState } from "@/shared/interfaces"; + +type State = LoadingState +> | null>; + +type Return = State; + +export const useAllUserCommonMemberInfo = (): Return => { + const user = useSelector(selectUser()); + const userId = user?.uid; + const [state, setState] = useState({ + loading: true, + fetched: false, + data: null, + }); + + useEffect(() => { + if (!userId) { + return; + } + + const unsubscribe = CommonService.subscribeToAllUserCommonMemberInfo( + userId, + (data) => { + setState((currentState) => { + if (!currentState.data || currentState.data.length === 0) { + return { + loading: false, + fetched: true, + data: data.map((item) => ({ + ...item.commonMember, + commonId: item.commonId, + })), + }; + } + + const nextData = [...currentState.data]; + + data.forEach((item) => { + const itemIndex = nextData.findIndex( + ({ commonId }) => commonId === item.commonId, + ); + + if (itemIndex === -1) { + nextData.push({ + ...item.commonMember, + commonId: item.commonId, + }); + return; + } + + if (item.statuses.isRemoved) { + nextData.splice(itemIndex, 1); + } else { + nextData[itemIndex] = { + ...item.commonMember, + commonId: item.commonId, + }; + } + }); + + return { + loading: false, + fetched: true, + data: nextData, + }; + }); + }, + ); + + return () => { + unsubscribe(); + setState({ + loading: true, + fetched: false, + data: null, + }); + }; + }, [userId]); + + return state; +}; diff --git a/src/shared/hooks/useCases/useCommonsByDirectParentId.ts b/src/shared/hooks/useCases/useCommonsByDirectParentId.ts new file mode 100644 index 0000000000..1be4c6028d --- /dev/null +++ b/src/shared/hooks/useCases/useCommonsByDirectParentId.ts @@ -0,0 +1,73 @@ +import { useEffect, useState } from "react"; +import { CommonService } from "@/services"; +import { LoadingState } from "@/shared/interfaces"; +import { Common } from "@/shared/models"; + +type State = LoadingState; + +type Return = State; + +export const useCommonsByDirectParentId = (parentCommonId?: string): Return => { + const [state, setState] = useState({ + loading: true, + fetched: false, + data: null, + }); + + useEffect(() => { + if (!parentCommonId) { + return; + } + + const unsubscribe = CommonService.subscribeToCommonsByDirectParentId( + parentCommonId, + (data) => { + setState((currentState) => { + if (!currentState.data || currentState.data.length === 0) { + return { + loading: false, + fetched: true, + data: data.map((item) => item.common), + }; + } + + const nextData = [...currentState.data]; + + data.forEach((item) => { + const itemIndex = nextData.findIndex( + ({ id }) => id === item.common.id, + ); + + if (itemIndex === -1) { + nextData.push(item.common); + return; + } + + if (item.statuses.isRemoved) { + nextData.splice(itemIndex, 1); + } else { + nextData[itemIndex] = item.common; + } + }); + + return { + loading: false, + fetched: true, + data: nextData, + }; + }); + }, + ); + + return () => { + unsubscribe(); + setState({ + loading: true, + fetched: false, + data: null, + }); + }; + }, [parentCommonId]); + + return state; +}; diff --git a/src/shared/hooks/useCases/useGovernanceListByCommonIds.ts b/src/shared/hooks/useCases/useGovernanceListByCommonIds.ts new file mode 100644 index 0000000000..5d8ddba5df --- /dev/null +++ b/src/shared/hooks/useCases/useGovernanceListByCommonIds.ts @@ -0,0 +1,66 @@ +import { useEffect, useState } from "react"; +import { GovernanceService } from "@/services"; +import { LoadingState } from "@/shared/interfaces"; +import { Governance } from "@/shared/models"; + +type State = LoadingState; + +type Return = State; + +export const useGovernanceListByCommonIds = (commonIds: string[]): Return => { + const [state, setState] = useState({ + loading: true, + fetched: false, + data: null, + }); + + useEffect(() => { + if (commonIds.length === 0) { + return; + } + + const unsubscribe = GovernanceService.subscribeToGovernanceListByCommonIds( + commonIds, + (data) => { + setState((currentState) => { + if (!currentState.data || currentState.data.length === 0) { + return { + loading: false, + fetched: true, + data: data.map((item) => item.governance), + }; + } + + const nextData = [...currentState.data]; + + data.forEach((item) => { + const itemIndex = nextData.findIndex( + ({ id }) => id === item.governance.id, + ); + + if (itemIndex === -1) { + nextData.push(item.governance); + return; + } + + if (item.statuses.isRemoved) { + nextData.splice(itemIndex, 1); + } else { + nextData[itemIndex] = item.governance; + } + }); + + return { + loading: false, + fetched: true, + data: nextData, + }; + }); + }, + ); + + return unsubscribe; + }, [commonIds]); + + return state; +}; diff --git a/src/shared/hooks/viewport/index.ts b/src/shared/hooks/viewport/index.ts index 642e70bf13..e16fad16b9 100644 --- a/src/shared/hooks/viewport/index.ts +++ b/src/shared/hooks/viewport/index.ts @@ -4,3 +4,4 @@ export * from "./useIsLaptopView"; export * from "./useIsPhoneOrientedView"; export * from "./useIsPhoneView"; export * from "./useIsTabletView"; +export * from "./useIsBigPhoneView"; diff --git a/src/shared/hooks/viewport/useIsBigPhoneView.ts b/src/shared/hooks/viewport/useIsBigPhoneView.ts new file mode 100644 index 0000000000..f97d7d9ee4 --- /dev/null +++ b/src/shared/hooks/viewport/useIsBigPhoneView.ts @@ -0,0 +1,5 @@ +import { ViewportBreakpoint } from "@/shared/constants"; +import useScreenSize from "../useScreenSize"; + +export const useIsBigPhoneView = (): boolean => + useScreenSize(`max-width: ${ViewportBreakpoint.BigPhone}px`); diff --git a/src/shared/icons/copy.icon.tsx b/src/shared/icons/copy.icon.tsx index 4db0c41a84..6d07a03261 100644 --- a/src/shared/icons/copy.icon.tsx +++ b/src/shared/icons/copy.icon.tsx @@ -17,16 +17,16 @@ export default function CopyIcon({ className }: CopyIconProps): ReactElement { ); diff --git a/src/shared/icons/hide.icon.tsx b/src/shared/icons/hide.icon.tsx index 32d65942b9..d9078dd53a 100644 --- a/src/shared/icons/hide.icon.tsx +++ b/src/shared/icons/hide.icon.tsx @@ -17,23 +17,23 @@ export default function HideIcon({ className }: HideIconProps): ReactElement { ); diff --git a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsSubscription.ts b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsSubscription.ts index 54797df4b4..0cb9d5fbca 100644 --- a/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsSubscription.ts +++ b/src/shared/layouts/CommonSidenavLayout/components/SidenavContent/hooks/useProjectsSubscription.ts @@ -28,6 +28,7 @@ const getProjectItemFromCommon = async ( image: common.image, name: common.name, directParent: common.directParent, + rootCommonId: common.rootCommonId, }; if (initialItem) { diff --git a/src/shared/layouts/MultipleSpacesLayout/MultipleSpacesLayout.tsx b/src/shared/layouts/MultipleSpacesLayout/MultipleSpacesLayout.tsx index 33bac15654..e0df2a4af1 100644 --- a/src/shared/layouts/MultipleSpacesLayout/MultipleSpacesLayout.tsx +++ b/src/shared/layouts/MultipleSpacesLayout/MultipleSpacesLayout.tsx @@ -17,7 +17,8 @@ import { selectMultipleSpacesLayoutBackUrl, } from "@/store/states"; import { getSidenavLeft } from "../CommonSidenavLayout/utils"; -import { Header, SidenavContent } from "./components"; +import { Header, /* SidenavContent */ } from "./components"; +import { SidenavContent as SidenavContentV04 } from "../CommonSidenavLayout/components"; import styles from "./MultipleSpacesLayout.module.scss"; const MULTIPLE_SPACES_LAYOUT_SIDENAV_OPEN_STATE = "open"; @@ -108,9 +109,9 @@ const MultipleSpacesLayout: FC = (props) => { shouldCheckViewportForOpenState={false} withAnimation > -
diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/ActiveBreadcrumbsItem/ActiveBreadcrumbsItem.tsx b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/ActiveBreadcrumbsItem/ActiveBreadcrumbsItem.tsx index bc5e9104d9..d2e3235c9b 100644 --- a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/ActiveBreadcrumbsItem/ActiveBreadcrumbsItem.tsx +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/ActiveBreadcrumbsItem/ActiveBreadcrumbsItem.tsx @@ -6,12 +6,13 @@ import { ProjectsStateItem } from "@/store/states"; import { BreadcrumbsMenu } from "../BreadcrumbsMenu"; import styles from "./ActiveBreadcrumbsItem.module.scss"; -interface ActiveBreadcrumbsItemProps { +export interface ActiveBreadcrumbsItemProps { name: string; image?: string; items?: ProjectsStateItem[]; commonIdToAddProject?: string | null; withMenu?: boolean; + isLoading?: boolean; } const ActiveBreadcrumbsItem: FC = (props) => { @@ -21,6 +22,7 @@ const ActiveBreadcrumbsItem: FC = (props) => { items = [], commonIdToAddProject, withMenu = true, + isLoading = false, } = props; const itemsButtonRef = useRef(null); const contextMenuRef = useRef(null); @@ -57,6 +59,7 @@ const ActiveBreadcrumbsItem: FC = (props) => { ref={contextMenuRef} items={items} commonIdToAddProject={commonIdToAddProject} + isLoading={isLoading} /> )} diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/ActiveBreadcrumbsItem/index.ts b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/ActiveBreadcrumbsItem/index.ts index 7df7cf9a08..4159264f72 100644 --- a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/ActiveBreadcrumbsItem/index.ts +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/ActiveBreadcrumbsItem/index.ts @@ -1 +1,2 @@ export { default as ActiveBreadcrumbsItem } from "./ActiveBreadcrumbsItem"; +export type { ActiveBreadcrumbsItemProps } from "./ActiveBreadcrumbsItem"; diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsItem/BreadcrumbsItem.tsx b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsItem/BreadcrumbsItem.tsx index cf31772150..373ea8c6ac 100644 --- a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsItem/BreadcrumbsItem.tsx +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsItem/BreadcrumbsItem.tsx @@ -6,35 +6,35 @@ import { ProjectsStateItem } from "@/store/states"; import { BreadcrumbsMenu } from "../BreadcrumbsMenu"; import styles from "./BreadcrumbsItem.module.scss"; -interface BreadcrumbsItemProps { - activeItemId: string; +export interface BreadcrumbsItemProps { + activeItem: ProjectsStateItem; items: ProjectsStateItem[]; commonIdToAddProject?: string | null; onCommonCreate?: () => void; withMenu?: boolean; + isLoading?: boolean; + onClick?: () => void; } const BreadcrumbsItem: FC = (props) => { const { - activeItemId, + activeItem, items, commonIdToAddProject, onCommonCreate, withMenu = true, + isLoading = false, + onClick, } = props; const history = useHistory(); const { getCommonPagePath } = useRoutesContext(); const containerRef = useRef(null); const contextMenuRef = useRef(null); - const activeItem = items.find((item) => item.commonId === activeItemId); - - if (!activeItem) { - return null; - } const handleButtonClick = () => { if (!withMenu) { - history.push(getCommonPagePath(activeItemId)); + history.push(getCommonPagePath(activeItem.commonId)); + onClick?.(); return; } if (containerRef.current) { @@ -52,8 +52,9 @@ const BreadcrumbsItem: FC = (props) => { )} diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsItem/index.ts b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsItem/index.ts index 7382a210fb..4e733600f6 100644 --- a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsItem/index.ts +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsItem/index.ts @@ -1 +1,2 @@ export { default as BreadcrumbsItem } from "./BreadcrumbsItem"; +export type { BreadcrumbsItemProps } from "./BreadcrumbsItem"; diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsMenu/BreadcrumbsMenu.tsx b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsMenu/BreadcrumbsMenu.tsx index 96cf4f263e..686527c30b 100644 --- a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsMenu/BreadcrumbsMenu.tsx +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/BreadcrumbsMenu/BreadcrumbsMenu.tsx @@ -8,12 +8,19 @@ interface BreadcrumbsMenuProps { items: ProjectsStateItem[]; activeItemId?: string; commonIdToAddProject?: string | null; + isLoading?: boolean; onCommonCreate?: () => void; } const BreadcrumbsMenu = forwardRef( (props, ref) => { - const { items, activeItemId, commonIdToAddProject, onCommonCreate } = props; + const { + items, + activeItemId, + commonIdToAddProject, + isLoading = false, + onCommonCreate, + } = props; const menuItems = useMenuItems({ items, activeItemId, @@ -21,7 +28,7 @@ const BreadcrumbsMenu = forwardRef( onCommonCreate, }); - if (menuItems.length === 0) { + if (menuItems.length === 0 && !isLoading) { return null; } @@ -30,6 +37,7 @@ const BreadcrumbsMenu = forwardRef( ref={ref} menuItems={menuItems} listClassName={styles.contextMenuList} + isLoading={isLoading} /> ); }, diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/FeedItemBreadcrumbs.tsx b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/FeedItemBreadcrumbs.tsx index cbc5852d49..dd86ccc171 100644 --- a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/FeedItemBreadcrumbs.tsx +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/FeedItemBreadcrumbs.tsx @@ -1,11 +1,15 @@ -import React, { FC, useMemo } from "react"; -import { MultipleSpacesLayoutFeedItemBreadcrumbs } from "@/store/states"; +import React, { FC } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { + commonLayoutActions, + MultipleSpacesLayoutFeedItemBreadcrumbs, + ProjectsStateItem, + selectCommonLayoutCommonId, +} from "@/store/states"; import { useGoToCreateCommon } from "../../../../../../hooks"; -import { ActiveBreadcrumbsItem } from "../ActiveBreadcrumbsItem"; -import { BreadcrumbsItem } from "../BreadcrumbsItem"; import { LoadingBreadcrumbsItem } from "../LoadingBreadcrumbsItem"; import { Separator } from "../Separator"; -import { getBreadcrumbsData } from "./utils"; +import { ActiveFeedBreadcrumbsItem, FeedBreadcrumbsItem } from "./components"; import styles from "./FeedItemBreadcrumbs.module.scss"; interface FeedItemBreadcrumbsProps { @@ -15,40 +19,45 @@ interface FeedItemBreadcrumbsProps { const FeedItemBreadcrumbs: FC = (props) => { const { breadcrumbs, itemsWithMenus } = props; + const dispatch = useDispatch(); + const currentLayoutCommonId = useSelector(selectCommonLayoutCommonId); const goToCreateCommon = useGoToCreateCommon(); - const { data, projects, hasPermissionToAddProjectInActiveCommon } = useMemo( - () => getBreadcrumbsData(breadcrumbs.items, breadcrumbs.activeCommonId), - [breadcrumbs.items, breadcrumbs.activeCommonId], - ); + + const handleItemClick = (item: ProjectsStateItem) => { + if ( + currentLayoutCommonId && + item.rootCommonId && + item.rootCommonId !== currentLayoutCommonId + ) { + dispatch(commonLayoutActions.setCurrentCommonId(item.rootCommonId)); + dispatch(commonLayoutActions.clearProjects()); + } + }; return (
    {breadcrumbs.areItemsLoading && } {!breadcrumbs.areItemsLoading && - data.map((item, index) => ( - + breadcrumbs.items.map((item, index) => ( + {index > 0 && } - handleItemClick(item)} /> ))} {breadcrumbs.activeItem && ( <> - {(breadcrumbs.areItemsLoading || data.length > 0) && } - 0) && ( + + )} + diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/ActiveFeedBreadcrumbsItem/ActiveFeedBreadcrumbsItem.tsx b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/ActiveFeedBreadcrumbsItem/ActiveFeedBreadcrumbsItem.tsx new file mode 100644 index 0000000000..59095f3c9d --- /dev/null +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/ActiveFeedBreadcrumbsItem/ActiveFeedBreadcrumbsItem.tsx @@ -0,0 +1,61 @@ +import React, { FC, useMemo } from "react"; +import { useSelector } from "react-redux"; +import { + selectCommonLayoutCommonsState, + selectCommonLayoutProjectsState, +} from "@/store/states"; +import { + ActiveBreadcrumbsItem, + ActiveBreadcrumbsItemProps, +} from "../../../ActiveBreadcrumbsItem"; + +interface ActiveFeedBreadcrumbsItemProps + extends Pick { + activeItemId: string; +} + +const ActiveFeedBreadcrumbsItem: FC = ( + props, +) => { + const { activeItemId, ...restProps } = props; + const { commons, areCommonsFetched } = useSelector( + selectCommonLayoutCommonsState, + ); + const { projects, areProjectsFetched } = useSelector( + selectCommonLayoutProjectsState, + ); + const baseItems = useMemo( + () => + projects.filter( + (project) => project.directParent?.commonId === activeItemId, + ), + [projects, activeItemId], + ); + const areItemsLoading = !areCommonsFetched || !areProjectsFetched; + const hasPermissionToAddProject = useMemo( + () => + ( + commons.find((item) => item.commonId === activeItemId) || + projects.find((item) => item.commonId === activeItemId) + )?.hasPermissionToAddProject ?? false, + [commons, projects, activeItemId], + ); + const items = useMemo( + () => + [...baseItems].sort((prevItem) => + prevItem.commonId === activeItemId ? -1 : 1, + ), + [baseItems, activeItemId], + ); + + return ( + + ); +}; + +export default ActiveFeedBreadcrumbsItem; diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/ActiveFeedBreadcrumbsItem/index.ts b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/ActiveFeedBreadcrumbsItem/index.ts new file mode 100644 index 0000000000..32c0207ed6 --- /dev/null +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/ActiveFeedBreadcrumbsItem/index.ts @@ -0,0 +1 @@ +export { default as ActiveFeedBreadcrumbsItem } from "./ActiveFeedBreadcrumbsItem"; diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/FeedBreadcrumbsItem/FeedBreadcrumbsItem.tsx b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/FeedBreadcrumbsItem/FeedBreadcrumbsItem.tsx new file mode 100644 index 0000000000..50b81a54e4 --- /dev/null +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/FeedBreadcrumbsItem/FeedBreadcrumbsItem.tsx @@ -0,0 +1,74 @@ +import React, { FC, useMemo } from "react"; +import { useSelector } from "react-redux"; +import { + ProjectsStateItem, + selectCommonLayoutCommonsState, + selectCommonLayoutProjectsState, +} from "@/store/states"; +import { + BreadcrumbsItem, + BreadcrumbsItemProps, +} from "../../../BreadcrumbsItem"; + +type FeedBreadcrumbsItemProps = Pick< + BreadcrumbsItemProps, + "activeItem" | "onCommonCreate" | "withMenu" | "onClick" +>; + +const getItemsByParentId = ( + parentId: string, + data: ProjectsStateItem[], +): ProjectsStateItem[] => + data.filter((item) => item.directParent?.commonId === parentId); + +const FeedBreadcrumbsItem: FC = (props) => { + const { activeItem, ...restProps } = props; + const { commons, areCommonsFetched } = useSelector( + selectCommonLayoutCommonsState, + ); + const { projects, areProjectsFetched } = useSelector( + selectCommonLayoutProjectsState, + ); + const parentCommonId = activeItem.directParent?.commonId; + const baseItems = useMemo( + () => + parentCommonId ? getItemsByParentId(parentCommonId, projects) : commons, + [parentCommonId, projects, commons], + ); + const areItemsLoading = parentCommonId + ? !areProjectsFetched + : !areCommonsFetched; + const hasParentPermissionToAddProject = useMemo( + () => + (parentCommonId && + ( + commons.find((item) => item.commonId === parentCommonId) || + projects.find((item) => item.commonId === parentCommonId) + )?.hasPermissionToAddProject) ?? + false, + [commons, projects, parentCommonId], + ); + const items = useMemo( + () => + baseItems.length === 0 + ? [activeItem] + : [...baseItems].sort((prevItem) => + prevItem.commonId === activeItem.commonId ? -1 : 1, + ), + [baseItems, activeItem], + ); + + return ( + + ); +}; + +export default FeedBreadcrumbsItem; diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/FeedBreadcrumbsItem/index.ts b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/FeedBreadcrumbsItem/index.ts new file mode 100644 index 0000000000..d3fae5bea5 --- /dev/null +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/FeedBreadcrumbsItem/index.ts @@ -0,0 +1 @@ +export { default as FeedBreadcrumbsItem } from "./FeedBreadcrumbsItem"; diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/index.ts b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/index.ts new file mode 100644 index 0000000000..091fdeaee7 --- /dev/null +++ b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/components/index.ts @@ -0,0 +1,2 @@ +export * from "./ActiveFeedBreadcrumbsItem"; +export * from "./FeedBreadcrumbsItem"; diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/utils/getBreadcrumbsData.ts b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/utils/getBreadcrumbsData.ts deleted file mode 100644 index 3e74178a3f..0000000000 --- a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/utils/getBreadcrumbsData.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { ProjectsStateItem } from "@/store/states"; - -interface Return { - data: { - activeCommonId: string; - items: ProjectsStateItem[]; - commonIdToAddProject?: string | null; - }[]; - projects: ProjectsStateItem[]; - hasPermissionToAddProjectInActiveCommon?: boolean; -} - -const getSortFn = ( - activeCommonId: string, -): ((item: ProjectsStateItem) => number) => { - return (item) => (item.commonId === activeCommonId ? -1 : 1); -}; - -export const getBreadcrumbsData = ( - items: ProjectsStateItem[], - activeCommonId: string, -): Return => { - const activeCommon = items.find((item) => item.commonId === activeCommonId); - - if (!activeCommon) { - return { - data: [], - projects: [], - hasPermissionToAddProjectInActiveCommon: false, - }; - } - - const mainLevelCommons = items - .filter((item) => !item.directParent) - .sort(getSortFn(activeCommonId)); - const activeCommonProjects = items.filter( - (item) => item.directParent?.commonId === activeCommonId, - ); - - if (!activeCommon.directParent) { - return { - data: [ - { - activeCommonId, - items: mainLevelCommons, - }, - ], - projects: activeCommonProjects, - hasPermissionToAddProjectInActiveCommon: - activeCommon.hasPermissionToAddProject, - }; - } - - let parentCommon = items.find( - (item) => item.commonId === activeCommon.directParent?.commonId, - ); - - if (!parentCommon) { - return { - data: [], - projects: [], - hasPermissionToAddProjectInActiveCommon: false, - }; - } - - const data: Return["data"] = []; - let activeCommonIdInParentCommonProjects = activeCommonId; - - while (parentCommon) { - const parentCommonProjects = items - .filter((item) => item.directParent?.commonId === parentCommon?.commonId) - .sort(getSortFn(activeCommonIdInParentCommonProjects)); - - data.unshift({ - activeCommonId: activeCommonIdInParentCommonProjects, - items: parentCommonProjects, - commonIdToAddProject: parentCommon.hasPermissionToAddProject - ? parentCommon.commonId - : null, - }); - - activeCommonIdInParentCommonProjects = parentCommon.commonId; - parentCommon = items.find( - (item) => item.commonId === parentCommon?.directParent?.commonId, - ); - } - - data.unshift({ - activeCommonId: activeCommonIdInParentCommonProjects, - items: mainLevelCommons.sort( - getSortFn(activeCommonIdInParentCommonProjects), - ), - }); - - return { - data, - projects: activeCommonProjects, - hasPermissionToAddProjectInActiveCommon: - activeCommon.hasPermissionToAddProject, - }; -}; diff --git a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/utils/index.ts b/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/utils/index.ts deleted file mode 100644 index 8c7b2a20dd..0000000000 --- a/src/shared/layouts/MultipleSpacesLayout/components/Header/components/Breadcrumbs/components/FeedItemBreadcrumbs/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./getBreadcrumbsData"; diff --git a/src/shared/ui-kit/ContextMenu/ContextMenu.module.scss b/src/shared/ui-kit/ContextMenu/ContextMenu.module.scss index f58d193abe..ea1ebedf7b 100644 --- a/src/shared/ui-kit/ContextMenu/ContextMenu.module.scss +++ b/src/shared/ui-kit/ContextMenu/ContextMenu.module.scss @@ -20,3 +20,7 @@ border-radius: var(--items-br); box-shadow: 0 0.25rem 0.9375rem #{$c-sidebar-user-menu-shadow}; } + +.loader { + margin: 0 auto; +} diff --git a/src/shared/ui-kit/ContextMenu/ContextMenu.tsx b/src/shared/ui-kit/ContextMenu/ContextMenu.tsx index d8774250bb..3b89afa047 100644 --- a/src/shared/ui-kit/ContextMenu/ContextMenu.tsx +++ b/src/shared/ui-kit/ContextMenu/ContextMenu.tsx @@ -23,6 +23,7 @@ import { } from "@floating-ui/react"; import { useLockedBody } from "@/shared/hooks"; import { ContextMenuItem as Item } from "@/shared/interfaces"; +import { Loader } from "../Loader"; import { ContextMenuItem } from "./components"; import styles from "./ContextMenu.module.scss"; @@ -34,11 +35,12 @@ interface ContextMenuProps { menuItems: Item[]; onOpenChange?: (open: boolean) => void; listClassName?: string; + isLoading?: boolean; } export const ContextMenu = forwardRef( (props, forwardedRef) => { - const { menuItems, onOpenChange, listClassName } = props; + const { menuItems, onOpenChange, listClassName, isLoading = false } = props; const [activeIndex, setActiveIndex] = useState(null); const [isOpen, setIsOpen] = useState(false); const listItemsRef = useRef<(HTMLElement | null)[]>([]); @@ -156,6 +158,7 @@ export const ContextMenu = forwardRef( })} /> ))} + {isLoading && } diff --git a/src/shared/ui-kit/TextEditor/components/EmojiPicker/EmojiPicker.module.scss b/src/shared/ui-kit/TextEditor/components/EmojiPicker/EmojiPicker.module.scss index a0cf538a35..10a84e836a 100644 --- a/src/shared/ui-kit/TextEditor/components/EmojiPicker/EmojiPicker.module.scss +++ b/src/shared/ui-kit/TextEditor/components/EmojiPicker/EmojiPicker.module.scss @@ -8,6 +8,7 @@ $phone-breakpoint: 415px; bottom: 1.5625rem; right: 0.5625rem; transform: translateY(50%); + width: fit-content; } .containerRtl { diff --git a/src/store/states/common/reducer.ts b/src/store/states/common/reducer.ts index 27c63fa277..150dd9154e 100644 --- a/src/store/states/common/reducer.ts +++ b/src/store/states/common/reducer.ts @@ -43,6 +43,14 @@ const initialState: CommonState = { recentAssignedCircle: null, }; +const sortFeedItems = (data: FeedItemFollowLayoutItem[]): void => { + data.sort( + (prevItem, nextItem) => + nextItem.feedItem.updatedAt.toMillis() - + prevItem.feedItem.updatedAt.toMillis(), + ); +}; + const updateFeedItemInList = ( state: WritableDraft, payload: { @@ -76,10 +84,17 @@ const updateFeedItemInList = ( ...updatedItem, }, }; + sortFeedItems(nextData); } + const firstDocTimestamp = nextData[0]?.feedItem.updatedAt || null; + const lastDocTimestamp = + nextData[nextData.length - 1]?.feedItem.updatedAt || null; + state.feedItems = { ...state.feedItems, + firstDocTimestamp, + lastDocTimestamp, data: nextData, }; }; @@ -95,8 +110,6 @@ const addNewFeedItems = ( }[], shouldSortNewItems = false, ) => { - let firstDocTimestamp = state.feedItems.firstDocTimestamp; - const data = payload.reduceRight((acc, { commonFeedItem, statuses }) => { const nextData = [...acc]; const itemIndex = nextData.findIndex( @@ -117,7 +130,6 @@ const addNewFeedItems = ( itemId: commonFeedItem.id, feedItem: commonFeedItem, }; - firstDocTimestamp = commonFeedItem.updatedAt; if (itemIndex >= 0) { nextData[itemIndex] = finalItem; @@ -139,11 +151,16 @@ const addNewFeedItems = ( return nextData; }, state.feedItems.data || []); + sortFeedItems(data); + + const firstDocTimestamp = data[0]?.feedItem.updatedAt || null; + const lastDocTimestamp = data[data.length - 1]?.feedItem.updatedAt || null; state.feedItems = { ...state.feedItems, data, firstDocTimestamp, + lastDocTimestamp, }; }; diff --git a/src/store/states/commonLayout/saga/getCommons.ts b/src/store/states/commonLayout/saga/getCommons.ts index 46e4efd778..72ed1e171b 100644 --- a/src/store/states/commonLayout/saga/getCommons.ts +++ b/src/store/states/commonLayout/saga/getCommons.ts @@ -89,6 +89,7 @@ export function* getCommons( image: common.image, name: common.name, directParent: common.directParent, + rootCommonId: common.rootCommonId, hasMembership, hasPermissionToAddProject, notificationsAmount: 0, diff --git a/src/store/states/commonLayout/saga/getProjects.ts b/src/store/states/commonLayout/saga/getProjects.ts index 978dfe64bb..cada89f37e 100644 --- a/src/store/states/commonLayout/saga/getProjects.ts +++ b/src/store/states/commonLayout/saga/getProjects.ts @@ -54,6 +54,7 @@ export function* getProjects( image: common.image, name: common.name, directParent: common.directParent, + rootCommonId: common.rootCommonId, hasMembership, hasPermissionToAddProject, notificationsAmount: 0, diff --git a/src/store/states/inbox/reducer.ts b/src/store/states/inbox/reducer.ts index af17b96832..ef82ac6c60 100644 --- a/src/store/states/inbox/reducer.ts +++ b/src/store/states/inbox/reducer.ts @@ -7,7 +7,7 @@ import { checkIsFeedItemFollowLayoutItem, FeedLayoutItemWithFollowData, } from "@/shared/interfaces"; -import { ChatChannel, CommonFeed } from "@/shared/models"; +import { ChatChannel, CommonFeed, Timestamp } from "@/shared/models"; import * as actions from "./actions"; import { InboxItems, InboxState } from "./types"; import { getFeedLayoutItemDateForSorting } from "./utils"; @@ -30,6 +30,26 @@ const initialState: InboxState = { nextChatChannelItemId: null, }; +const sortInboxItems = (data: FeedLayoutItemWithFollowData[]): void => { + data.sort( + (prevItem, nextItem) => + getFeedLayoutItemDateForSorting(nextItem).toMillis() - + getFeedLayoutItemDateForSorting(prevItem).toMillis(), + ); +}; + +const getDocTimestamps = ( + data: FeedLayoutItemWithFollowData[], +): { + firstDocTimestamp: Timestamp | null; + lastDocTimestamp: Timestamp | null; +} => ({ + firstDocTimestamp: data[0] ? getFeedLayoutItemDateForSorting(data[0]) : null, + lastDocTimestamp: data[data.length - 1] + ? getFeedLayoutItemDateForSorting(data[data.length - 1]) + : null, +}); + const updateInboxItemInList = ( state: WritableDraft, payload: { @@ -64,10 +84,14 @@ const updateInboxItemInList = ( ...nextData[itemIndex], ...updatedItem, }; + sortInboxItems(nextData); } + const { firstDocTimestamp, lastDocTimestamp } = getDocTimestamps(nextData); state.items = { ...state.items, + firstDocTimestamp, + lastDocTimestamp, data: nextData, }; }; @@ -162,9 +186,13 @@ const updateFeedItemInInboxItem = ( feedItem: { ...newFeedItem }, }, }; + sortInboxItems(nextData); + const { firstDocTimestamp, lastDocTimestamp } = getDocTimestamps(nextData); state.items = { ...state.items, + firstDocTimestamp, + lastDocTimestamp, data: nextData, }; }; @@ -285,9 +313,13 @@ const updateChatChannelItemInInboxItem = ( lastMessage: updatedChatChannelItem.lastMessage || undefined, }, }; + sortInboxItems(nextData); + const { firstDocTimestamp, lastDocTimestamp } = getDocTimestamps(nextData); state.items = { ...state.items, + firstDocTimestamp, + lastDocTimestamp, data: nextData, }; }; @@ -421,8 +453,6 @@ export const reducer = createReducer(initialState) (chatChannelItem) => chatChannelItem.itemId === item.item.itemId, ), ); - let firstDocTimestamp = nextState.items.firstDocTimestamp; - const data = payload.reduceRight((acc, { item, statuses }) => { const nextData = [...acc]; const itemIndex = nextData.findIndex( @@ -443,7 +473,6 @@ export const reducer = createReducer(initialState) } const finalItem: FeedLayoutItemWithFollowData = { ...item }; - firstDocTimestamp = getFeedLayoutItemDateForSorting(item); if (itemIndex < 0) { return [finalItem, ...nextData]; @@ -453,11 +482,14 @@ export const reducer = createReducer(initialState) return nextData; }, nextState.items.data || []); + sortInboxItems(data); + const { firstDocTimestamp, lastDocTimestamp } = getDocTimestamps(data); nextState.items = { ...nextState.items, data, firstDocTimestamp, + lastDocTimestamp, }; }), ) diff --git a/src/store/states/multipleSpacesLayout/reducer.ts b/src/store/states/multipleSpacesLayout/reducer.ts index 347ae0914f..25fd6537ec 100644 --- a/src/store/states/multipleSpacesLayout/reducer.ts +++ b/src/store/states/multipleSpacesLayout/reducer.ts @@ -28,9 +28,12 @@ const updateProjectInBreadcrumbs = ( ); if (itemIndex > -1) { + const item = state.breadcrumbs.items[itemIndex]; + state.breadcrumbs.items[itemIndex] = { - ...state.breadcrumbs.items[itemIndex], - ...payload, + ...item, + name: payload.name ?? item.name, + image: payload.image ?? item.image, }; } }; @@ -56,20 +59,8 @@ export const reducer = createReducer( ) .handleAction(actions.addOrUpdateProjectInBreadcrumbs, (state, { payload }) => produce(state, (nextState) => { - if (nextState.breadcrumbs?.type !== InboxItemType.FeedItemFollow) { - return; - } - - const isItemFound = nextState.breadcrumbs.items.some( - (item) => item.commonId === payload.commonId, - ); - - if (isItemFound) { - updateProjectInBreadcrumbs(nextState, payload); - } else { - nextState.breadcrumbs.items = - nextState.breadcrumbs.items.concat(payload); - } + // Intentionally removed adding logic, because now we do not need any new items in the list + updateProjectInBreadcrumbs(nextState, payload); }), ) .handleAction(actions.updateProjectInBreadcrumbs, (state, { payload }) => diff --git a/src/store/states/multipleSpacesLayout/saga/configureBreadcrumbsData.ts b/src/store/states/multipleSpacesLayout/saga/configureBreadcrumbsData.ts index 45025fea08..9a189226e1 100644 --- a/src/store/states/multipleSpacesLayout/saga/configureBreadcrumbsData.ts +++ b/src/store/states/multipleSpacesLayout/saga/configureBreadcrumbsData.ts @@ -1,8 +1,43 @@ import { put, select } from "redux-saga/effects"; import { InboxItemType } from "@/shared/constants"; +import { + selectCommonLayoutCommonsState, + selectCommonLayoutProjectsState, +} from "@/store/states"; import * as actions from "../actions"; import { selectMultipleSpacesLayoutBreadcrumbs } from "../selectors"; -import { MultipleSpacesLayoutState } from "../types"; +import { MultipleSpacesLayoutState, ProjectsStateItem } from "../types"; + +const getItemsByExistingData = ( + activeCommonId: string, + existingItems: ProjectsStateItem[], +): ProjectsStateItem[] | null => { + const currentItem = existingItems.find( + (item) => item.commonId === activeCommonId, + ); + + if (!currentItem) { + return null; + } + + const items: ProjectsStateItem[] = [currentItem]; + let parentCommonId = currentItem.directParent?.commonId; + + while (parentCommonId) { + const parentItem = existingItems.find( + (item) => item.commonId === parentCommonId, + ); + + if (!parentItem) { + return null; + } + + items.unshift(parentItem); + parentCommonId = parentItem.directParent?.commonId; + } + + return items; +}; export function* configureBreadcrumbsData( action: ReturnType, @@ -41,27 +76,44 @@ export function* configureBreadcrumbsData( return; } + const { commons, areCommonsFetched } = (yield select( + selectCommonLayoutCommonsState, + )) as { commons: ProjectsStateItem[]; areCommonsFetched: boolean }; + const { projects } = (yield select(selectCommonLayoutProjectsState)) as { + projects: ProjectsStateItem[]; + }; + const items = areCommonsFetched + ? getItemsByExistingData(payload.activeCommonId, [...commons, ...projects]) + : null; + yield put( actions.setBreadcrumbsData({ - ...(currentBreadcrumbs || { - items: [], - areItemsLoading: true, - areItemsFetched: false, - }), + ...(items + ? { + items, + areItemsLoading: false, + areItemsFetched: true, + } + : currentBreadcrumbs || { + items: [], + areItemsLoading: true, + areItemsFetched: false, + }), type: InboxItemType.FeedItemFollow, activeItem: payload.activeItem ? { ...payload.activeItem } : null, activeCommonId: payload.activeCommonId, }), ); - if ( - currentBreadcrumbs?.activeCommonId !== payload.activeCommonId || - !currentBreadcrumbs.items.some( - (item) => item.commonId === payload.activeCommonId, - ) - ) { + if (!items) { yield put( actions.fetchBreadcrumbsItemsByCommonId.request(payload.activeCommonId), ); + } else { + yield put( + actions.fetchBreadcrumbsItemsByCommonId.cancel( + "Stop current breadcrumbs items fetch", + ), + ); } } diff --git a/src/store/states/multipleSpacesLayout/saga/fetchBreadcrumbsItemsByCommonId.ts b/src/store/states/multipleSpacesLayout/saga/fetchBreadcrumbsItemsByCommonId.ts index 53e6b3f16d..c7e27de108 100644 --- a/src/store/states/multipleSpacesLayout/saga/fetchBreadcrumbsItemsByCommonId.ts +++ b/src/store/states/multipleSpacesLayout/saga/fetchBreadcrumbsItemsByCommonId.ts @@ -1,74 +1,32 @@ import { call, put, select } from "redux-saga/effects"; -import { selectUser } from "@/pages/Auth/store/selectors"; -import { CommonService, GovernanceService, ProjectService } from "@/services"; +import { CommonService } from "@/services"; import { InboxItemType } from "@/shared/constants"; import { Awaited } from "@/shared/interfaces"; -import { Common, User } from "@/shared/models"; -import { compareCommonsByLastActivity } from "@/shared/utils"; -import { getPermissionsDataByAllUserCommonMemberInfo } from "../../commonLayout/saga/utils"; import * as actions from "../actions"; import { selectMultipleSpacesLayoutBreadcrumbs } from "../selectors"; import { MultipleSpacesLayoutState, ProjectsStateItem } from "../types"; const fetchProjectsInfoByActiveCommonId = async ( commonId: string, - userId?: string, -): Promise> => { +): Promise => { const activeCommon = await CommonService.getCommonById(commonId); if (!activeCommon) { return []; } - const finalCommons: Common[] = []; - let commonForSiblings: Common | null = activeCommon; - let lastParentCommon: Common | null = !commonForSiblings.directParent - ? commonForSiblings - : null; - - while (commonForSiblings?.directParent?.commonId) { - const commonIdForProjects = commonForSiblings.directParent.commonId; - const commonProjects = await CommonService.getCommonsByDirectParentIds([ - commonIdForProjects, - ]); - commonForSiblings = await CommonService.getCommonById(commonIdForProjects); - finalCommons.push(...commonProjects); - - if (!commonForSiblings?.directParent) { - lastParentCommon = commonForSiblings; - } - } - - const allUserCommonMemberInfo = userId - ? await CommonService.getAllUserCommonMemberInfo(userId) - : []; - const userCommonIds = allUserCommonMemberInfo.map((item) => item.commonId); - const [userCommons, activeCommonProjects, governanceList] = await Promise.all( - [ - CommonService.getParentCommonsByIds(userCommonIds), - CommonService.getCommonsByDirectParentIds([commonId]), - GovernanceService.getGovernanceListByCommonIds(userCommonIds), - ], - ); - const permissionsData = getPermissionsDataByAllUserCommonMemberInfo( - allUserCommonMemberInfo, - governanceList, + const commons = await CommonService.getAllParentCommonsForCommon( + activeCommon, ); - if ( - lastParentCommon && - !userCommons.some((common) => common.id === lastParentCommon?.id) - ) { - userCommons.push(lastParentCommon); - } - - finalCommons.push(...userCommons, ...activeCommonProjects); - - return ProjectService.parseDataToProjectsInfo( - finalCommons, - userCommonIds, - permissionsData, - ); + return [...commons, activeCommon].map((common) => ({ + commonId: common.id, + image: common.image, + name: common.name, + directParent: common.directParent, + rootCommonId: common.rootCommonId, + hasMembership: true, + })); }; export function* fetchBreadcrumbsItemsByCommonId( @@ -88,6 +46,14 @@ export function* fetchBreadcrumbsItemsByCommonId( return; } + const commonIndex = currentBreadcrumbs.items.findIndex( + (item) => item.commonId === commonId, + ); + + if (commonIndex > -1) { + return; + } + yield put( actions.setBreadcrumbsData({ ...currentBreadcrumbs, @@ -97,27 +63,10 @@ export function* fetchBreadcrumbsItemsByCommonId( ); try { - const user = (yield select(selectUser())) as User | null; - const projectsInfo = (yield call( + const projectsStateItems = (yield call( fetchProjectsInfoByActiveCommonId, commonId, - user?.uid, )) as Awaited>; - console.log(projectsInfo.map((c) => c)); - console.log(projectsInfo.map((c) => c.common.updatedAt)); - const projectsData: ProjectsStateItem[] = [...projectsInfo] - .sort((prevItem, nextItem) => - compareCommonsByLastActivity(prevItem.common, nextItem.common), - ) - .map(({ common, hasMembership, hasPermissionToAddProject }) => ({ - commonId: common.id, - image: common.image, - name: common.name, - directParent: common.directParent, - hasMembership, - hasPermissionToAddProject, - })); - const currentBreadcrumbs = (yield select( selectMultipleSpacesLayoutBreadcrumbs, )) as MultipleSpacesLayoutState["breadcrumbs"]; @@ -126,7 +75,7 @@ export function* fetchBreadcrumbsItemsByCommonId( yield put( actions.setBreadcrumbsData({ ...currentBreadcrumbs, - items: projectsData, + items: projectsStateItems, areItemsLoading: false, areItemsFetched: true, }), diff --git a/src/store/states/projects/saga.ts b/src/store/states/projects/saga.ts index f65febe742..19b11aa1e8 100644 --- a/src/store/states/projects/saga.ts +++ b/src/store/states/projects/saga.ts @@ -25,6 +25,7 @@ function* getProjects(action: ReturnType) { image: common.image, name: common.name, directParent: common.directParent, + rootCommonId: common.rootCommonId, hasMembership, notificationsAmount: 0, }), diff --git a/src/store/states/projects/types.ts b/src/store/states/projects/types.ts index 02497d91dd..2926b9ac13 100644 --- a/src/store/states/projects/types.ts +++ b/src/store/states/projects/types.ts @@ -5,6 +5,7 @@ export interface ProjectsStateItem { image: string; name: string; directParent: Common["directParent"]; + rootCommonId?: string; hasMembership?: boolean; hasPermissionToAddProject?: boolean; notificationsAmount?: number;