Skip to content

Commit

Permalink
enable follow/unfollow for common
Browse files Browse the repository at this point in the history
* add follow state synchronisation between common and fed items
* add button with multiple actions to the feed layout header
  • Loading branch information
budnik9 committed Sep 23, 2023
1 parent a99f9bb commit 9f3248e
Show file tree
Hide file tree
Showing 34 changed files with 448 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ const DiscussionFeedCard = forwardRef<FeedItemRef, DiscussionFeedCardProps>(
fetchFeedItemUserMetadata,
} = useFeedItemUserMetadata();
const { data: common } = useCommon(isHome ? commonId : "");
const feedItemFollow = useFeedItemFollow(item.id, commonId);
const feedItemFollow = useFeedItemFollow(
{ feedItemId: item.id, commonId },
{ withSubscription: true },
);
const menuItems = useMenuItems(
{
commonId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,10 @@ const ProposalFeedCard = forwardRef<FeedItemRef, ProposalFeedCardProps>(
onOpen: onShareModalOpen,
onClose: onShareModalClose,
} = useModal(false);
const feedItemFollow = useFeedItemFollow(item.id, commonId);
const feedItemFollow = useFeedItemFollow(
{ feedItemId: item.id, commonId },
{ withSubscription: true },
);
const menuItems = useMenuItems(
{
commonId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ interface FollowFeedItemButtonProps {

const FollowFeedItemButton: FC<FollowFeedItemButtonProps> = (props) => {
const { feedItemId, commonId } = props;
const { isDisabled, isFollowing, onFollowToggle } = useFeedItemFollow(
const { isDisabled, isFollowing, onFollowToggle } = useFeedItemFollow({
feedItemId,
commonId,
);
});

return (
<ButtonIcon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@
overflow: hidden;
}

.commonMainInfoWrapper {
display: flex;
align-items: center;
column-gap: 8px;
}

.commonName {
margin: 0;
font-family: PoppinsSans, sans-serif;
Expand Down
28 changes: 17 additions & 11 deletions src/pages/commonFeed/components/HeaderContent/HeaderContent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { FC } from "react";
import { NavLink } from "react-router-dom";
import classNames from "classnames";
import { InviteFriendsButton } from "@/pages/common/components";
import { useRoutesContext } from "@/shared/contexts";
import { useCommonFollow } from "@/shared/hooks/useCases";
import { useIsTabletView } from "@/shared/hooks/viewport";
import { RightArrowThinIcon } from "@/shared/icons";
import { RightArrowThinIcon, StarIcon } from "@/shared/icons";
import {
CirclesPermissions,
Common,
Expand All @@ -13,7 +13,7 @@ import {
} from "@/shared/models";
import { CommonAvatar, TopNavigationOpenSidenavButton } from "@/shared/ui-kit";
import { checkIsProject, getPluralEnding } from "@/shared/utils";
import { NewStreamButton, ShareButton } from "./components";
import { ActionsButton, NewStreamButton } from "./components";
import styles from "./HeaderContent.module.scss";

interface HeaderContentProps {
Expand All @@ -29,7 +29,11 @@ const HeaderContent: FC<HeaderContentProps> = (props) => {
props;
const { getCommonPageAboutTabPath } = useRoutesContext();
const isMobileVersion = useIsTabletView();
const commonFollow = useCommonFollow(common.id, commonMember);
const isProject = checkIsProject(common);
const showFollowIcon = commonFollow.isFollowInProgress
? !commonMember?.isFollowing
: commonMember?.isFollowing;

return (
<div className={classNames(styles.container, className)}>
Expand All @@ -52,7 +56,10 @@ const HeaderContent: FC<HeaderContentProps> = (props) => {
/>

<div className={styles.commonInfoWrapper}>
<h1 className={styles.commonName}>{common.name}</h1>
<div className={styles.commonMainInfoWrapper}>
<h1 className={styles.commonName}>{common.name}</h1>
{showFollowIcon && <StarIcon stroke="currentColor" />}
</div>
<p className={styles.commonMembersAmount}>
{commonMembersAmount} member{getPluralEnding(commonMembersAmount)}
</p>
Expand All @@ -66,13 +73,12 @@ const HeaderContent: FC<HeaderContentProps> = (props) => {
governance={governance}
isMobileVersion={isMobileVersion}
/>
{!isMobileVersion && (
<InviteFriendsButton
isMobileVersion={isMobileVersion}
common={common}
TriggerComponent={ShareButton}
/>
)}
<ActionsButton
common={common}
commonMember={commonMember}
commonFollow={commonFollow}
isMobileVersion={isMobileVersion}
/>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { FC } from "react";
import { ShareModal } from "@/shared/components";
import { useModal } from "@/shared/hooks";
import { CommonFollowState } from "@/shared/hooks/useCases/useCommonFollow";
import { CirclesPermissions, Common, CommonMember } from "@/shared/models";
import { DesktopMenu, MenuButton } from "@/shared/ui-kit";
import { StaticLinkType, generateStaticShareLink } from "@/shared/utils";
import { useMenuItems } from "./hooks";

interface ActionsButtonProps {
common: Common;
commonMember: (CommonMember & CirclesPermissions) | null;
commonFollow: CommonFollowState;
isMobileVersion: boolean;
}

const ActionsButton: FC<ActionsButtonProps> = (props) => {
const { common, commonMember, commonFollow, isMobileVersion } = props;
const {
isShowing: isShareModalOpen,
onOpen: onShareModalOpen,
onClose: onShareModalClose,
} = useModal(false);
const items = useMenuItems(
{
common,
commonMember,
isMobileVersion,
isFollowInProgress: commonFollow.isFollowInProgress,
},
{
share: onShareModalOpen,
onFollowToggle: commonFollow.onFollowToggle,
},
);
const shareLink = generateStaticShareLink(StaticLinkType.Common, common);

return (
<>
{items.length > 0 && (
<DesktopMenu triggerEl={<MenuButton />} items={items} />
)}
<ShareModal
sourceUrl={shareLink}
isShowing={isShareModalOpen}
onClose={onShareModalClose}
/>
</>
);
};

export default ActionsButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./useMenuItems";
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";
import { CommonFollowState } from "@/shared/hooks/useCases";
import { FollowIcon, Share3Icon, UnfollowIcon } from "@/shared/icons";
import { MenuItem as Item } from "@/shared/interfaces";
import { CommonFeedMenuItem } from "../../../../../constants";
import {
Options as GetAllowedItemsOptions,
getAllowedItems,
} from "../../../../../utils";

interface Actions {
share: () => void;
onFollowToggle: CommonFollowState["onFollowToggle"];
}

export const useMenuItems = (
options: GetAllowedItemsOptions,
actions: Actions,
): Item[] => {
const { common } = options;
const { share, onFollowToggle } = actions;

const items: Item[] = [
{
id: CommonFeedMenuItem.Share,
text: "Share",
onClick: share,
icon: <Share3Icon />,
},
{
id: CommonFeedMenuItem.Follow,
text: `Follow ${common.name}`,
onClick: () => onFollowToggle(),
icon: <FollowIcon />,
},
{
id: CommonFeedMenuItem.Mute,
text: `Unfollow ${common.name}`,
onClick: () => onFollowToggle(),
icon: <UnfollowIcon />,
},
];

return getAllowedItems(items, options);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ActionsButton } from "./ActionsButton";
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./NewStreamButton";
export * from "./ShareButton";
export * from "./ActionsButton";
5 changes: 5 additions & 0 deletions src/pages/commonFeed/constants/commonFeedMenuItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum CommonFeedMenuItem {
Share = "share",
Follow = "follow",
Mute = "mute",
}
3 changes: 2 additions & 1 deletion src/pages/commonFeed/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const MIN_CHAT_WIDTH = 384;
export * from "./commonFeedMenuItem";
export * from "./minChatWidth";
1 change: 1 addition & 0 deletions src/pages/commonFeed/constants/minChatWidth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MIN_CHAT_WIDTH = 384;
24 changes: 24 additions & 0 deletions src/pages/commonFeed/utils/getAllowedItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MenuItem as Item } from "@/shared/interfaces";
import { CirclesPermissions, Common, CommonMember } from "@/shared/models";
import { CommonFeedMenuItem } from "../constants";

export interface Options {
common: Common;
commonMember: (CommonMember & CirclesPermissions) | null;
isFollowInProgress: boolean;
isMobileVersion: boolean;
}

const MENU_ITEM_TO_CHECK_FUNCTION_MAP: Record<
CommonFeedMenuItem,
(options: Options) => boolean
> = {
[CommonFeedMenuItem.Share]: ({ isMobileVersion }) => !isMobileVersion,
[CommonFeedMenuItem.Follow]: ({ commonMember, isFollowInProgress }) =>
!isFollowInProgress && Boolean(commonMember && !commonMember.isFollowing),
[CommonFeedMenuItem.Mute]: ({ commonMember, isFollowInProgress }) =>
!isFollowInProgress && Boolean(commonMember?.isFollowing),
};

export const getAllowedItems = (items: Item[], options: Options): Item[] =>
items.filter(({ id }) => MENU_ITEM_TO_CHECK_FUNCTION_MAP[id](options));
1 change: 1 addition & 0 deletions src/pages/commonFeed/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./generateSplitViewMaxSizeGetter";
export * from "./getLastMessage";
export * from "./getAllowedItems";
13 changes: 11 additions & 2 deletions src/services/Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { commonMembersSubCollection } from "@/pages/OldCommon/store/api";
import { store } from "@/shared/appConfig";
import {
ApiEndpoint,
DocChange,
GovernanceActions,
ProposalsTypes,
} from "@/shared/constants";
Expand Down Expand Up @@ -337,8 +338,8 @@ class CommonService {

if (docChange) {
callback(docChange.doc.data(), {
isAdded: docChange.type === "added",
isRemoved: docChange.type === "removed",
isAdded: docChange.type === DocChange.Added,
isRemoved: docChange.type === DocChange.Removed,
});
}
});
Expand Down Expand Up @@ -381,6 +382,14 @@ class CommonService {
args: { circleId, commonId, userId },
});
};

public followCommon = async (commonId: string): Promise<void> => {
await Api.post(ApiEndpoint.FollowCommon, { commonId });
};

public muteCommon = async (commonId: string): Promise<void> => {
await Api.post(ApiEndpoint.MuteCommon, { commonId });
};
}

export default new CommonService();
32 changes: 32 additions & 0 deletions src/services/FeedItemFollows.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApiEndpoint } from "@/shared/constants";
import { DocChange } from "@/shared/constants/docChange";
import { UnsubscribeFunction } from "@/shared/interfaces";
import { FollowFeedItemPayload } from "@/shared/interfaces/api";
import {
Expand Down Expand Up @@ -36,6 +37,37 @@ class FeedItemFollowsService {
return snapshot.docs[0]?.data() || null;
};

public subscribeToUserFeedItemFollowData = (
userId: string,
feedItemId: string,
callback: (
userFeedItemFollowData: FeedItemFollow | null,
statuses: {
isAdded: boolean;
isRemoved: boolean;
isModified: boolean;
},
) => void,
): UnsubscribeFunction => {
const query = this.getFeedItemFollowsSubCollection(userId).where(
"feedItemId",
"==",
feedItemId,
);

return query.onSnapshot((snapshot) => {
const docChange = snapshot.docChanges()[0];

if (docChange) {
callback(docChange.doc.data(), {
isAdded: docChange.type === DocChange.Added,
isRemoved: docChange.type === DocChange.Removed,
isModified: docChange.type === DocChange.Modified,
});
}
});
};

public getUserFeedItemFollowDataWithMetadata = async (
userId: string,
feedItemId: string,
Expand Down
5 changes: 5 additions & 0 deletions src/shared/constants/docChange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum DocChange {
Added = "added",
Removed = "removed",
Modified = "modified",
}
2 changes: 2 additions & 0 deletions src/shared/constants/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const ApiEndpoint = {
CreateReport: "/moderation/report",
HideContent: "/moderation/hide",
ShowContent: "/moderation/show",
FollowCommon: "/commons/follow",
MuteCommon: "/commons/mute",
LeaveCommon: "/commons/leave",
CreateSubscription: "/commons/immediate-contribution",
UpdateSubscription: "/subscriptions/update",
Expand Down
1 change: 1 addition & 0 deletions src/shared/constants/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ export * from "./viewportBreakpoint";
export * from "./currencyTypes";
export * from "./systemDiscussionMessage";
export * from "./theme";
export * from "./docChange";
1 change: 1 addition & 0 deletions src/shared/hooks/useCases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ export { useDiscussionMessagesById } from "./useDiscussionMessagesById";
export { useUserPendingJoin } from "./useUserPendingJoin";
export { useCommonMemberWithUserInfo } from "./useCommonMemberWithUserInfo";
export { useEligibleVoters } from "./useEligibleVoters";
export * from "./useCommonFollow";
Loading

0 comments on commit 9f3248e

Please sign in to comment.