Skip to content

Commit

Permalink
Merge pull request #636 from us3r-network/F-communityMembers-shixuewen
Browse files Browse the repository at this point in the history
feat: community members
  • Loading branch information
friendlysxw authored Mar 20, 2024
2 parents f8eafec + 0cebada commit eda3b30
Show file tree
Hide file tree
Showing 10 changed files with 361 additions and 174 deletions.
70 changes: 70 additions & 0 deletions apps/u3/src/components/community/FarcasterMemberItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useState } from 'react';
import useFarcasterFollowAction from '@/hooks/social/farcaster/useFarcasterFollowAction';
import useFarcasterUserData from '@/hooks/social/farcaster/useFarcasterUserData';
import { MemberEntity } from '@/services/community/types/community';
import { SocialPlatform } from '@/services/social/types';
import MemberItem from './MemberItem';

const formatFarcasterUserData = (data) => {
const temp: {
[key: string]: { type: number; value: string }[];
} = {};
data.forEach((item) => {
if (temp[item.fid]) {
temp[item.fid].push(item);
} else {
temp[item.fid] = [item];
}
});
return temp;
};
export default function FarcasterMemberItem({
following,
data,
}: {
following: string[];
data: MemberEntity;
}) {
const { fid, data: memberData } = data;
const [followPending, setFollowPending] = useState(false);
const [unfollowPending, setUnfollowPending] = useState(false);
const [followed, setFollowed] = useState(following.includes(fid));

const { followAction, unfollowAction } = useFarcasterFollowAction();

const farcasterUserData = useFarcasterUserData({
fid,
farcasterUserData: formatFarcasterUserData(memberData),
});
const avatar = farcasterUserData.pfp;
const name = farcasterUserData.display || farcasterUserData.fid;
const { bio } = farcasterUserData;
const handle = farcasterUserData.userName;
return (
<MemberItem
data={{
handle,
address: '',
name,
avatar,
bio,
isFollowed: followed,
platforms: [SocialPlatform.Farcaster],
}}
followPending={followPending}
unfollowPending={unfollowPending}
followAction={async () => {
setFollowPending(true);
await followAction(Number(fid));
setFollowed(true);
setFollowPending(false);
}}
unfollowAction={async () => {
setUnfollowPending(true);
await unfollowAction(Number(fid));
setFollowed(false);
setUnfollowPending(false);
}}
/>
);
}
121 changes: 121 additions & 0 deletions apps/u3/src/components/community/MemberItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { ComponentPropsWithRef, useEffect, useMemo } from 'react';
import ColorButton from '../common/button/ColorButton';
import { SocialPlatform } from '@/services/social/types';
import { useXmtpClient } from '@/contexts/message/XmtpClientCtx';
import useCanMessage from '@/hooks/message/xmtp/useCanMessage';
import {
farcasterHandleToBioLinkHandle,
lensHandleToBioLinkHandle,
} from '@/utils/profile/biolink';
import { cn } from '@/lib/utils';
import NavigateToProfileLink from '../profile/info/NavigateToProfileLink';

export type MemberData = {
handle: string;
avatar: string;
name: string;
address: string;
bio: string;
platforms: SocialPlatform[];
isFollowed: boolean;
};
export type MemberItemProps = ComponentPropsWithRef<'div'> & {
data: MemberData;
followPending?: boolean;
unfollowPending?: boolean;
followAction?: () => void;
unfollowAction?: () => void;
};
export default function MemberItem({
data,
followPending,
unfollowPending,
followAction,
unfollowAction,
className,
...props
}: MemberItemProps) {
const { setCanEnableXmtp } = useXmtpClient();
useEffect(() => {
setCanEnableXmtp(true);
}, []);

const { handle, avatar, name, address, bio, platforms, isFollowed } = data;
const { canMessage } = useCanMessage(address);
const { setMessageRouteParams } = useXmtpClient();

const profileIdentity = useMemo(() => {
if (handle.endsWith('.eth')) return handle;
const firstPlatform = platforms?.[0];
switch (firstPlatform) {
case SocialPlatform.Lens:
return lensHandleToBioLinkHandle(handle);
case SocialPlatform.Farcaster:
return farcasterHandleToBioLinkHandle(handle);
default:
return '';
}
}, [handle, platforms]);

const profileUrl = useMemo(() => {
if (profileIdentity) {
return `/u/${profileIdentity}`;
}
return '';
}, [profileIdentity]);
return (
<div
className={cn(
'flex px-[10px] py-[20px] box-border items-start gap-[10px] self-stretch max-sm:py-[10px]',
className
)}
{...props}
>
<NavigateToProfileLink href={profileUrl}>
<img
src={avatar}
alt=""
className="w-[50px] h-[50px] rounded-[50%] max-sm:w-[40px] max-sm:h-[40px]"
/>
</NavigateToProfileLink>

<div className="flex-1 flex flex-col gap-[5px]">
<div>
<NavigateToProfileLink href={profileUrl}>
<span className="text-[#FFF] text-[16px] font-medium">{name}</span>
</NavigateToProfileLink>
</div>

<span className="text-[#718096] text-[12px] font-normal">{handle}</span>

<span className="text-[#FFF] text-[16px] font-normal leading-[20px] break-all">
{bio}
</span>
</div>
<ColorButton
className="bg-[#00D1A7] hover:bg-[#00D1A7] h-[30px] rounded-[20px] text-[12px] font-normal max-sm:h-[30px]"
disabled={followPending || unfollowPending}
onClick={() => {
if (isFollowed) {
unfollowAction?.();
} else {
followAction?.();
}
}}
>
{(() => {
if (followPending) {
return 'Following';
}
if (unfollowPending) {
return 'Unfollowing';
}
if (isFollowed) {
return 'Following';
}
return 'Follow';
})()}
</ColorButton>
</div>
);
}
44 changes: 12 additions & 32 deletions apps/u3/src/container/community/CommunityLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import { cn } from '@/lib/utils';
import CommunityMenu from './CommunityMenu';
import { useFarcasterCtx } from '@/contexts/social/FarcasterCtx';
import Loading from '@/components/common/loading/Loading';
import useLoadCommunityMembers from '@/hooks/community/useLoadCommunityMembers';
import useLoadCommunityTopMembers from '@/hooks/community/useLoadCommunityTopMembers';
import { CommunityInfo } from '@/services/community/types/community';
import { fetchCommunity } from '@/services/community/api/community';
import CommunityMobileHeader from './CommunityMobileHeader';
import useJoinCommunityAction from '@/hooks/community/useJoinCommunityAction';
import useBrowsingCommunity from '@/hooks/community/useBrowsingCommunity';
import useLoadCommunityMembersTotalNum from '@/hooks/community/useLoadCommunityMembersTotalNum';

export default function CommunityLayout() {
const { channelId } = useParams();
Expand Down Expand Up @@ -56,24 +55,17 @@ export default function CommunityLayout() {
};
}, [communityInfo, setBrowsingCommunity, clearBrowsingCommunity]);

// members state
const {
members,
pageInfo: membersPageInfo,
firstLoading: membersFirstLoading,
moreLoading: membersMoreLoading,
loadFirst: loadFirstMembers,
loadMore: loadMoreMembers,
} = useLoadCommunityMembers(channelId);

const {
members: topMembers,
loading: topMembersLoading,
load: loadTopMembers,
} = useLoadCommunityTopMembers(channelId);

const { joined } = useJoinCommunityAction(communityInfo);

const id = communityInfo?.id;
const { totalNum: totalMembers, loadCommunityMembersTotalNum } =
useLoadCommunityMembersTotalNum();
useEffect(() => {
if (id) {
loadCommunityMembersTotalNum({ id });
}
}, [id]);

if (communityLoading) {
<div className="w-full h-full flex justify-center items-center">
<Loading />
Expand Down Expand Up @@ -102,6 +94,7 @@ export default function CommunityLayout() {
className="min-sm:hidden"
communityInfo={communityInfo}
channelId={channel?.channel_id}
totalMembers={totalMembers}
/>
<div
className={cn(
Expand All @@ -113,20 +106,7 @@ export default function CommunityLayout() {
context={{
channelId,
communityInfo,

// TODO mentioned links

// members state
members,
membersPageInfo,
membersFirstLoading,
membersMoreLoading,
loadFirstMembers,
loadMoreMembers,

topMembers,
topMembersLoading,
loadTopMembers,
totalMembers,
}}
/>
</div>
Expand Down
5 changes: 4 additions & 1 deletion apps/u3/src/container/community/CommunityMobileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ export default function CommunityMobileHeader({
className,
communityInfo,
channelId,
totalMembers,
...props
}: ComponentPropsWithRef<'div'> & {
communityInfo: CommunityInfo;
channelId: string;
totalMembers: number;
}) {
const navigate = useNavigate();
const { mainNavs } = getCommunityNavs(channelId, communityInfo);
Expand Down Expand Up @@ -65,7 +67,8 @@ export default function CommunityMobileHeader({
value={nav.href}
className="hover:bg-[#20262F]"
>
{nav.title}
{nav.title}{' '}
{nav.href.includes('/members') && `(${totalMembers})`}
</SelectItem>
);
})}
Expand Down
Loading

0 comments on commit eda3b30

Please sign in to comment.