feat: sync favorite player state with server and improve clear confirmation

- Add `isFav` selector to `useFavPlayerStore` to determine initial button state immediately.
- Initialize `FavButton` value based on the store's `isFav` result instead of a stale local state.
- In `FavePlayersPage`, integrate Ant Design message hooks for user feedback during server sync.
  - Show "syncing" loading indicator when starting the sync process.
  - Show "synced" success notification upon completion.
- Enhance the "Clear Local Favorites" confirmation dialog:
  - Update warning text to indicate the action is irreversible.
  - Add custom button labels ("清空", "取消").
  - Make the confirm button dangerous and add a delete icon for better UX.
This commit is contained in:
kyuuseiryuu 2026-03-11 11:51:30 +09:00
parent 62e9053c79
commit 9044073afd
2 changed files with 15 additions and 5 deletions

View File

@ -22,7 +22,7 @@ const StyledContainer = styled.div`
export function FavButton(props: Props) { export function FavButton(props: Props) {
const { isAuthenticated, getIdTokenClaims } = useLogto(); const { isAuthenticated, getIdTokenClaims } = useLogto();
const { fav, unFav } = useFavPlayerStore(store => store); const { fav, unFav, isFav } = useFavPlayerStore(store => store);
const favReq = useRequest(async () => { const favReq = useRequest(async () => {
if (!isAuthenticated) return; if (!isAuthenticated) return;
const claims = await getIdTokenClaims(); const claims = await getIdTokenClaims();
@ -33,7 +33,7 @@ export function FavButton(props: Props) {
const claims = await getIdTokenClaims(); const claims = await getIdTokenClaims();
await fetch(`/api/fav/${claims?.aud}/${props.user?.uid}`, { method: 'DELETE' }); await fetch(`/api/fav/${claims?.aud}/${props.user?.uid}`, { method: 'DELETE' });
}, { manual: true, refreshDeps: [props, isAuthenticated] }); }, { manual: true, refreshDeps: [props, isAuthenticated] });
const [value, setValue] = useState(0); const [value, setValue] = useState(isFav(props.user?.uid) ? 1 : 0);
useEffect(() => { useEffect(() => {
if (!props.user) return; if (!props.user) return;
if (!isAuthenticated) return; if (!isAuthenticated) return;

View File

@ -1,5 +1,5 @@
import { Avatar, Button, Card, Divider, Flex, Image, Popconfirm, Radio, Segmented, Spin, Typography } from "antd"; import { Avatar, Button, Card, Divider, Flex, Image, message as AntdMessage, Popconfirm, Radio, Segmented, Spin, Typography } from "antd";
import { useFavPlayerStore, type FavPlayer } from "../store/useFavPlayerStore"; import { useFavPlayerStore } from "../store/useFavPlayerStore";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { useLogto, type IdTokenClaims } from "@logto/react"; import { useLogto, type IdTokenClaims } from "@logto/react";
@ -19,6 +19,7 @@ enum ShowType {
} }
export function FavePlayersPage() { export function FavePlayersPage() {
const [message, contextHolder] = AntdMessage.useMessage();
const { favMap, unFav } = useFavPlayerStore(state => state); const { favMap, unFav } = useFavPlayerStore(state => state);
const [sortType, setSortType] = useState<SortType>(SortType.DEFAULT); const [sortType, setSortType] = useState<SortType>(SortType.DEFAULT);
const [showType, setShowType] = useState(ShowType.LOCAL); const [showType, setShowType] = useState(ShowType.LOCAL);
@ -62,14 +63,17 @@ export function FavePlayersPage() {
const jobs = list.map(async u => { const jobs = list.map(async u => {
await fetch(`/api/fav/${aud}/${u.uid}`, { method: 'PUT' }); await fetch(`/api/fav/${aud}/${u.uid}`, { method: 'PUT' });
}); });
message.open({ key: 'sync', content: '同步中...', type: 'loading' });
await Promise.all(jobs); await Promise.all(jobs);
await favListRequest.runAsync(aud!); await favListRequest.runAsync(aud!);
message.open({ key: 'sync', content: '已同步', type: 'success' });
}, [isAuthenticated, list]); }, [isAuthenticated, list]);
const handleClearLocal = useCallback(() => { const handleClearLocal = useCallback(() => {
list.forEach(e => unFav(e.uid)); list.forEach(e => unFav(e.uid));
}, []); }, []);
return ( return (
<div className="app"> <div className="app">
{contextHolder}
<Flex vertical gap={48}> <Flex vertical gap={48}>
<Typography.Title></Typography.Title> <Typography.Title></Typography.Title>
<Flex justify="center" align="center" gap={12} style={{ display: isAuthenticated ? '' : 'none' }}> <Flex justify="center" align="center" gap={12} style={{ display: isAuthenticated ? '' : 'none' }}>
@ -142,7 +146,13 @@ export function FavePlayersPage() {
></Button> ></Button>
) } ) }
{(showType === ShowType.LOCAL && ( {(showType === ShowType.LOCAL && (
<Popconfirm title='确定要清空本地收藏吗?' onConfirm={handleClearLocal}> <Popconfirm
title='确定要清空本地收藏吗?该操作不可恢复。'
onConfirm={handleClearLocal}
okText='清空'
cancelText='取消'
okButtonProps={{ danger: true, icon: <DeleteOutlined /> }}
>
<Button danger icon={<DeleteOutlined />}></Button> <Button danger icon={<DeleteOutlined />}></Button>
</Popconfirm> </Popconfirm>
))} ))}