feat(fav-players): optimize un-fav API response and refactor player list UI
- Modify the DELETE /api/fav endpoint to include the player uid in the response JSON. - Refactor FavPlayersPage to remove explicit dependency on `aud` for API calls, relying solely on `useAuthHeaders`. - Add a dedicated "un-fav" button to player cards to allow users to unfollow individual players. - Implement logic to distinguish between local un-fav (for unauthenticated users) and server-side un-fav (for authenticated users). - Improve UI layout using `styled-components` and updated Ant Design components (`Typography.Title`). - Add a link to sign in and view cloud favorites when the local list is empty.
This commit is contained in:
parent
2d928ab1e3
commit
86c3b6651b
@ -172,7 +172,7 @@ const server = serve({
|
||||
async DELETE(req) {
|
||||
const payload = await verifyLogtoToken(req.headers);
|
||||
await unFavPlayer(`${payload.sub}`, req.params.uid);
|
||||
return Response.json({ ok: 'ok' });
|
||||
return Response.json({ ok: 'ok' , uid: req.params.uid });
|
||||
}
|
||||
},
|
||||
'/api/battle/:eventId': {
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { Avatar, Button, Card, Divider, Flex, Image, message as AntdMessage, Popconfirm, Radio, Segmented, Spin, Typography, App } from "antd";
|
||||
import { useFavPlayerStore } from "../store/useFavPlayerStore";
|
||||
import { useFavPlayerStore, type FavPlayer } from "../store/useFavPlayerStore";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useLogto, type IdTokenClaims } from "@logto/react";
|
||||
import { DeleteOutlined, LoginOutlined, SyncOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import { DeleteOutlined, LoginOutlined, ShopTwoTone, StarFilled, SyncOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import { useRequest } from "ahooks";
|
||||
import type { XCXProfile } from "../types";
|
||||
import useAutoLogin from "../hooks/useAutoLogin";
|
||||
import { useAuthHeaders } from "../hooks/useAuthHeaders";
|
||||
import styled from "styled-components";
|
||||
|
||||
enum SortType {
|
||||
DEFAULT = '注册时间',
|
||||
@ -19,15 +20,22 @@ enum ShowType {
|
||||
ACCOUNT = '账号收藏',
|
||||
}
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
.player-name {
|
||||
margin: 0;
|
||||
}
|
||||
.unfav-btn {
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
export function FavePlayersPage() {
|
||||
const { favMap, unFav } = useFavPlayerStore(state => state);
|
||||
const [sortType, setSortType] = useState<SortType>(SortType.DEFAULT);
|
||||
const [showType, setShowType] = useState(ShowType.LOCAL);
|
||||
const [claims, setClaims] = useState<IdTokenClaims>();
|
||||
const headers = useAuthHeaders();
|
||||
const { autoSignIn } = useAutoLogin();
|
||||
const localList = Object.values(favMap);
|
||||
const favListRequest = useRequest<XCXProfile[], [string]>(async (aud: string) => {
|
||||
const favListRequest = useRequest<XCXProfile[], []>(async () => {
|
||||
const res = await fetch(`/api/fav`, { headers });
|
||||
const data = await res.json();
|
||||
return data;
|
||||
@ -52,29 +60,34 @@ export function FavePlayersPage() {
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) return;
|
||||
const id = setTimeout(async () => {
|
||||
const claims = await getIdTokenClaims();
|
||||
setClaims(claims);
|
||||
favListRequest.runAsync(claims?.aud!);
|
||||
favListRequest.runAsync();
|
||||
}, 300);
|
||||
return () => clearTimeout(id);
|
||||
}, [isAuthenticated, getIdTokenClaims]);
|
||||
const handleSyncFav = useCallback(async () => {
|
||||
if (!isAuthenticated) return;
|
||||
const claims = await getIdTokenClaims()!;
|
||||
const aud = claims?.aud;
|
||||
const jobs = list.map(async u => {
|
||||
await fetch(`/api/fav/${u.uid}`, { method: 'PUT', headers });
|
||||
});
|
||||
message.open({ key: 'sync', content: '同步中...', type: 'loading' });
|
||||
await Promise.all(jobs);
|
||||
await favListRequest.runAsync(aud!);
|
||||
await favListRequest.runAsync();
|
||||
message.open({ key: 'sync', content: '已同步', type: 'success' });
|
||||
}, [isAuthenticated, list]);
|
||||
const handleClearLocal = useCallback(() => {
|
||||
list.forEach(e => unFav(e.uid));
|
||||
}, []);
|
||||
const handleUnFav = useCallback(async (e: FavPlayer) => {
|
||||
if (showType === ShowType.LOCAL) {
|
||||
unFav(e.uid);
|
||||
}
|
||||
if (showType === ShowType.ACCOUNT) {
|
||||
await fetch(`/api/fav/${e.uid}`, { method: 'DELETE', headers });
|
||||
await favListRequest.runAsync();
|
||||
}
|
||||
}, [showType, favListRequest, headers]);
|
||||
return (
|
||||
<div className="app">
|
||||
<StyledContainer className="app">
|
||||
<Flex vertical gap={48}>
|
||||
<Typography.Title>收藏的球员</Typography.Title>
|
||||
<Flex justify="center" align="center" gap={12} style={{ display: isAuthenticated ? '' : 'none' }}>
|
||||
@ -88,7 +101,7 @@ export function FavePlayersPage() {
|
||||
]}
|
||||
/>
|
||||
<Button
|
||||
onClick={async () => favListRequest.runAsync(claims?.aud!)}
|
||||
onClick={async () => favListRequest.runAsync()}
|
||||
icon={<SyncOutlined spin={favListRequest.loading} />}
|
||||
/>
|
||||
</Flex>
|
||||
@ -107,21 +120,25 @@ export function FavePlayersPage() {
|
||||
{!favListRequest.loading && list.length === 0 ? (
|
||||
<>
|
||||
<Divider>暂无收藏的球员</Divider>
|
||||
{showType === ShowType.LOCAL && (
|
||||
<Button type='link' onClick={() => autoSignIn()}>登录后查看云端收藏</Button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Flex wrap gap={16} align="center" justify="center">
|
||||
{list.map(e => (
|
||||
<Card
|
||||
hoverable
|
||||
size="small"
|
||||
key={e.uid}
|
||||
style={{ width: 240 }}
|
||||
style={{ minWidth: 280 }}
|
||||
>
|
||||
<Flex gap={12}>
|
||||
{e.avatar?.includes('noavatar')
|
||||
? <Avatar size={64} shape="square">{e.name}</Avatar>
|
||||
: <Image width={64} height={64} src={e.avatar} style={{ objectFit: 'cover' }} />}
|
||||
<Flex vertical align="center" justify="center">
|
||||
{e.avatar?.includes('noavatar')
|
||||
? <Avatar size={64} shape="square">{e.name}</Avatar>
|
||||
: <Image width={64} height={64} src={e.avatar} style={{ objectFit: 'cover' }} />}
|
||||
</Flex>
|
||||
<Flex
|
||||
vertical
|
||||
justify="center"
|
||||
@ -129,9 +146,14 @@ export function FavePlayersPage() {
|
||||
style={{ flex: 1 }}
|
||||
onClick={() => navigate(`/profile/${e.uid}`)}
|
||||
>
|
||||
<h2 style={{ margin: 0 }}>{e.name}</h2>
|
||||
<Typography.Title level={3} className="player-name">{e.name}</Typography.Title>
|
||||
<Typography.Text type="secondary">{e.realname}</Typography.Text>
|
||||
</Flex>
|
||||
<Flex align="center" justify="center">
|
||||
<Button className="unfav-btn" type="link" onClick={() => handleUnFav(e)}>
|
||||
取关
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
))}
|
||||
@ -162,6 +184,6 @@ export function FavePlayersPage() {
|
||||
)}
|
||||
</Spin>
|
||||
</Flex>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user