Skip to content

Commit

Permalink
Merge pull request #97 from BuidlerDAO/nig-655
Browse files Browse the repository at this point in the history
feat: nig-655
  • Loading branch information
Ken-LiuL authored Apr 1, 2024
2 parents e7f0492 + 6e429ec commit 67e75d2
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 83 deletions.
7 changes: 7 additions & 0 deletions src/service/community/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ export async function getList(status: 0 | 1) {
return response.data.items;
}

export async function getBySubject(subject: string) {
const response = await http.get<ResultData<Community>>('/api/community', {
subject,
});
return response.data;
}

// 获取我在这个 subject 社区里的用户信息
export async function getMyInfo(subject: string) {
const response = await http.get<ResultData<CommunityUserInfo>>('/api/community/user/me', {
Expand Down
7 changes: 6 additions & 1 deletion src/welcome/Profile/Community.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const Community = () => {
getList(1).then((items: CommunityWithMessage[]) => {
let len = 0;
p++;
if (items.length === 0) {
setUnlockedCommunities(items);
setLoading(false);
return;
}
items.forEach((item) => {
getUnreadMessageCount(wallet, item.subject).then((res) => {
item.lastMsg = res.latestMsg;
Expand Down Expand Up @@ -170,7 +175,7 @@ const Community = () => {
{renderUnlocked()}
{renderLocked()}
{selectedCommunity && selectedCommunity.status === 0 && (
<StackModal community={selectedCommunity} onClose={handleStakeModalClose} />
<StackModal subject={selectedCommunity.subject} onClose={handleStakeModalClose} />
)}
{
<ChatRoomDrawer
Expand Down
167 changes: 101 additions & 66 deletions src/welcome/Profile/community/ChatRoomDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import dayjs from 'dayjs';

import ArrowBackIcon from '../../../components/icons/ArrowBackIcon';
import Loading from '../../../components/Loading';
import Modal from '../../../components/Modal';
import useAccount from '../../../hooks/useAccount';
import { getMyInfo, getUserCount } from '../../../service/community';
import { ReceiveMessage, SendMessage } from '../../../service/room';
Expand Down Expand Up @@ -50,31 +51,36 @@ export default function ChatRoomDrawer({ open = false, community, onClose }: Pro
manual: true,
});

const isFirstRenderMessages = useRef(false);
const [isFirstRenderCompleted, setIsFirstRenderCompleted] = useState(false);
useEffect(() => {
if (ref.current == null) return;
const observer = new ResizeObserver(() => {
if (ref.current == null) return;
if (isFirstRenderMessages.current === false) return;
if (ref.current.scrollHeight > ref.current.clientHeight) {
ref.current.scrollTop = 999999999;
if (isFirstRenderCompleted === false) return;
if (open) {
const observer = new ResizeObserver(() => {
if (ref.current == null) return;
if (ref.current.scrollHeight > ref.current.clientHeight) {
ref.current.scrollTop = 999999999;
}
});
// 监听子组件,以后图片可能来自网络,可能要考虑图片加载完成后导致的位置变更
for (const child of ref.current.children) {
observer.observe(child);
}
});
observer.observe(ref.current);
return () => {
observer.disconnect();
};
}, []);
// 需要提前更新 isFirstRenderMessages 让 ResizeObserver 触发后能及时拿到新状态
useLayoutEffect(() => {
return () => {
observer.disconnect();
};
}
}, [isFirstRenderCompleted, open]);

useEffect(() => {
if (open) {
if (messages.length > 0 && isFirstRenderMessages.current === false) {
isFirstRenderMessages.current = true;
if (messages.length > 0 && members.length > 0 && isFirstRenderCompleted === false) {
setIsFirstRenderCompleted(true);
}
} else {
isFirstRenderMessages.current = false;
setIsFirstRenderCompleted(false);
}
}, [messages.length, open]);
}, [isFirstRenderCompleted, members.length, messages.length, open]);

useEffect(() => {
if (ref.current == null) return;
Expand Down Expand Up @@ -111,15 +117,29 @@ export default function ChatRoomDrawer({ open = false, community, onClose }: Pro
);

function renderMessages() {
if (messages.length === 0) return <Loading />;
if (members.length === 0) return <Loading />;
if (messages.length === 0)
return (
<div className="flex flex-1 items-center justify-center">
<Loading />
</div>
);
if (members.length === 0)
return (
<div className="flex flex-1 items-center justify-center">
<Loading />
</div>
);
return messages.map((msg) => {
const senderUserInfo = members.find((member) => member.address === msg.sender);
if (senderUserInfo == null) return null;
if (msg.sender === wallet) {
return <MessageFromMeItem key={msg.id} msg={msg} userInfo={senderUserInfo} />;
}
return <MessageFromOtherItem key={msg.id} msg={msg} userInfo={senderUserInfo} />;
return (
<MessageItem
key={msg.id}
msg={msg}
from={msg.sender === wallet ? 'me' : 'other'}
userInfo={senderUserInfo}
/>
);
});
}
return (
Expand Down Expand Up @@ -159,14 +179,14 @@ export default function ChatRoomDrawer({ open = false, community, onClose }: Pro
</header>
<div
ref={ref}
className="xfans-scrollbar relative flex flex-1 flex-col items-center justify-center overflow-y-auto px-[16px]"
className="xfans-scrollbar relative flex flex-1 flex-col items-center overflow-y-auto px-[16px]"
onScroll={handleScroll}
>
{renderMessages()}
</div>
<SendMessageBox disabled={myInfo?.isBlocked} sendMessage={sendMessage} />
{isStackModalOpen && community && (
<StackModal community={community} onClose={() => setIsStackModalOpen(false)} />
<StackModal subject={community.subject} onClose={() => setIsStackModalOpen(false)} />
)}
<MembersDrawer
isOwner={myInfo?.address === community?.subject}
Expand All @@ -193,27 +213,74 @@ function FireIcon() {
type MessageItemProps = {
msg: ReceiveMessage;
userInfo: CommunityUserInfo;
from: 'other' | 'me';
};

function MessageFromMeItem({ msg, userInfo }: MessageItemProps) {
function MessageItem({ msg, userInfo, from }: MessageItemProps) {
const { openProfile } = useProfileModal();
const [previewImg, setPreviewImg] = useState<string | null>(null);
const { run: batchUserInfo } = useTweetBatchUserInfo(
[userInfo.username],
(result) => {
const twitterUserInfo = result?.data?.items?.[0];
if (twitterUserInfo == null) return;
openProfile(twitterUserInfo, 1);
openProfile(twitterUserInfo, 0);
},
() => undefined
);
function handleAvatarClick() {
batchUserInfo();
}

if (from === 'other') {
return (
<div className="mt-[39px] flex w-full items-start">
<img
onClick={handleAvatarClick}
className="mr-[12px] w-[44px] cursor-pointer rounded-full"
src={userInfo.avatar}
alt="avatar"
/>
<div className="flex flex-col">
<div className="flex max-w-[290px] flex-col rounded-[25px] rounded-tl-none bg-[#EEEEEE] p-[16px]">
<span className="text-xs text-[#B9B9BA]">{userInfo.username}</span>
<div className="text-sm text-[#505050]">
{msg.image && (
<img
onClick={() => setPreviewImg(msg.image)}
src={msg.image}
alt="pic"
className="mb-[16px]"
/>
)}
{msg.message}
</div>
</div>
<span className="mt-[6px] text-xs text-[#A6A6A9]">
{dayjs(msg.createTime).format('YYYY/MM/DD HH:mm')}
</span>
</div>
{previewImg && (
<Modal open onClose={() => setPreviewImg(null)} closebuttonstyle={{ display: 'none' }}>
<img src={previewImg} alt="preview" className="mb-[18px]" />
</Modal>
)}
</div>
);
}

return (
<div className="mt-[39px] flex w-full items-start justify-end">
<div className="flex flex-col items-end">
<div className="max-w-[290px] rounded-[25px] rounded-tr-none bg-[#9A6CF9] p-[16px] text-sm text-white">
{msg.image && <img src={msg.image} alt="pic" className="mb-[16px]" />}
{msg.image && (
<img
onClick={() => setPreviewImg(msg.image)}
src={msg.image}
alt="pic"
className="mb-[16px]"
/>
)}
{msg.message}
</div>
<span className="mt-[6px] text-xs text-[#A6A6A9]">
Expand All @@ -226,43 +293,11 @@ function MessageFromMeItem({ msg, userInfo }: MessageItemProps) {
src={userInfo.avatar}
alt="avatar"
/>
</div>
);
}
function MessageFromOtherItem({ msg, userInfo }: MessageItemProps) {
const { openProfile } = useProfileModal();
const { run: batchUserInfo } = useTweetBatchUserInfo(
[userInfo.username],
(result) => {
const twitterUserInfo = result?.data?.items?.[0];
if (twitterUserInfo == null) return;
openProfile(twitterUserInfo, 1);
},
() => undefined
);
function handleAvatarClick() {
batchUserInfo();
}
return (
<div className="mt-[39px] flex w-full items-start">
<img
onClick={handleAvatarClick}
className="mr-[12px] w-[44px] cursor-pointer rounded-full"
src={userInfo.avatar}
alt="avatar"
/>
<div className="flex flex-col">
<div className="flex max-w-[290px] flex-col rounded-[25px] rounded-tl-none bg-[#EEEEEE] p-[16px]">
<span className="text-xs text-[#B9B9BA]">{userInfo.username}</span>
<div className="text-sm text-[#505050]">
{msg.image && <img src={msg.image} alt="pic" className="mb-[16px]" />}
{msg.message}
</div>
</div>
<span className="mt-[6px] text-xs text-[#A6A6A9]">
{dayjs(msg.createTime).format('YYYY/MM/DD HH:mm')}
</span>
</div>
{previewImg && (
<Modal open onClose={() => setPreviewImg(null)} closebuttonstyle={{ display: 'none' }}>
<img src={previewImg} alt="preview" className="mb-[18px]" />
</Modal>
)}
</div>
);
}
Expand Down
28 changes: 16 additions & 12 deletions src/welcome/Profile/community/StackModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { BasicButton, PrimaryButton } from '../../../components/Button';
import Modal from '../../../components/Modal';
import NumberInput from '../../../components/NumberInput';
import { success } from '../../../components/Toaster';
import { getBySubject } from '../../../service/community';
import {
getSharesBalance,
getStakeBalance,
Expand All @@ -16,18 +17,20 @@ import {
type ModalProps = {
// false cancel true confirm
onClose(fromConfirm?: boolean): void;
community: Community;
subject: string;
};
export default function StackModal({ onClose, community }: ModalProps) {
export default function StackModal({ onClose, subject }: ModalProps) {
const [currentTab, setCurrentTab] = useState<'stack' | 'unstack'>('stack');
const activedTabNavClassName = 'bg-[#9A6CF9] text-white';
const [sharesBalance, setSharesBalance] = useState('0');
const [stakeBalance, setStakeBalance] = useState('0');
const { data: community } = useRequest(() => getBySubject(subject));

useEffect(() => {
if (community == null) return;
getSharesBalance(community.subject).then(setSharesBalance);
getStakeBalance(community.subject).then(setStakeBalance);
}, [community.subject]);
}, [community]);

return (
<Modal
Expand All @@ -38,20 +41,20 @@ export default function StackModal({ onClose, community }: ModalProps) {
}}
>
<div className="relative flex flex-col items-center pb-[24px]">
<h2 className="text-[24px] font-medium text-[#2E2E32]">Unlock Vincents Community</h2>
<h2 className="text-[24px] font-medium text-[#2E2E32]">Unlock Vincent&apos;s Community</h2>
<div className="mt-[15px] h-[1px] w-[438px] bg-[#EBEEF0]"></div>

<p className="relative mt-[27px] pl-[25px] text-center text-sm text-[#919099]">
<Icon /> Stake at least 5 shares to unlock the
<br /> creators community
<br /> creator&apos;s community
</p>

<ProgressBar
staked={+community.stakedShares}
stakeRequired={+community.requiredStakedShares}
staked={+(community?.stakedShares ?? 0)}
stakeRequired={+(community?.requiredStakedShares ?? 0)}
/>
<p className="mt-[14px] w-full text-right text-sm text-[#919099]">
Community total staked: {+community.stakedShares / 100}
Community total staked: {+(community?.stakedShares ?? 0) / 100}
</p>
<p className="w-full text-right text-sm text-[#919099]">
you staked: {+stakeBalance / 100}
Expand Down Expand Up @@ -81,9 +84,9 @@ export default function StackModal({ onClose, community }: ModalProps) {
</div>

{currentTab === 'stack' ? (
<StackPanel onClose={onClose} address={community.subject} sharesBalance={sharesBalance} />
<StackPanel onClose={onClose} address={subject} sharesBalance={sharesBalance} />
) : (
<UnstackPanel onClose={onClose} address={community.subject} stakeBalance={stakeBalance} />
<UnstackPanel onClose={onClose} address={subject} stakeBalance={stakeBalance} />
)}
</div>
</Modal>
Expand Down Expand Up @@ -215,14 +218,15 @@ function UnstackPanel({ stakeBalance, address, onClose }: UnstackPanelProps) {
}

function ProgressBar({ staked, stakeRequired }: { staked: number; stakeRequired: number }) {
const percentage = Number.isNaN(staked / stakeRequired) ? 0 : (staked / stakeRequired) * 100;
return (
<div className="relative mt-[30px] h-[20px] w-full rounded-[31px] bg-[#F6F5F7]">
<div className="w-[50%]" />
<div
className={`item-center absolute left-0 top-0 flex h-[20px] rounded-full bg-[#9A6CF969] pl-[10px] pr-[10px]`}
style={{ width: `calc(${Math.min((staked / stakeRequired) * 100, 100)}%)` }}
style={{ width: `calc(${Math.min(percentage, 100)}%)` }}
/>
<div className="item-center absolute right-0 top-[1px] flex h-[18px] w-[18px] justify-center rounded-full border border-[#9A6CF9] bg-white text-xs text-[#2E2E32]">
<div className="absolute right-0 top-[1px] flex h-[18px] items-center justify-center rounded-full border border-[#9A6CF9] bg-white px-[5px] text-xs text-[#2E2E32]">
{stakeRequired / 100}
</div>
</div>
Expand Down
Loading

0 comments on commit 67e75d2

Please sign in to comment.