diff --git a/apps/u3/src/components/community/FarcasterMemberItem.tsx b/apps/u3/src/components/community/FarcasterMemberItem.tsx new file mode 100644 index 00000000..2f360c01 --- /dev/null +++ b/apps/u3/src/components/community/FarcasterMemberItem.tsx @@ -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 ( + { + setFollowPending(true); + await followAction(Number(fid)); + setFollowed(true); + setFollowPending(false); + }} + unfollowAction={async () => { + setUnfollowPending(true); + await unfollowAction(Number(fid)); + setFollowed(false); + setUnfollowPending(false); + }} + /> + ); +} diff --git a/apps/u3/src/components/community/MemberItem.tsx b/apps/u3/src/components/community/MemberItem.tsx new file mode 100644 index 00000000..56028da6 --- /dev/null +++ b/apps/u3/src/components/community/MemberItem.tsx @@ -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 ( +
+ + + + +
+
+ + {name} + +
+ + {handle} + + + {bio} + +
+ { + if (isFollowed) { + unfollowAction?.(); + } else { + followAction?.(); + } + }} + > + {(() => { + if (followPending) { + return 'Following'; + } + if (unfollowPending) { + return 'Unfollowing'; + } + if (isFollowed) { + return 'Following'; + } + return 'Follow'; + })()} + +
+ ); +} diff --git a/apps/u3/src/components/explore/posts/ImgPostCard.tsx b/apps/u3/src/components/explore/posts/ImgPostCard.tsx index 7849ef4d..ba94b084 100644 --- a/apps/u3/src/components/explore/posts/ImgPostCard.tsx +++ b/apps/u3/src/components/explore/posts/ImgPostCard.tsx @@ -83,10 +83,11 @@ function FirstCard({ data, ...wrapperProps }: Props) { - {!isMobile && {recReason}} +
+ {platformIconUrl && } + {recReason} +
- - {platformIconUrl && } @@ -139,9 +140,11 @@ function RightCard({ idx, data, ...wrapperProps }: Props) { {authorDisplayName} {authorHandle && `@${authorHandle}`} - {!isMobile && {recReason}} +
+ {platformIconUrl && } + {recReason} +
- {platformIconUrl && } @@ -194,9 +197,11 @@ function BottomCard({ idx, data, ...wrapperProps }: Props) { {authorDisplayName} {authorHandle && `@${authorHandle}`} - {!isMobile && {recReason}} +
+ {platformIconUrl && } + {recReason} +
- {platformIconUrl && } @@ -223,7 +228,7 @@ function CardBody({ className, ...props }: ComponentPropsWithRef<'div'>) {
) { ) { return ( - {!isMobile && {recReason}} +
+ {platformIconUrl && } + {recReason} +
- - {platformIconUrl && } @@ -234,15 +237,15 @@ const RecReason = styled.div` -webkit-background-clip: text; -webkit-text-fill-color: transparent; `; -const PlatformIcon = styled.img` - width: 30px; - height: 30px; - border-radius: 50%; - object-fit: cover; - flex-shrink: 0; - ${isMobile && - ` - width: 14px; - height: 14px; - `} -`; +function PlatformIcon({ className, ...props }: ComponentPropsWithRef<'img'>) { + return ( + + ); +} diff --git a/apps/u3/src/components/layout/LoginButtonV2.tsx b/apps/u3/src/components/layout/LoginButtonV2.tsx index 26c3271a..ab837add 100644 --- a/apps/u3/src/components/layout/LoginButtonV2.tsx +++ b/apps/u3/src/components/layout/LoginButtonV2.tsx @@ -32,6 +32,7 @@ import { LensAccount, } from '../profile/info/PlatformAccounts'; import LoginIcon from './nav-icons/LoginIcon'; +import useUnreadNotificationsCount from '@/hooks/social/useUnreadNotificationsCount'; const CONTACT_LINKS = [ { @@ -66,7 +67,7 @@ export default function LoginButtonV2() { const [openMenu, setOpenMenu] = useState(false); const [openLogoutConfirm, setOpenLogoutConfirm] = useState(false); const navigate = useNavigate(); - + const { unreadCount, clearUnread } = useUnreadNotificationsCount(); if (!isLogin) { return ( @@ -99,7 +100,12 @@ export default function LoginButtonV2() { setOpenMenu((pre) => !pre); }} > - +
+ + {unreadCount > 0 && ( +
+ )} +
navigate('/notification/activity')} + onClick={() => { + clearUnread(); + navigate('/notification/activity'); + }} > - - Notifications +
+
+ + Notifications +
+ {unreadCount > 0 && ( +
+ {unreadCount} +
+ )} +
Communities - - + clearUnread()} + > + 0} + /> Notification diff --git a/apps/u3/src/components/poster/gallery/GalleryItem.tsx b/apps/u3/src/components/poster/gallery/GalleryItem.tsx index 67d41a41..709d25d4 100644 --- a/apps/u3/src/components/poster/gallery/GalleryItem.tsx +++ b/apps/u3/src/components/poster/gallery/GalleryItem.tsx @@ -16,6 +16,7 @@ import { Button } from '@/components/ui/button'; import PosterPreviewModal from './PosterPreviewModal'; import { PosterEntity, PosterMetadata } from '@/services/poster/types/poster'; import { getSaleStatus } from '@/utils/shared/zora'; +import { DailyPosterLayoutData } from '../layout/DailyPosterLayout'; interface GalleryItemProps extends ComponentPropsWithoutRef<'div'> { data: PosterEntity; @@ -31,7 +32,14 @@ export default function GalleryItem({ const { imageOriginUrl, createAt, posterDataJson } = metadata?.properties || {}; - const posterData = JSON.parse(posterDataJson || '{}'); + const posterData = JSON.parse( + posterDataJson || '{}' + ) as DailyPosterLayoutData; + // TODO 2024/3/19 的数据异常,存储的数据多了,需要截取下数据 + posterData.links = posterData.links.slice(0, 4); + posterData.dapps = posterData.dapps.slice(0, 2); + posterData.topics = posterData.topics.slice(0, 2); + const previewImg = imageOriginUrl; const { isLogin, login } = useLogin(); @@ -82,12 +90,13 @@ export default function GalleryItem({ open={openPreviewModal} closeModal={() => setOpenPreviewModal(false)} /> - setOpenPreviewModal(true)} - /> + > + +
+
{dayjs(posterCreateTimestamp).format('MMMM DD, YYYY')} diff --git a/apps/u3/src/container/community/CommunityLayout.tsx b/apps/u3/src/container/community/CommunityLayout.tsx index 660f2e59..c78f917f 100644 --- a/apps/u3/src/container/community/CommunityLayout.tsx +++ b/apps/u3/src/container/community/CommunityLayout.tsx @@ -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(); @@ -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) {
@@ -102,6 +94,7 @@ export default function CommunityLayout() { className="min-sm:hidden" communityInfo={communityInfo} channelId={channel?.channel_id} + totalMembers={totalMembers} />
diff --git a/apps/u3/src/container/community/CommunityMobileHeader.tsx b/apps/u3/src/container/community/CommunityMobileHeader.tsx index 3cd97ecd..c6b704fb 100644 --- a/apps/u3/src/container/community/CommunityMobileHeader.tsx +++ b/apps/u3/src/container/community/CommunityMobileHeader.tsx @@ -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); @@ -65,7 +67,8 @@ export default function CommunityMobileHeader({ value={nav.href} className="hover:bg-[#20262F]" > - {nav.title} + {nav.title}{' '} + {nav.href.includes('/members') && `(${totalMembers})`} ); })} diff --git a/apps/u3/src/container/community/MembersLayout.tsx b/apps/u3/src/container/community/MembersLayout.tsx index 8ac5e167..f1f68763 100644 --- a/apps/u3/src/container/community/MembersLayout.tsx +++ b/apps/u3/src/container/community/MembersLayout.tsx @@ -1,8 +1,10 @@ -import { useEffect } from 'react'; import { useOutletContext } from 'react-router-dom'; import InfiniteScroll from 'react-infinite-scroll-component'; -import { useFarcasterCtx } from 'src/contexts/social/FarcasterCtx'; import Loading from 'src/components/common/loading/Loading'; +import { useEffect } from 'react'; +import useLoadCommunityMembers from '@/hooks/community/useLoadCommunityMembers'; +import FarcasterMemberItem from '@/components/community/FarcasterMemberItem'; +import { useFarcasterCtx } from '@/contexts/social/FarcasterCtx'; export default function MembersLayout() { return ( @@ -10,102 +12,61 @@ export default function MembersLayout() {
-
- -
+ {/*
top members
*/}
); } function TotalMembers() { - const { openFarcasterQR } = useFarcasterCtx(); + const { following } = useFarcasterCtx(); + const { totalMembers, communityInfo } = useOutletContext(); + const id = communityInfo?.id; const { - members, - membersPageInfo, - membersFirstLoading, - membersMoreLoading, - loadFirstMembers, - loadMoreMembers, - } = useOutletContext(); - const membersLen = members.length; + communityMembers, + loadCommunityMembers, + loading: membersLoading, + pageInfo, + } = useLoadCommunityMembers(); + useEffect(() => { - if (membersLen === 0) { - loadFirstMembers(); + if (id) { + loadCommunityMembers({ id }); } - }, [membersLen]); + }, [id]); return ( -
-
Total Members ({membersLen})
+
+
+ Total Members ({totalMembers}) +
- {(membersFirstLoading && ( -
- -
- )) || ( - { - if (membersMoreLoading) return; - loadMoreMembers(); - }} - hasMore={membersPageInfo?.hasNextPage || false} - loader={ -
- -
- } - scrollableTarget="total-members-scroll-wrapper" - > -
- {members.map((member) => { - return ( -
-
-
{member.name}
-
- ); - })} + { + if (!id || membersLoading) return; + loadCommunityMembers({ id }); + }} + hasMore={pageInfo?.hasNextPage || false} + loader={ +
+
-
- )} -
-
- ); -} - -function TopMembers() { - const { openFarcasterQR } = useFarcasterCtx(); - const { topMembers, topMembersLoading, loadTopMembers } = - useOutletContext(); - const membersLen = topMembers.length; - useEffect(() => { - if (membersLen === 0) { - loadTopMembers(); - } - }, [membersLen]); - - return ( -
-
✨ Top 10 Community Stars
-
- {topMembersLoading ? ( -
- -
- ) : ( -
- {topMembers.map((member) => { + } + scrollableTarget="total-members-scroll-wrapper" + > +
+ {communityMembers.map((data) => { return ( -
-
-
{member.name}
-
+ ); })}
- )} +
); diff --git a/apps/u3/src/container/explore/Home.tsx b/apps/u3/src/container/explore/Home.tsx index 5b7267ee..12bcf6a0 100644 --- a/apps/u3/src/container/explore/Home.tsx +++ b/apps/u3/src/container/explore/Home.tsx @@ -141,7 +141,7 @@ export default function Home() { }); fetchTrendingCommunities({ - pageSize: 3, + pageSize: 12, pageNumber: 1, }) .then((res) => { @@ -163,7 +163,7 @@ export default function Home() { const { trendChannels, trendChannelsLoading } = useFarcasterCtx(); useEffect(() => { setTopChannels({ - channels: (trendChannels || []).splice(0, 4).map((item) => { + channels: (trendChannels || []).splice(0, 2).map((item) => { return { channel_id: item.channel_id, logo: item?.image, diff --git a/apps/u3/src/container/poster/PosterGallery.tsx b/apps/u3/src/container/poster/PosterGallery.tsx index 992ef3cf..75202018 100644 --- a/apps/u3/src/container/poster/PosterGallery.tsx +++ b/apps/u3/src/container/poster/PosterGallery.tsx @@ -75,7 +75,7 @@ export default function PosterGallery() { } scrollableTarget="poster-gallery-scroll" > -
+
{posters.map((item) => { return ; })} diff --git a/apps/u3/src/contexts/notification/NotificationStoreCtx.tsx b/apps/u3/src/contexts/notification/NotificationStoreCtx.tsx index f7bcbb95..e482e533 100644 --- a/apps/u3/src/contexts/notification/NotificationStoreCtx.tsx +++ b/apps/u3/src/contexts/notification/NotificationStoreCtx.tsx @@ -1,30 +1,23 @@ /* eslint-disable no-plusplus */ import { + Notification as LensNotification, + LimitType, + useNotifications as useLensNotifications, +} from '@lens-protocol/react-web'; +import { + ReactNode, createContext, useCallback, useContext, - useEffect, useMemo, - useState, - ReactNode, } from 'react'; - -import { - useNotifications as useLensNotifications, - Notification as LensNotification, - LimitType, -} from '@lens-protocol/react-web'; - +import { NotificationType } from '@/services/notification/types/notifications'; import useFarcasterNotifications from '../../hooks/social/farcaster/useFarcasterNotifications'; -import useUnreadFarcasterNotificationsCount from '../../hooks/social/farcaster/useUnreadFarcasterNotificationsCount'; import { FarcasterNotification } from '../../services/social/api/farcaster'; -import { NotificationType } from '@/services/notification/types/notifications'; interface NotificationStoreCtxValue { notifications: (LensNotification | FarcasterNotification)[] | undefined; farcasterUserData: { [key: string]: { type: number; value: string }[] }; - unreadCount: number; - clearUnread: () => void; loading: boolean; hasMore: boolean; loadMore: () => void; @@ -33,8 +26,6 @@ interface NotificationStoreCtxValue { const defaultContextValue: NotificationStoreCtxValue = { notifications: [], farcasterUserData: {}, - unreadCount: 0, - clearUnread: () => {}, loading: false, hasMore: false, loadMore: () => {}, @@ -152,10 +143,6 @@ export function NotificationStoreProvider({ children, config, }: NotificationStoreProviderProps) { - const [unreadCount, setUnreadCount] = useState( - defaultContextValue.unreadCount - ); - let lensPageSize: LimitType; if (config.pageSize > 50) { lensPageSize = LimitType.Fifty; @@ -192,11 +179,6 @@ export function NotificationStoreProvider({ [lensNotifications, farcasterNotifications] ); - const { - unreadNotificationCount: unreadFarcasterCount, - clear: clearFarcasterUnread, - } = useUnreadFarcasterNotificationsCount(config.fid); - const loadMore = useCallback(async () => { if (lensNotificationsHasMore && !lensNotificationsLoading) { await loadMoreLensNotifications(); @@ -217,15 +199,6 @@ export function NotificationStoreProvider({ farcasterNotificationsLoading, ]); - const clearUnread = useCallback(async () => { - await clearFarcasterUnread(); - setUnreadCount(0); - }, [clearFarcasterUnread]); - - useEffect(() => { - setUnreadCount(unreadFarcasterCount); - }, [unreadFarcasterCount, config]); - return ( {children} diff --git a/apps/u3/src/hooks/community/useLoadCommunityMembers.ts b/apps/u3/src/hooks/community/useLoadCommunityMembers.ts index 05b9a872..e04e0f7b 100644 --- a/apps/u3/src/hooks/community/useLoadCommunityMembers.ts +++ b/apps/u3/src/hooks/community/useLoadCommunityMembers.ts @@ -1,60 +1,76 @@ -import { useCallback, useState } from 'react'; -import { - CommunityMembersPageInfo, - fetchCommunityMembers, -} from '@/services/community/api/community'; +import { useCallback, useRef, useState } from 'react'; +import { toast } from 'react-toastify'; +import { fetchCommunityMembers } from '@/services/community/api/community'; import { MemberEntity } from '@/services/community/types/community'; -const PAGE_SIZE = 16; -export default function useLoadCommunityMembers(communityId: string | number) { - const [members, setMembers] = useState([]); - const [pageInfo, setPageInfo] = useState({ - hasNextPage: true, +const PAGE_SIZE = 30; +export const getDefaultCommunityMembersCachedData = () => { + return { + data: [], + pageInfo: { + hasNextPage: true, + }, + nextPageNumber: 1, + }; +}; + +type CommunityMembersCachedData = ReturnType< + typeof getDefaultCommunityMembersCachedData +>; + +type CommunityMembersOpts = { + cachedDataRefValue?: CommunityMembersCachedData; +}; + +export default function useLoadCommunityMembers(opts?: CommunityMembersOpts) { + const { cachedDataRefValue } = opts || {}; + const defaultCachedDataRef = useRef({ + ...getDefaultCommunityMembersCachedData(), }); - const [firstLoading, setFirstLoading] = useState(false); - const [moreLoading, setMoreLoading] = useState(false); + const cachedData = cachedDataRefValue || defaultCachedDataRef.current; - const loadFirst = useCallback(async () => { - setFirstLoading(true); - setMembers([]); - try { - const res = await fetchCommunityMembers(communityId, { - pageSize: PAGE_SIZE, - }); - const data = res.data?.data; - setPageInfo(data.pageInfo); - setMembers(data.members); - } catch (error) { - console.error(error); - } finally { - setFirstLoading(false); - } - }, [communityId]); + const [communityMembers, setCommunityMembers] = useState>( + cachedData.data + ); + const [loading, setLoading] = useState(false); + const [pageInfo, setPageInfo] = useState(cachedData.pageInfo); + + const loadCommunityMembers = useCallback(async (params?: { id?: string }) => { + const { id } = params || {}; - const loadMore = useCallback(async () => { - if (firstLoading || moreLoading) return; - setMoreLoading(true); + if (cachedData.pageInfo.hasNextPage === false) { + return; + } + setLoading(true); try { - const res = await fetchCommunityMembers(communityId, { + const res = await fetchCommunityMembers(id, { pageSize: PAGE_SIZE, - endCursor: pageInfo.endCursor, + pageNumber: cachedData.nextPageNumber, }); - const data = res.data?.data; - setPageInfo(data.pageInfo); - setMembers((prev) => [...prev, ...data.members]); + const { code, msg, data } = res.data; + if (code === 0) { + const newCommunityMembers = data?.members || []; + const hasNextPage = newCommunityMembers.length >= PAGE_SIZE; + setCommunityMembers((prev) => [...prev, ...newCommunityMembers]); + setPageInfo({ hasNextPage }); + cachedData.data = cachedData.data.concat(newCommunityMembers); + cachedData.nextPageNumber += 1; + cachedData.pageInfo.hasNextPage = hasNextPage; + } else { + throw new Error(msg); + } } catch (error) { console.error(error); + toast.error(`Load community members failed: ${error.message}`); } finally { - setMoreLoading(false); + setLoading(false); } - }, [communityId, pageInfo, moreLoading, firstLoading]); + }, []); return { - members, + loading, + communityMembers, + loadCommunityMembers, pageInfo, - firstLoading, - moreLoading, - loadFirst, - loadMore, }; } diff --git a/apps/u3/src/hooks/community/useLoadCommunityMembersTotalNum.ts b/apps/u3/src/hooks/community/useLoadCommunityMembersTotalNum.ts new file mode 100644 index 00000000..da7c5a3e --- /dev/null +++ b/apps/u3/src/hooks/community/useLoadCommunityMembersTotalNum.ts @@ -0,0 +1,42 @@ +import { useCallback, useState } from 'react'; +import { toast } from 'react-toastify'; +import { fetchCommunityMembers } from '@/services/community/api/community'; + +export default function useLoadCommunityMembersTotalNum() { + const [totalNum, setTotalNum] = useState(0); + const [loading, setLoading] = useState(false); + + const loadCommunityMembersTotalNum = useCallback( + async (params?: { id?: string | number }) => { + const { id } = params || {}; + setLoading(true); + try { + const res = await fetchCommunityMembers(id, { + pageSize: 0, + pageNumber: 0, + }); + const { code, msg, data } = res.data; + if (code === 0) { + const num = data?.totalNum || 0; + setTotalNum(num); + } else { + throw new Error(msg); + } + } catch (error) { + console.error(error); + toast.error( + `Load community members total num failed: ${error.message}` + ); + } finally { + setLoading(false); + } + }, + [] + ); + + return { + loading, + totalNum, + loadCommunityMembersTotalNum, + }; +} diff --git a/apps/u3/src/hooks/social/useUnreadNotificationsCount.ts b/apps/u3/src/hooks/social/useUnreadNotificationsCount.ts new file mode 100644 index 00000000..ffde25fb --- /dev/null +++ b/apps/u3/src/hooks/social/useUnreadNotificationsCount.ts @@ -0,0 +1,27 @@ +import { useCallback, useEffect, useState } from 'react'; +import useUnreadFarcasterNotificationsCount from './farcaster/useUnreadFarcasterNotificationsCount'; +import useFarcasterCurrFid from './farcaster/useFarcasterCurrFid'; +// todo: add lens notification count +export default function useUnreadNotificationsCount() { + const [unreadCount, setUnreadCount] = useState(0); + + const fid = Number(useFarcasterCurrFid()); + const { + unreadNotificationCount: unreadFarcasterCount, + clear: clearFarcasterUnread, + } = useUnreadFarcasterNotificationsCount(fid); + + const clearUnread = useCallback(async () => { + await clearFarcasterUnread(); + setUnreadCount(0); + }, [clearFarcasterUnread]); + + useEffect(() => { + setUnreadCount(unreadFarcasterCount); + }, [unreadFarcasterCount]); + + return { + unreadCount, + clearUnread, + }; +} diff --git a/apps/u3/src/services/community/api/community.ts b/apps/u3/src/services/community/api/community.ts index 5e2dc0d4..99026466 100644 --- a/apps/u3/src/services/community/api/community.ts +++ b/apps/u3/src/services/community/api/community.ts @@ -131,24 +131,21 @@ export function fetchCommunity( }); } -export type CommunityMembersPageInfo = { - endCursor?: number; - hasNextPage: boolean; -}; export type CommunityMembersParams = { pageSize?: number; - endCursor?: number; + pageNumber?: number; + type?: string; }; -export type CommunityMembersResponse = { +export type CommunityMembersData = { members: Array; - pageInfo: CommunityMembersPageInfo; + totalNum: number; }; export function fetchCommunityMembers( id: string | number, params: CommunityMembersParams -): RequestPromise> { +): RequestPromise> { return request({ - url: `/community/${id}/members`, + url: `/topics/${id}/members`, method: 'get', params, }); @@ -159,7 +156,7 @@ export function fetchCommunityTopMembers( id: string | number ): RequestPromise> { return request({ - url: `/community/${id}/members/top`, + url: `/topics/${id}/members/top`, method: 'get', }); } diff --git a/apps/u3/src/services/community/types/community.ts b/apps/u3/src/services/community/types/community.ts index 64fc94ad..48e0a1cd 100644 --- a/apps/u3/src/services/community/types/community.ts +++ b/apps/u3/src/services/community/types/community.ts @@ -40,11 +40,8 @@ export type CommunityStatistics = { }; export type MemberEntity = { - id: number; - name: string; - avatar: string; - address: string; - bio: string; + fid: string; + data: Array<{ fid: string; type: number; value: string }>; }; export type CommunityInfo = CommunityEntity & CommunityStatistics; diff --git a/apps/u3/src/services/shared/api/explore.ts b/apps/u3/src/services/shared/api/explore.ts index b3c6236e..983c8257 100644 --- a/apps/u3/src/services/shared/api/explore.ts +++ b/apps/u3/src/services/shared/api/explore.ts @@ -16,7 +16,7 @@ export function getTopLinks(): RequestPromise { return request({ url: `/3r-farcaster/embedLinks`, params: { - pageSize: 6, + pageSize: 4, orderBy: 'TRENDING', }, method: 'get', @@ -26,7 +26,7 @@ export function getHighScoreDapps(): RequestPromise { return request({ url: `/dapps/searching`, params: { - pageSize: 4, + pageSize: 2, pageNumber: 0, keywords: '', type: '', diff --git a/apps/u3/src/utils/community/getCommunityNavs.ts b/apps/u3/src/utils/community/getCommunityNavs.ts index 4b44235d..1406c9ea 100644 --- a/apps/u3/src/utils/community/getCommunityNavs.ts +++ b/apps/u3/src/utils/community/getCommunityNavs.ts @@ -16,7 +16,7 @@ export default function getCommunityNavs( const mainNavs = [ { title: 'Posts', href: getCommunityPostsPath(channelId) }, { title: 'Links', href: getCommunityLinksPath(channelId) }, - // { title: 'Members', href: `/community/${channelId}/members` }, + { title: 'Members', href: `/community/${channelId}/members` }, ]; const nft = nfts?.length > 0 ? nfts[0] : null; if (nft) {