+
diff --git a/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx b/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx
index f8775030d4..e047d3c936 100644
--- a/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx
+++ b/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx
@@ -17,7 +17,7 @@ import {
LOADER_APPEARANCE_DELAY,
QueryParamKey,
} from "@/shared/constants";
-import { useQueryParams } from "@/shared/hooks";
+import { useForceUpdate, useQueryParams } from "@/shared/hooks";
import {
checkIsUserDiscussionMessage,
CommonFeedObjectUserUnique,
@@ -97,6 +97,13 @@ const ChatContent: ForwardRefRenderFunction<
const userId = user?.uid;
const queryParams = useQueryParams();
const messageIdParam = queryParams[QueryParamKey.Message];
+ const forceUpdate = useForceUpdate();
+
+ useEffect(() => {
+ if (messages) {
+ forceUpdate();
+ }
+ }, [messages]);
const [highlightedMessageId, setHighlightedMessageId] = useState(
() => (typeof messageIdParam === "string" && messageIdParam) || null,
@@ -120,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(() => {
@@ -234,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/services/Common.ts b/src/services/Common.ts
index 4a131c415a..7bc06fc7ea 100644
--- a/src/services/Common.ts
+++ b/src/services/Common.ts
@@ -119,6 +119,38 @@ class CommonService {
.reduce((acc, items) => [...acc, ...items], []);
};
+ public subscribeToCommonsByDirectParentId = (
+ parentCommonId: string,
+ callback: (
+ data: {
+ common: Common;
+ statuses: {
+ isAdded: boolean;
+ isRemoved: boolean;
+ };
+ }[],
+ ) => void,
+ ): UnsubscribeFunction => {
+ const query = firebase
+ .firestore()
+ .collection(Collection.Daos)
+ .where("state", "==", CommonState.ACTIVE)
+ .where("directParent.commonId", "==", parentCommonId)
+ .withConverter(converter);
+
+ return query.onSnapshot((snapshot) => {
+ callback(
+ snapshot.docChanges().map((docChange) => ({
+ common: docChange.doc.data(),
+ statuses: {
+ isAdded: docChange.type === DocChange.Added,
+ isRemoved: docChange.type === DocChange.Removed,
+ },
+ })),
+ );
+ });
+ };
+
public getUserCommonIds = async (userId: string): Promise
=>
(
await firebase
@@ -144,6 +176,39 @@ class CommonService {
}));
};
+ public subscribeToAllUserCommonMemberInfo = (
+ userId: string,
+ callback: (
+ data: {
+ commonId: string;
+ commonMember: CommonMember;
+ statuses: {
+ isAdded: boolean;
+ isRemoved: boolean;
+ };
+ }[],
+ ) => void,
+ ): UnsubscribeFunction => {
+ const query = firebase
+ .firestore()
+ .collectionGroup(SubCollections.Members)
+ .where("userId", "==", userId)
+ .withConverter(commonMemberConverter);
+
+ return query.onSnapshot((snapshot) => {
+ callback(
+ snapshot.docChanges().map((docChange) => ({
+ commonId: docChange.doc.ref.path.split("/")[1],
+ commonMember: docChange.doc.data(),
+ statuses: {
+ isAdded: docChange.type === DocChange.Added,
+ isRemoved: docChange.type === DocChange.Removed,
+ },
+ })),
+ );
+ });
+ };
+
public getCommonsWithSubCommons = async (
commonIds: string[],
): Promise => {
diff --git a/src/services/Governance.ts b/src/services/Governance.ts
index 003b8b824c..3a1784db95 100644
--- a/src/services/Governance.ts
+++ b/src/services/Governance.ts
@@ -1,4 +1,5 @@
import { governanceCollection } from "@/pages/OldCommon/store/api";
+import { DocChange } from "@/shared/constants";
import { UnsubscribeFunction } from "@/shared/interfaces";
import { Collection, Governance } from "@/shared/models";
import {
@@ -64,6 +65,37 @@ class GovernanceService {
}
});
};
+
+ public subscribeToGovernanceListByCommonIds = (
+ commonIds: string[],
+ callback: (
+ data: {
+ governance: Governance;
+ statuses: {
+ isAdded: boolean;
+ isRemoved: boolean;
+ };
+ }[],
+ ) => void,
+ ): UnsubscribeFunction => {
+ const query = firebase
+ .firestore()
+ .collection(Collection.Governance)
+ .where("commonId", "in", commonIds)
+ .withConverter(converter);
+
+ return query.onSnapshot((snapshot) => {
+ callback(
+ snapshot.docChanges().map((docChange) => ({
+ governance: docChange.doc.data(),
+ statuses: {
+ isAdded: docChange.type === DocChange.Added,
+ isRemoved: docChange.type === DocChange.Removed,
+ },
+ })),
+ );
+ });
+ };
}
export default new GovernanceService();
diff --git a/src/shared/assets/images/landing-video-poster.jpeg b/src/shared/assets/images/landing-video-poster.jpeg
deleted file mode 100644
index 72a4f41aa1..0000000000
Binary files a/src/shared/assets/images/landing-video-poster.jpeg and /dev/null differ
diff --git a/src/shared/assets/images/landing-video-poster.jpg b/src/shared/assets/images/landing-video-poster.jpg
new file mode 100644
index 0000000000..9ec203d64f
Binary files /dev/null and b/src/shared/assets/images/landing-video-poster.jpg differ
diff --git a/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss b/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss
index 9da9e2e916..065ea21b61 100644
--- a/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss
+++ b/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss
@@ -58,6 +58,8 @@
box-sizing: border-box;
position: relative;
text-align: start;
+ font-size: $small;
+ color: $secondary-blue;
&:hover {
.menuArrowButton {
@@ -85,10 +87,10 @@
}
.systemMessage {
- width: 100%;
- max-width: unset;
- min-width: unset;
- background-color: $c-pink-light;
+ width: fit-content;
+ text-align: center;
+ font-size: $xxsmall-2;
+ color: $c-gray-60;
}
.messageName {
@@ -102,8 +104,6 @@
}
.messageContent {
- font-size: $small;
- color: $secondary-blue;
word-wrap: break-word;
white-space: pre-line;
margin-right: 1rem;
diff --git a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx
index 343a2e74c9..76fe7c0841 100644
--- a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx
+++ b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx
@@ -3,10 +3,10 @@ import React, {
useCallback,
useEffect,
useState,
- useRef,
useMemo,
} from "react";
import classNames from "classnames";
+import { useLongPress } from "use-long-press";
import {
ElementDropdown,
UserAvatar,
@@ -52,10 +52,6 @@ interface ChatMessageProps {
chatType: ChatType;
highlighted?: boolean;
className?: string;
- onMessageDropdownOpen?: (
- isOpen: boolean,
- messageTopPosition?: number,
- ) => void;
user: User | null;
scrollToRepliedMessage: (messageId: string) => void;
hasPermissionToHide: boolean;
@@ -86,7 +82,6 @@ export default function ChatMessage({
chatType,
highlighted = false,
className,
- onMessageDropdownOpen,
user,
scrollToRepliedMessage,
hasPermissionToHide,
@@ -100,7 +95,6 @@ export default function ChatMessage({
onFeedItemClick,
onInternalLinkClick,
}: ChatMessageProps) {
- const messageRef = useRef(null);
const { getCommonPagePath, getCommonPageAboutTabPath } = useRoutesContext();
const [isEditMode, setEditMode] = useState(false);
const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -140,15 +134,6 @@ export default function ChatMessage({
}
};
- const handleMessageDropdownOpen =
- onMessageDropdownOpen &&
- ((isOpen: boolean) => {
- onMessageDropdownOpen(
- isOpen,
- messageRef.current?.getBoundingClientRect().top,
- );
- });
-
useEffect(() => {
(async () => {
if (!discussionMessage.text) {
@@ -216,19 +201,17 @@ export default function ChatMessage({
onUserClick,
]);
- const handleMenuToggle = (isOpen: boolean) => {
- setIsMenuOpen(isOpen);
-
- if (handleMessageDropdownOpen) {
- handleMessageDropdownOpen(isOpen);
- }
+ const handleLongPress = () => {
+ setIsMenuOpen(true);
};
- const handleMessageClick: MouseEventHandler = () => {
- if (isTabletView) {
- setIsMenuOpen(true);
- }
- };
+ const getLongPressProps = useLongPress(
+ isTabletView ? handleLongPress : null,
+ {
+ threshold: 400,
+ cancelOnMovement: true,
+ },
+ );
const handleContextMenu: MouseEventHandler = (event) => {
if (!isTabletView) {
@@ -376,7 +359,6 @@ export default function ChatMessage({
) : (
<>
{isNotCurrentUserMessage && !isSystemMessage && (
@@ -418,7 +400,11 @@ export default function ChatMessage({
- {messageText.map((text) => text)}
+ {!messageText.length ? (
+ Loading...
+ ) : (
+ messageText.map((text) => text)
+ )}
{!isSystemMessage && (
)}
- {isSystemMessage && (
-
- )}
>
)}
diff --git a/src/shared/components/Dropdown/Dropdown.tsx b/src/shared/components/Dropdown/Dropdown.tsx
index 019f1f1303..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,12 +20,15 @@ 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 {
+ labelWrapper?: string;
menuButton?: string;
value?: string;
placeholder?: string;
@@ -78,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,
@@ -152,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,
@@ -178,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(
@@ -205,7 +169,12 @@ const Dropdown: ForwardRefRenderFunction = (
onMenuToggle={handleMenuToggle}
>
{label && (
-
+
{label}
)}
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/components/Image/Image.tsx b/src/shared/components/Image/Image.tsx
index a8609da1f9..3e32f9c523 100644
--- a/src/shared/components/Image/Image.tsx
+++ b/src/shared/components/Image/Image.tsx
@@ -8,6 +8,7 @@ import React, {
ReactEventHandler,
} from "react";
import classNames from "classnames";
+import { ImageWithZoom } from "../ImageWithZoom";
import styles from "./Image.module.scss";
interface CustomImageProps extends ImgHTMLAttributes {
@@ -16,6 +17,7 @@ interface CustomImageProps extends ImgHTMLAttributes {
placeholderElement?: ReactNode;
imageOverlayClassName?: string;
imageContainerClassName?: string;
+ hasZoom?: boolean;
}
const CustomImage: FC = (props) => {
@@ -28,6 +30,7 @@ const CustomImage: FC = (props) => {
imageOverlayClassName,
imageContainerClassName,
onClick,
+ hasZoom = false,
...restProps
} = props;
const [isLoaded, setIsLoaded] = useState(false);
@@ -70,13 +73,24 @@ const CustomImage: FC = (props) => {
return hasError && (placeholderElement || placeholderElement === null) ? (
<>{placeholderElement}>
) : (
-
-
-
-
+ <>
+ {hasZoom ? (
+
+ ) : (
+
+
+
+
+ )}
+ >
);
};
diff --git a/src/shared/components/ImageWithZoom/ImageWithZoom.module.scss b/src/shared/components/ImageWithZoom/ImageWithZoom.module.scss
new file mode 100644
index 0000000000..c87c46f308
--- /dev/null
+++ b/src/shared/components/ImageWithZoom/ImageWithZoom.module.scss
@@ -0,0 +1,4 @@
+.imageWithZoomContainer {
+ width: 100%;
+ height: 100%;
+}
diff --git a/src/shared/components/ImageWithZoom/ImageWithZoom.tsx b/src/shared/components/ImageWithZoom/ImageWithZoom.tsx
new file mode 100644
index 0000000000..2c8b2b7dbc
--- /dev/null
+++ b/src/shared/components/ImageWithZoom/ImageWithZoom.tsx
@@ -0,0 +1,23 @@
+import React, { ImgHTMLAttributes } from "react";
+import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
+import styles from "./ImageWithZoom.module.scss";
+
+const ImageWthZoom = ({
+ src,
+ alt,
+ className,
+ ...restProps
+}: ImgHTMLAttributes) => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default ImageWthZoom;
diff --git a/src/shared/components/ImageWithZoom/index.ts b/src/shared/components/ImageWithZoom/index.ts
new file mode 100644
index 0000000000..a40fbbecb6
--- /dev/null
+++ b/src/shared/components/ImageWithZoom/index.ts
@@ -0,0 +1 @@
+export { default as ImageWithZoom } from "./ImageWithZoom";
\ No newline at end of file
diff --git a/src/shared/components/index.tsx b/src/shared/components/index.tsx
index 522ef5eae2..0e8ec5fdbf 100644
--- a/src/shared/components/index.tsx
+++ b/src/shared/components/index.tsx
@@ -41,3 +41,4 @@ export * from "./BackgroundNotificationModal";
export * from "./Chat";
export * from "./ReportModal";
export * from "./UserInfoPopup";
+export * from "./ImageWithZoom";
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/avatar3.icon.tsx b/src/shared/icons/avatar3.icon.tsx
new file mode 100644
index 0000000000..6cd1eb5693
--- /dev/null
+++ b/src/shared/icons/avatar3.icon.tsx
@@ -0,0 +1,44 @@
+import React, { FC } from "react";
+
+interface Avatar3IconProps {
+ className?: string;
+}
+
+const Avatar3Icon: FC = ({ className }) => {
+ const color = "currentColor";
+
+ return (
+
+ );
+};
+
+export default Avatar3Icon;
diff --git a/src/shared/icons/billing.icon.tsx b/src/shared/icons/billing.icon.tsx
new file mode 100644
index 0000000000..f2a36eae6b
--- /dev/null
+++ b/src/shared/icons/billing.icon.tsx
@@ -0,0 +1,44 @@
+import React, { FC } from "react";
+
+interface BillingIconProps {
+ className?: string;
+}
+
+const BillingIcon: FC = ({ className }) => {
+ const color = "currentColor";
+
+ return (
+
+ );
+};
+
+export default BillingIcon;
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/icons/index.ts b/src/shared/icons/index.ts
index 230f33abbb..9f9a8fb7b3 100644
--- a/src/shared/icons/index.ts
+++ b/src/shared/icons/index.ts
@@ -1,6 +1,8 @@
export * from "./socials";
export { default as AttachIcon } from "./attach.icon";
export { default as Avatar2Icon } from "./avatar2.icon";
+export { default as Avatar3Icon } from "./avatar3.icon";
+export { default as BillingIcon } from "./billing.icon";
export { default as BlocksIcon } from "./blocks.icon";
export { default as BoldMarkIcon } from "./boldMark.icon";
export { default as BoldPlusIcon } from "./boldPlus.icon";
@@ -58,6 +60,7 @@ export { default as ShareIcon } from "./share.icon";
export { default as Share2Icon } from "./share2.icon";
export { default as Share3Icon } from "./share3.icon";
export { default as SendIcon } from "./send.icon";
+export { default as SettingsIcon } from "./settings.icon";
export { default as MinusIcon } from "./minus.icon";
export { default as FileIcon } from "./file.icon";
export { default as EmojiIcon } from "./emoji.icon";
diff --git a/src/shared/icons/settings.icon.tsx b/src/shared/icons/settings.icon.tsx
new file mode 100644
index 0000000000..136299a67b
--- /dev/null
+++ b/src/shared/icons/settings.icon.tsx
@@ -0,0 +1,37 @@
+import React, { FC } from "react";
+
+interface SettingsIconProps {
+ className?: string;
+}
+
+const SettingsIcon: FC = ({ className }) => {
+ const color = "currentColor";
+
+ return (
+
+ );
+};
+
+export default SettingsIcon;
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/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/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/MenuItems.module.scss b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/MenuItems.module.scss
index c37fa11e7d..dbeec0bb42 100644
--- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/MenuItems.module.scss
+++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/MenuItems.module.scss
@@ -22,7 +22,3 @@
.itemsWrapperPlacementBottom {
top: 100%;
}
-
-.logoutItem {
- color: $c-error-300;
-}
diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/MenuItems.tsx b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/MenuItems.tsx
index 383dfece6f..78b6884971 100644
--- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/MenuItems.tsx
+++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/MenuItems.tsx
@@ -4,6 +4,12 @@ import classNames from "classnames";
import { Menu } from "@headlessui/react";
import { logOut } from "@/pages/Auth/store/actions";
import { useRoutesContext } from "@/shared/contexts";
+import {
+ Avatar3Icon,
+ BillingIcon,
+ LogoutIcon,
+ SettingsIcon,
+} from "@/shared/icons";
import { MenuItem } from "./components";
import { Item, ItemType } from "./types";
import styles from "./MenuItems.module.scss";
@@ -31,23 +37,26 @@ const MenuItems: FC = (props) => {
{
key: "my-profile",
text: "My profile",
+ icon: ,
to: getProfilePagePath(),
},
{
key: "settings",
text: "Settings",
+ icon: ,
to: getSettingsPagePath(),
},
{
key: "billing",
text: "Billing",
+ icon: ,
to: getBillingPagePath(),
},
{
key: "log-out",
- className: styles.logoutItem,
type: ItemType.Button,
text: "Log out",
+ icon: ,
onClick: () => {
dispatch(logOut());
},
diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/components/MenuItem/MenuItem.module.scss b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/components/MenuItem/MenuItem.module.scss
index 664877803e..17721a6e55 100644
--- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/components/MenuItem/MenuItem.module.scss
+++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/components/MenuItem/MenuItem.module.scss
@@ -5,13 +5,16 @@
--item-border: 0.0625rem solid #{$c-neutrals-100};
padding: 1.125rem 1.5rem;
+ display: flex;
+ align-items: center;
text-decoration: none;
color: inherit;
background-color: var(--item-bg-color);
border: var(--item-border);
border-bottom: 0;
font-family: PoppinsSans, sans-serif;
- font-size: $small;
+ font-size: 1rem;
+ font-weight: 500;
text-align: left;
cursor: pointer;
box-sizing: border-box;
@@ -30,3 +33,10 @@
.itemActive {
--item-bg-color: var(--hover-fill);
}
+
+.icon {
+ width: 1.5rem;
+ height: 1.5rem;
+ margin-right: 1rem;
+ color: inherit;
+}
diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/components/MenuItem/MenuItem.tsx b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/components/MenuItem/MenuItem.tsx
index d569f8e4bf..16cadcc424 100644
--- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/components/MenuItem/MenuItem.tsx
+++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/components/MenuItem/MenuItem.tsx
@@ -1,7 +1,10 @@
import React, {
+ cloneElement,
forwardRef,
ForwardRefRenderFunction,
+ isValidElement,
MouseEventHandler,
+ ReactNode,
RefObject,
} from "react";
import { NavLink } from "react-router-dom";
@@ -20,10 +23,24 @@ const MenuItem: ForwardRefRenderFunction = (
ref,
) => {
const { item, active, ...restProps } = props;
- const content = item.text;
+ let iconEl: ReactNode | null = null;
+
+ if (isValidElement(item.icon)) {
+ iconEl = cloneElement(item.icon, {
+ ...item.icon.props,
+ className: classNames(styles.icon, item.icon.props.className),
+ });
+ }
+
const className = classNames(styles.item, item.className, {
[styles.itemActive]: active,
});
+ const content = (
+ <>
+ {iconEl}
+ {item.text}
+ >
+ );
switch (item.type) {
case ItemType.Button:
diff --git a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/types.ts b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/types.ts
index 69a3858abc..ed180e01bb 100644
--- a/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/types.ts
+++ b/src/shared/layouts/SidenavLayout/components/SidenavContent/components/UserInfo/components/MenuItems/types.ts
@@ -10,6 +10,7 @@ interface GeneralItem {
key: string;
className?: string;
text: ReactNode;
+ icon?: ReactNode;
}
interface LinkItem extends GeneralItem {
diff --git a/src/shared/models/Common.tsx b/src/shared/models/Common.tsx
index c03ad4ca19..31989caa75 100644
--- a/src/shared/models/Common.tsx
+++ b/src/shared/models/Common.tsx
@@ -4,6 +4,7 @@ import { Discussion } from "./Discussion";
import { DiscussionMessage } from "./DiscussionMessage";
import { PaymentAmount } from "./Payment";
import { Proposal } from "./Proposals";
+import { Timestamp } from "./Timestamp";
import { User } from "./User";
import {
AllowedActions,
@@ -119,6 +120,8 @@ export interface Common extends BaseEntity {
hasPublicItems: boolean;
rootCommonId?: string;
+
+ lastActivity?: Timestamp;
}
export interface Project extends Common {
diff --git a/src/shared/models/User.tsx b/src/shared/models/User.tsx
index 060933f486..fa6afdad06 100644
--- a/src/shared/models/User.tsx
+++ b/src/shared/models/User.tsx
@@ -1,4 +1,5 @@
-import { Proposal } from ".";
+import { BaseEntity } from "./BaseEntity";
+import { Proposal } from "./Proposals";
export enum UserRole {
Trustee = "trustee",
@@ -17,7 +18,7 @@ export enum UserEmailNotificationPreference {
AllInbox = "allInbox",
}
-export interface User {
+export interface User extends Omit {
displayName?: string;
country: string;
firstName: string;
@@ -27,8 +28,6 @@ export interface User {
photo?: string;
photoURL?: string;
intro?: string;
- createdAt?: Date;
- updatedAt?: Date;
proposals?: Proposal[];
uid: string;
roles?: UserRole[];
diff --git a/src/shared/ui-kit/Button/Button.module.scss b/src/shared/ui-kit/Button/Button.module.scss
index e77542eb27..cfc8e566a7 100644
--- a/src/shared/ui-kit/Button/Button.module.scss
+++ b/src/shared/ui-kit/Button/Button.module.scss
@@ -161,6 +161,25 @@
}
}
+.buttonLightPinkVariant {
+ --btn-color: #{$c-pink-mention};
+ --btn-bg-color: #{$c-pink-active-feed-cards-light};
+ --btn-border-color: none;
+ --btn-border: 0;
+
+ &:hover {
+ --btn-bg-color: #{$c-pink-hover-feed-cards};
+ }
+
+ &:active {
+ --btn-bg-color: #{$c-pink-active-btn};
+ }
+
+ &.buttonDisabled {
+ --btn-bg-color: #{$c-gray-10};
+ }
+}
+
.buttonOutlineBlueVariant {
--btn-color: #{$c-primary-400};
--btn-bg-color: #{$c-primary-100};
diff --git a/src/shared/ui-kit/Button/Button.tsx b/src/shared/ui-kit/Button/Button.tsx
index 554a4a0d61..846e7132c7 100644
--- a/src/shared/ui-kit/Button/Button.tsx
+++ b/src/shared/ui-kit/Button/Button.tsx
@@ -17,6 +17,7 @@ export enum ButtonVariant {
PrimaryPurple = "primary-purple",
PrimaryPink = "primary-pink",
LightPurple = "light-purple",
+ LightPink = "light-pink",
OutlineBlue = "outline-blue",
OutlinePink = "outline-pink",
OutlineDarkPink = "outline-dark-pink",
@@ -60,6 +61,7 @@ const Button: ForwardRefRenderFunction = (
variant === ButtonVariant.PrimaryPurple,
[styles.buttonPrimaryPinkVariant]: variant === ButtonVariant.PrimaryPink,
[styles.buttonLightPurpleVariant]: variant === ButtonVariant.LightPurple,
+ [styles.buttonLightPinkVariant]: variant === ButtonVariant.LightPink,
[styles.buttonOutlineBlueVariant]: variant === ButtonVariant.OutlineBlue,
[styles.buttonOutlinePinkVariant]: variant === ButtonVariant.OutlinePink,
[styles.buttonOutlineDarkPinkVariant]:
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/ImageGallery/components/ImageGalleryMobileModal/ImageGalleryMobileModal.tsx b/src/shared/ui-kit/ImageGallery/components/ImageGalleryMobileModal/ImageGalleryMobileModal.tsx
index 741688fd47..28d97d352e 100644
--- a/src/shared/ui-kit/ImageGallery/components/ImageGalleryMobileModal/ImageGalleryMobileModal.tsx
+++ b/src/shared/ui-kit/ImageGallery/components/ImageGalleryMobileModal/ImageGalleryMobileModal.tsx
@@ -73,6 +73,7 @@ const ImageGalleryMobileModal: FC = (props) => {
)}
{images.map((imageURL, index) => (
= (props) => {
loop={true}
pagination
initialSlide={initialSlide}
+ allowTouchMove={false}
>
{videoSrc && (
@@ -61,6 +62,7 @@ const ImageGalleryModal: FC = (props) => {
{images.map((imageURL, index) => (
{
+ if (!nextCommon.lastActivity) {
+ return -1;
+ }
+ if (!prevCommon.lastActivity) {
+ return 1;
+ }
+
+ return nextCommon.lastActivity.seconds - prevCommon.lastActivity.seconds;
+};
diff --git a/src/shared/utils/generateStaticShareLink.ts b/src/shared/utils/generateStaticShareLink.ts
index 51a2df05fc..c68737288f 100644
--- a/src/shared/utils/generateStaticShareLink.ts
+++ b/src/shared/utils/generateStaticShareLink.ts
@@ -1,5 +1,6 @@
-import { Environment, REACT_APP_ENV } from "../constants";
+import { Environment, REACT_APP_ENV, ROUTE_PATHS } from "../constants";
import { Common, Discussion, DiscussionMessage, Proposal } from "../models";
+import { matchRoute } from "./matchRoute";
const staticLinkPrefix = () => {
if (window.location.hostname === "localhost") {
@@ -15,6 +16,24 @@ const staticLinkPrefix = () => {
}
};
+const getStaticLinkBasePath = (): string => {
+ const pathname: string = window.location.pathname;
+
+ if (matchRoute(pathname, ROUTE_PATHS.COMMON)) {
+ return "commons";
+ }
+
+ if (matchRoute(pathname, ROUTE_PATHS.V04_COMMON)) {
+ return "commons-v04";
+ }
+
+ if (matchRoute(pathname, ROUTE_PATHS.V03_COMMON)) {
+ return "commons-v03";
+ }
+
+ return "commons";
+};
+
export const enum StaticLinkType {
DiscussionMessage,
ProposalComment,
@@ -28,22 +47,24 @@ export const generateStaticShareLink = (
elem: Common | Proposal | Discussion | DiscussionMessage,
feedItemId?: string,
): string => {
+ const basePath: string = getStaticLinkBasePath();
+
if (!feedItemId && linkType === StaticLinkType.Common) {
elem = elem as Common;
- return `${staticLinkPrefix()}/commons/${elem.id}`;
+ return `${staticLinkPrefix()}/${basePath}/${elem.id}`;
}
switch (linkType) {
case StaticLinkType.Proposal:
case StaticLinkType.Discussion:
elem = elem as Discussion;
- return `${staticLinkPrefix()}/commons/${
+ return `${staticLinkPrefix()}/${basePath}/${
elem.commonId
}?item=${feedItemId}`;
case StaticLinkType.DiscussionMessage:
case StaticLinkType.ProposalComment:
elem = elem as DiscussionMessage;
- return `${staticLinkPrefix()}/commons/${
+ return `${staticLinkPrefix()}/${basePath}/${
elem.commonId
}?item=${feedItemId}&message=${elem.id}`;
default:
diff --git a/src/shared/utils/index.tsx b/src/shared/utils/index.tsx
index dbf698daac..74e159b1d9 100755
--- a/src/shared/utils/index.tsx
+++ b/src/shared/utils/index.tsx
@@ -15,6 +15,7 @@ export * from "./parseLinksForSubmission";
export * from "./proposals";
export * from "./queryParams";
export { default as request } from "./request";
+export * from "./compareCommonsByLastActivity";
export * from "./convertDatesToFirestoreTimestamps";
export * from "./convertLinkToUploadFile";
export * from "./timeAgo";
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 08d44bd956..72ed1e171b 100644
--- a/src/store/states/commonLayout/saga/getCommons.ts
+++ b/src/store/states/commonLayout/saga/getCommons.ts
@@ -3,7 +3,7 @@ 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 { compareCommonsByLastActivity, isError } from "@/shared/utils";
import { ProjectsStateItem } from "../../projects";
import * as actions from "../actions";
import { getPermissionsDataByAllUserCommonMemberInfo } from "./utils";
@@ -80,17 +80,20 @@ export function* getCommons(
commonId,
userId,
)) as Awaited>;
- const projectsData: ProjectsStateItem[] = data.map(
- ({ common, hasMembership, hasPermissionToAddProject }) => ({
+ const projectsData: ProjectsStateItem[] = [...data]
+ .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,
- }),
- );
+ }));
yield put(
actions.getCommons.success({
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 b505658868..c7e27de108 100644
--- a/src/store/states/multipleSpacesLayout/saga/fetchBreadcrumbsItemsByCommonId.ts
+++ b/src/store/states/multipleSpacesLayout/saga/fetchBreadcrumbsItemsByCommonId.ts
@@ -1,73 +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 { 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(
@@ -87,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,
@@ -96,23 +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>;
- const projectsData: ProjectsStateItem[] = projectsInfo.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"];
@@ -121,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;
diff --git a/src/styles/typography.scss b/src/styles/typography.scss
index f9ea03f775..74a1321b9a 100644
--- a/src/styles/typography.scss
+++ b/src/styles/typography.scss
@@ -3,7 +3,7 @@
@mixin h5 {
font-family: PoppinsSans, sans-serif;
font-weight: normal;
- font-size: $moderate-small-2;
+ font-size: 1.125rem;
line-height: 1.25rem;
color: $c-gray-90;
text-align: center;
@@ -12,7 +12,7 @@
@mixin h6 {
font-family: PoppinsSans, sans-serif;
font-weight: 500;
- font-size: $moderate-xsmall;
+ font-size: 1rem;
line-height: 1.5rem;
color: $c-gray-100;
text-align: center;
@@ -21,7 +21,7 @@
@mixin body-sm-regular {
font-family: PoppinsSans, sans-serif;
font-weight: normal;
- font-size: $mobile-title;
+ font-size: 0.875rem;
line-height: 1.25rem;
color: $c-gray-50;
text-align: center;
diff --git a/yarn.lock b/yarn.lock
index 11e925ebf6..e4e8ed095d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -17005,6 +17005,11 @@ react-virtualized@^9.22.3:
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4"
+react-zoom-pan-pinch@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.2.0.tgz#6ce7d014a8dc4aa62ce83ca57f85e76cf2e934b8"
+ integrity sha512-7MS0wYWoXjr6PrmpgHOVpVyNQr9gj7LEr4xIvq6lBy62nuNwjdI1r+XxahQ0SDHhWrLuSF11e2PTL/YLengYyg==
+
react@^17.0.1:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"