新增 pc-client:畅联 PC 端工程(Electron+React,按确认效果图改造,B-59)
- 基于 openim-electron-demo 改造:工号+密码登录(自研账号服务)、会话列表/聊天窗/通讯录界面重做
- 文字/语音/文件/图片消息、文件拖拽发送、表情面板、一对一语音通话(LiveKit)
- RTC 令牌对接账号服务带鉴权版 /api/rtc_token(Bearer imToken + {room,identity})
- account-service: CORS Allow-Headers 补 authorization(浏览器/渲染进程跨域调 rtc_token 需要)
- 附本地 mock 账号服务(scripts/mock-account-server.js)与截图联调脚本
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { Badge } from "antd";
|
||||
import clsx from "clsx";
|
||||
import i18n, { t } from "i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import group_notifications from "@/assets/images/contact/group_notifications.png";
|
||||
import my_friends from "@/assets/images/contact/my_friends.png";
|
||||
import my_groups from "@/assets/images/contact/my_groups.png";
|
||||
import new_friends from "@/assets/images/contact/new_friends.png";
|
||||
import FlexibleSider from "@/components/FlexibleSider";
|
||||
import { useContactStore } from "@/store";
|
||||
|
||||
const Links = [
|
||||
{
|
||||
label: t("placeholder.newFriends"),
|
||||
icon: new_friends,
|
||||
path: "/contact/newFriends",
|
||||
},
|
||||
{
|
||||
label: t("placeholder.groupNotification"),
|
||||
icon: group_notifications,
|
||||
path: "/contact/groupNotifications",
|
||||
},
|
||||
{
|
||||
label: t("placeholder.myFriend"),
|
||||
icon: my_friends,
|
||||
path: "/contact",
|
||||
},
|
||||
{
|
||||
label: t("placeholder.myGroup"),
|
||||
icon: my_groups,
|
||||
path: "/contact/myGroups",
|
||||
},
|
||||
];
|
||||
|
||||
i18n.on("languageChanged", () => {
|
||||
Links[0].label = t("placeholder.newFriends");
|
||||
Links[1].label = t("placeholder.groupNotification");
|
||||
Links[2].label = t("placeholder.myFriend");
|
||||
Links[3].label = t("placeholder.myGroup");
|
||||
});
|
||||
|
||||
const ContactSider = () => {
|
||||
const [selectIndex, setSelectIndex] = useState(2);
|
||||
const unHandleFriendApplicationCount = useContactStore(
|
||||
(state) => state.unHandleFriendApplicationCount,
|
||||
);
|
||||
const unHandleGroupApplicationCount = useContactStore(
|
||||
(state) => state.unHandleGroupApplicationCount,
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (location.hash.includes("/contact/newFriends")) {
|
||||
setSelectIndex(0);
|
||||
}
|
||||
if (location.hash.includes("/contact/groupNotifications")) {
|
||||
setSelectIndex(1);
|
||||
}
|
||||
if (location.hash.includes("/contact/myGroups")) {
|
||||
setSelectIndex(3);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getBadge = (index: number) => {
|
||||
if (index === 0) {
|
||||
return unHandleFriendApplicationCount;
|
||||
}
|
||||
if (index === 1) {
|
||||
return unHandleGroupApplicationCount;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
return (
|
||||
<FlexibleSider
|
||||
needHidden={true}
|
||||
wrapClassName="flex flex-col border-r border-[var(--gap-text)] bg-white"
|
||||
>
|
||||
<div className="app-drag px-4 pt-4">
|
||||
<div className="pb-3 text-base font-bold">{t("placeholder.contact")}</div>
|
||||
</div>
|
||||
<div className="h-full flex-1 overflow-y-auto bg-white">
|
||||
<ul>
|
||||
{Links.map((item, index) => {
|
||||
return (
|
||||
<li
|
||||
key={item.path}
|
||||
className={clsx(
|
||||
"mx-2 flex cursor-pointer items-center rounded-lg px-3 py-2.5 text-sm",
|
||||
index === selectIndex
|
||||
? "bg-[var(--primary-active)]"
|
||||
: "hover:bg-[#F3F5F7]",
|
||||
)}
|
||||
onClick={() => {
|
||||
setSelectIndex(index);
|
||||
navigate(String(item.path));
|
||||
}}
|
||||
>
|
||||
<Badge size="small" count={getBadge(index)}>
|
||||
<img
|
||||
alt={item.label}
|
||||
src={item.icon}
|
||||
className="mr-3 h-12 w-12 rounded-lg"
|
||||
/>
|
||||
</Badge>
|
||||
<div className="text-sm">{item.label}</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</FlexibleSider>
|
||||
);
|
||||
};
|
||||
export default ContactSider;
|
||||
@@ -0,0 +1,116 @@
|
||||
import { ApplicationHandleResult } from "@openim/wasm-client-sdk";
|
||||
import { GroupApplicationItem } from "@openim/wasm-client-sdk/lib/types/entity";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
|
||||
import ApplicationItem, { AccessFunction } from "@/components/ApplicationItem";
|
||||
import { IMSDK } from "@/layout/MainContentWrap";
|
||||
import { useUserStore } from "@/store";
|
||||
import { useContactStore } from "@/store/contact";
|
||||
import { feedbackToast } from "@/utils/common";
|
||||
|
||||
export const GroupNotifications = () => {
|
||||
const { t } = useTranslation();
|
||||
const currentUserID = useUserStore((state) => state.selfInfo.userID);
|
||||
|
||||
const recvGroupApplicationList = useContactStore(
|
||||
(state) => state.recvGroupApplicationList,
|
||||
);
|
||||
const sendGroupApplicationList = useContactStore(
|
||||
(state) => state.sendGroupApplicationList,
|
||||
);
|
||||
const updateRecvGroupApplication = useContactStore(
|
||||
(state) => state.updateRecvGroupApplication,
|
||||
);
|
||||
const updateSendGroupApplication = useContactStore(
|
||||
(state) => state.updateSendGroupApplication,
|
||||
);
|
||||
|
||||
const groupApplicationList = sortArray(
|
||||
recvGroupApplicationList.concat(sendGroupApplicationList),
|
||||
);
|
||||
|
||||
const onAccept = useCallback(
|
||||
async (application: GroupApplicationItem, isRecv: boolean) => {
|
||||
try {
|
||||
await IMSDK.acceptGroupApplication({
|
||||
groupID: application.groupID,
|
||||
fromUserID: application.userID,
|
||||
handleMsg: "",
|
||||
});
|
||||
const newApplication = {
|
||||
...application,
|
||||
handleResult: ApplicationHandleResult.Agree,
|
||||
};
|
||||
if (isRecv) {
|
||||
updateRecvGroupApplication(newApplication);
|
||||
} else {
|
||||
updateSendGroupApplication(newApplication);
|
||||
}
|
||||
} catch (error) {
|
||||
feedbackToast({ error });
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const onReject = useCallback(
|
||||
async (application: GroupApplicationItem, isRecv: boolean) => {
|
||||
try {
|
||||
await IMSDK.refuseGroupApplication({
|
||||
groupID: application.groupID,
|
||||
fromUserID: application.userID,
|
||||
handleMsg: "",
|
||||
});
|
||||
const newApplication = {
|
||||
...application,
|
||||
handleResult: ApplicationHandleResult.Reject,
|
||||
};
|
||||
if (isRecv) {
|
||||
updateRecvGroupApplication(newApplication);
|
||||
} else {
|
||||
updateSendGroupApplication(newApplication);
|
||||
}
|
||||
} catch (error) {
|
||||
feedbackToast({ error });
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col bg-white">
|
||||
<p className="m-5.5 text-base font-extrabold">
|
||||
{t("placeholder.groupNotification")}
|
||||
</p>
|
||||
<div className="flex-1 pb-3">
|
||||
<Virtuoso
|
||||
className="h-full overflow-x-hidden"
|
||||
data={groupApplicationList}
|
||||
itemContent={(_, item) => (
|
||||
<ApplicationItem
|
||||
key={`${item.userID}${item.reqTime}`}
|
||||
source={item}
|
||||
currentUserID={currentUserID}
|
||||
onAccept={onAccept as AccessFunction}
|
||||
onReject={onReject as AccessFunction}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const sortArray = (list: GroupApplicationItem[]) => {
|
||||
list.sort((a, b) => {
|
||||
if (a.handleResult === 0 && b.handleResult !== 0) {
|
||||
return -1;
|
||||
} else if (b.handleResult === 0 && a.handleResult !== 0) {
|
||||
return 1;
|
||||
}
|
||||
return b.reqTime - a.reqTime;
|
||||
});
|
||||
return list;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Layout } from "antd";
|
||||
import { Outlet } from "react-router-dom";
|
||||
|
||||
import ContactSider from "@/pages/contact/ContactSider";
|
||||
|
||||
export const Contact = () => {
|
||||
return (
|
||||
<Layout className="relative z-0 flex-row">
|
||||
<ContactSider />
|
||||
<Outlet />
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
forwardRef,
|
||||
ForwardRefRenderFunction,
|
||||
memo,
|
||||
useImperativeHandle,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
type IAlphabetIndexProps = {
|
||||
indexList: string[];
|
||||
scrollToLetter: (idx: number) => void;
|
||||
};
|
||||
|
||||
const AlphabetIndex: ForwardRefRenderFunction<
|
||||
{ updateCurrentLetter: (letter: string) => void },
|
||||
IAlphabetIndexProps
|
||||
> = ({ indexList, scrollToLetter }, ref) => {
|
||||
const [currentAlphabet, setCurrentAlphabet] = useState("");
|
||||
|
||||
const jumpToLetter = (idx: number, letter: string) => {
|
||||
scrollToLetter(idx);
|
||||
setCurrentAlphabet(letter);
|
||||
};
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
updateCurrentLetter: (letter: string) => setCurrentAlphabet(letter),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="absolute right-3 top-14 z-10 flex scale-90 flex-col items-center">
|
||||
{indexList.map((letter, idx) => (
|
||||
<span
|
||||
className={clsx("my-0.5 cursor-pointer text-xs text-[var(--sub-text)]", {
|
||||
"!text-[#0073D9]": currentAlphabet === letter,
|
||||
})}
|
||||
key={letter}
|
||||
onClick={() => jumpToLetter(idx, letter)}
|
||||
>
|
||||
{letter}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(forwardRef(AlphabetIndex));
|
||||
@@ -0,0 +1,23 @@
|
||||
import { FriendUserItem } from "@openim/wasm-client-sdk/lib/types/entity";
|
||||
|
||||
import OIMAvatar from "@/components/OIMAvatar";
|
||||
|
||||
const FriendListItem = ({
|
||||
friend,
|
||||
showUserCard,
|
||||
}: {
|
||||
friend: FriendUserItem;
|
||||
showUserCard: (userID: string) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center rounded-md px-3.5 pb-3 pt-2.5 transition-colors hover:bg-[var(--primary-active)]"
|
||||
onClick={() => showUserCard(friend.userID)}
|
||||
>
|
||||
<OIMAvatar size={48} src={friend.faceURL} text={friend.remark || friend.nickname} />
|
||||
<div className="ml-3 truncate text-sm">{friend.remark || friend.nickname}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FriendListItem;
|
||||
@@ -0,0 +1,115 @@
|
||||
import { useRequest } from "ahooks";
|
||||
import { Empty, Spin } from "antd";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GroupedVirtuoso, GroupedVirtuosoHandle } from "react-virtuoso";
|
||||
|
||||
import { useContactStore } from "@/store";
|
||||
import { formatContactsByWorker } from "@/utils/contactsFormat";
|
||||
import { emit } from "@/utils/events";
|
||||
|
||||
import AlphabetIndex from "./AlphabetIndex";
|
||||
import FriendListItem from "./FriendListItem";
|
||||
|
||||
export const MyFriends = () => {
|
||||
const { t } = useTranslation();
|
||||
const friendList = useContactStore((state) => state.friendList);
|
||||
const virtuoso = useRef<GroupedVirtuosoHandle>(null);
|
||||
const alphabetRef = useRef<{ updateCurrentLetter: (letter: string) => void }>(null);
|
||||
|
||||
const { data: sectionData, cancel } = useRequest(
|
||||
() => formatContactsByWorker(friendList),
|
||||
{
|
||||
refreshDeps: [friendList],
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cancel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const scrollToLetter = useCallback(
|
||||
(idx: number) => {
|
||||
const prevNum = sectionData?.groupCounts.slice(0, idx).reduce((a, b) => a + b, 0);
|
||||
console.log(prevNum);
|
||||
|
||||
virtuoso.current?.scrollToIndex({
|
||||
index: prevNum ?? 0,
|
||||
// behavior: "smooth",
|
||||
});
|
||||
},
|
||||
[sectionData?.groupCounts],
|
||||
);
|
||||
|
||||
const showUserCard = useCallback((userID: string) => {
|
||||
emit("OPEN_USER_CARD", {
|
||||
userID,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const determineCurrentGroup = (startIndex: number) => {
|
||||
if (!sectionData) return;
|
||||
|
||||
let currentItemIndex = 0;
|
||||
|
||||
for (
|
||||
let groupIndex = 0;
|
||||
groupIndex < sectionData.groupCounts.length;
|
||||
groupIndex++
|
||||
) {
|
||||
const groupItemCount = sectionData.groupCounts[groupIndex];
|
||||
|
||||
if (startIndex < currentItemIndex + groupItemCount) {
|
||||
alphabetRef.current?.updateCurrentLetter(sectionData.indexList[groupIndex]);
|
||||
break;
|
||||
}
|
||||
|
||||
currentItemIndex += groupItemCount;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden bg-white">
|
||||
<div className="m-5.5 text-base font-extrabold">{t("placeholder.myFriend")}</div>
|
||||
{!sectionData ? (
|
||||
<Spin />
|
||||
) : !sectionData.groupCounts.length ? (
|
||||
<Empty className="mt-[30%]" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
) : (
|
||||
<div className="ml-4 mt-4 flex-1 overflow-auto pr-4">
|
||||
<AlphabetIndex
|
||||
ref={alphabetRef}
|
||||
indexList={sectionData.indexList}
|
||||
scrollToLetter={scrollToLetter}
|
||||
/>
|
||||
|
||||
<GroupedVirtuoso
|
||||
ref={virtuoso}
|
||||
groupCounts={sectionData.groupCounts}
|
||||
groupContent={(index) => (
|
||||
<div>
|
||||
<div className="bg-white px-3.5 pb-1 text-[13px] text-[#8E9AB0FF]">
|
||||
{sectionData.indexList[index]}
|
||||
</div>
|
||||
<div className="mx-3.5 mb-3 h-px w-full bg-[#E8EAEFFF] bg-white" />
|
||||
</div>
|
||||
)}
|
||||
itemContent={(index) => {
|
||||
return (
|
||||
<FriendListItem
|
||||
key={sectionData.totalList[index].userID}
|
||||
friend={sectionData.totalList[index]}
|
||||
showUserCard={showUserCard}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
rangeChanged={({ startIndex }) => determineCurrentGroup(startIndex)}
|
||||
className="no-scrollbar h-full overflow-x-hidden"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { GroupItem } from "@openim/wasm-client-sdk/lib/types/entity";
|
||||
|
||||
import OIMAvatar from "@/components/OIMAvatar";
|
||||
|
||||
const GroupListItem = ({
|
||||
source,
|
||||
showGroupCard,
|
||||
}: {
|
||||
source: GroupItem;
|
||||
showGroupCard: (group: GroupItem) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row rounded-md px-3.5 py-3 transition-colors hover:bg-[var(--primary-active)]"
|
||||
onClick={() => showGroupCard(source)}
|
||||
>
|
||||
<OIMAvatar size={48} src={source?.faceURL} isgroup />
|
||||
<div className="ml-3">
|
||||
<p className="text-sm">{source.groupName}</p>
|
||||
<p className="text-xs text-[#8E9AB0FF]">{source.memberCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupListItem;
|
||||
@@ -0,0 +1,77 @@
|
||||
import { GroupItem } from "@openim/wasm-client-sdk/lib/types/entity";
|
||||
import { Select } from "antd";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
|
||||
import { useContactStore, useUserStore } from "@/store";
|
||||
import { emit } from "@/utils/events";
|
||||
|
||||
import GroupListItem from "./GroupListItem";
|
||||
|
||||
export enum GroupTypeEnum {
|
||||
JoinedGroup,
|
||||
CreatedGroup,
|
||||
}
|
||||
|
||||
export const MyGroups = () => {
|
||||
const { t } = useTranslation();
|
||||
const [selectGroup, setSelectGroup] = useState(GroupTypeEnum.CreatedGroup);
|
||||
|
||||
const joinedGroupList = useContactStore((state) => state.groupList);
|
||||
const { userID } = useUserStore((state) => state.selfInfo);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
setSelectGroup(Number(value));
|
||||
};
|
||||
|
||||
const filterGroup = joinedGroupList.filter((group) => {
|
||||
if (selectGroup === GroupTypeEnum.JoinedGroup) {
|
||||
return group.creatorUserID !== userID;
|
||||
} else if (selectGroup === GroupTypeEnum.CreatedGroup) {
|
||||
return group.creatorUserID === userID;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const showGroupCard = useCallback((group: GroupItem) => {
|
||||
emit("OPEN_GROUP_CARD", group);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col bg-white">
|
||||
<div className="m-5.5 flex flex-row justify-between">
|
||||
<p className="text-base font-extrabold">{t("placeholder.myGroup")}</p>
|
||||
<Select
|
||||
defaultValue={String(selectGroup)}
|
||||
popupClassName="p-0"
|
||||
style={{ width: 200 }}
|
||||
onChange={handleChange}
|
||||
options={[
|
||||
{
|
||||
value: String(GroupTypeEnum.CreatedGroup),
|
||||
label: t("placeholder.myCreated"),
|
||||
},
|
||||
{
|
||||
value: String(GroupTypeEnum.JoinedGroup),
|
||||
label: t("placeholder.myJoined"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="box-border flex-1 overflow-y-auto px-2 pb-3">
|
||||
<Virtuoso
|
||||
className="h-full overflow-x-hidden"
|
||||
data={filterGroup}
|
||||
itemContent={(_, group) => (
|
||||
<GroupListItem
|
||||
key={group.groupID}
|
||||
source={group}
|
||||
showGroupCard={showGroupCard}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
import { ApplicationHandleResult } from "@openim/wasm-client-sdk";
|
||||
import { FriendApplicationItem } from "@openim/wasm-client-sdk/lib/types/entity";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
|
||||
import ApplicationItem, { AccessFunction } from "@/components/ApplicationItem";
|
||||
import { IMSDK } from "@/layout/MainContentWrap";
|
||||
import { useUserStore } from "@/store";
|
||||
import { useContactStore } from "@/store/contact";
|
||||
import { feedbackToast } from "@/utils/common";
|
||||
import { calcApplicationBadge } from "@/utils/imCommon";
|
||||
|
||||
export const NewFriends = () => {
|
||||
const { t } = useTranslation();
|
||||
const currentUserID = useUserStore((state) => state.selfInfo.userID);
|
||||
|
||||
const recvFriendApplicationList = useContactStore(
|
||||
(state) => state.recvFriendApplicationList,
|
||||
);
|
||||
const sendFriendApplicationList = useContactStore(
|
||||
(state) => state.sendFriendApplicationList,
|
||||
);
|
||||
const updateRecvFriendApplication = useContactStore(
|
||||
(state) => state.updateRecvFriendApplication,
|
||||
);
|
||||
const updateSendFriendApplication = useContactStore(
|
||||
(state) => state.updateSendFriendApplication,
|
||||
);
|
||||
|
||||
const friendApplicationList = sortArray(
|
||||
recvFriendApplicationList.concat(sendFriendApplicationList),
|
||||
);
|
||||
|
||||
const onAccept = useCallback(
|
||||
async (application: FriendApplicationItem, isRecv: boolean) => {
|
||||
try {
|
||||
await IMSDK.acceptFriendApplication({
|
||||
toUserID: application.fromUserID,
|
||||
handleMsg: "",
|
||||
});
|
||||
const newApplication = {
|
||||
...application,
|
||||
handleResult: ApplicationHandleResult.Agree,
|
||||
};
|
||||
if (isRecv) {
|
||||
updateRecvFriendApplication(newApplication);
|
||||
} else {
|
||||
updateSendFriendApplication(newApplication);
|
||||
}
|
||||
} catch (error) {
|
||||
feedbackToast({ error });
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const onReject = useCallback(
|
||||
async (application: FriendApplicationItem, isRecv: boolean) => {
|
||||
try {
|
||||
await IMSDK.refuseFriendApplication({
|
||||
toUserID: application.fromUserID,
|
||||
handleMsg: "",
|
||||
});
|
||||
const newApplication = {
|
||||
...application,
|
||||
handleResult: ApplicationHandleResult.Reject,
|
||||
};
|
||||
if (isRecv) {
|
||||
updateRecvFriendApplication(newApplication);
|
||||
} else {
|
||||
updateSendFriendApplication(newApplication);
|
||||
}
|
||||
} catch (error) {
|
||||
feedbackToast({ error });
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col bg-white">
|
||||
<p className="m-5.5 text-base font-extrabold">{t("placeholder.newFriends")}</p>
|
||||
<div className="flex-1 pb-3">
|
||||
<Virtuoso
|
||||
className="h-full overflow-x-hidden"
|
||||
data={friendApplicationList}
|
||||
itemContent={(_, item) => (
|
||||
<ApplicationItem
|
||||
key={`${
|
||||
currentUserID === item.fromUserID ? item.toUserID : item.fromUserID
|
||||
}${item.createTime}`}
|
||||
source={item}
|
||||
currentUserID={currentUserID}
|
||||
onAccept={onAccept as AccessFunction}
|
||||
onReject={onReject as AccessFunction}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const sortArray = (list: FriendApplicationItem[]) => {
|
||||
list.sort((a, b) => {
|
||||
if (a.handleResult === 0 && b.handleResult !== 0) {
|
||||
return -1;
|
||||
} else if (b.handleResult === 0 && a.handleResult !== 0) {
|
||||
return 1;
|
||||
}
|
||||
return b.createTime - a.createTime;
|
||||
});
|
||||
return list;
|
||||
};
|
||||
Reference in New Issue
Block a user