feat: add player profile cache & manual sync button
- Cache user profile data in Redis with 10-minute expiration in XcxAPI service to reduce API overhead. - Added a refresh/sync button on FavPlayersPage to manually trigger fetching players from the account. - Refactored authentication logic to properly set and use ID token claims for syncing. - Improved UX by removing automatic view switching logic that caused layout shifts, relying on state-driven rendering instead. - Unified login redirect flow using the new `useAutoLogin` hook.
This commit is contained in:
parent
06665f3371
commit
54d275796e
15
src/hooks/useAutoLogin.ts
Normal file
15
src/hooks/useAutoLogin.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { useCallback } from "react"
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
const useAutoLogin = () => {
|
||||
const navigate = useNavigate();
|
||||
const login = useCallback((redirect?: string) => {
|
||||
if (redirect) {
|
||||
sessionStorage.setItem('redirect', redirect);
|
||||
}
|
||||
navigate('/user-center?autoSignIn=true');
|
||||
}, []);
|
||||
return { login };
|
||||
}
|
||||
|
||||
export default useAutoLogin;
|
||||
@ -2,10 +2,11 @@ import { Avatar, Button, Card, Divider, Flex, Image, Popconfirm, Radio, Segmente
|
||||
import { useFavPlayerStore, type FavPlayer } from "../store/useFavPlayerStore";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useLogto } from "@logto/react";
|
||||
import { DeleteOutlined, LoginOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import { useLogto, type IdTokenClaims } from "@logto/react";
|
||||
import { DeleteOutlined, LoginOutlined, SyncOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import { useRequest } from "ahooks";
|
||||
import type { XCXProfile } from "../types";
|
||||
import useAutoLogin from "../hooks/useAutoLogin";
|
||||
|
||||
enum SortType {
|
||||
DEFAULT = '注册时间',
|
||||
@ -21,6 +22,8 @@ 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 { login } = useAutoLogin();
|
||||
const localList = Object.values(favMap);
|
||||
const favListRequest = useRequest<XCXProfile[], [string]>(async (aud: string) => {
|
||||
const res = await fetch(`/api/fav/${aud}`);
|
||||
@ -47,6 +50,7 @@ export function FavePlayersPage() {
|
||||
if (!isAuthenticated) return;
|
||||
const id = setTimeout(async () => {
|
||||
const claims = await getIdTokenClaims();
|
||||
setClaims(claims);
|
||||
favListRequest.runAsync(claims?.aud!);
|
||||
}, 300);
|
||||
return () => clearTimeout(id);
|
||||
@ -64,14 +68,11 @@ export function FavePlayersPage() {
|
||||
const handleClearLocal = useCallback(() => {
|
||||
list.forEach(e => unFav(e.uid));
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
setShowType(isAuthenticated ? ShowType.ACCOUNT : ShowType.LOCAL);
|
||||
}, [isAuthenticated]);
|
||||
return (
|
||||
<div className="app">
|
||||
<Flex vertical gap={48}>
|
||||
<Typography.Title>收藏的球员</Typography.Title>
|
||||
<div style={{ display: isAuthenticated ? 'block' : 'none' }}>
|
||||
<Flex justify="center" align="center" gap={12} style={{ display: isAuthenticated ? '' : 'none' }}>
|
||||
<Radio.Group
|
||||
optionType="button"
|
||||
value={showType}
|
||||
@ -81,7 +82,11 @@ export function FavePlayersPage() {
|
||||
{ label: `${ShowType.ACCOUNT}(${favListRequest.data?.length ?? 0})`, value: ShowType.ACCOUNT}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={async () => favListRequest.runAsync(claims?.aud!)}
|
||||
icon={<SyncOutlined spin={favListRequest.loading} />}
|
||||
/>
|
||||
</Flex>
|
||||
<div style={{ position: 'sticky', zIndex: 1 }}>
|
||||
<Segmented
|
||||
value={sortType}
|
||||
@ -93,7 +98,7 @@ export function FavePlayersPage() {
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<Spin spinning={favListRequest.loading}>
|
||||
<Spin spinning={showType === ShowType.ACCOUNT && favListRequest.loading}>
|
||||
{!favListRequest.loading && list.length === 0 ? (
|
||||
<>
|
||||
<Divider>暂无收藏的球员</Divider>
|
||||
@ -132,10 +137,7 @@ export function FavePlayersPage() {
|
||||
) }
|
||||
{ (showType === ShowType.LOCAL && !isAuthenticated && list.length > 0) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
sessionStorage.setItem('redirect', window.location.pathname);
|
||||
navigate('/user-center?autoSignIn=true');
|
||||
}}
|
||||
onClick={() => login(window.location.pathname)}
|
||||
icon={<LoginOutlined />}
|
||||
>登陆后可同步收藏球员</Button>
|
||||
) }
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { GamesData, XCXFindUserResp, XCXMember, XCXProfile, XCXTag } from "../types";
|
||||
import { BASE_URL } from "../utils/common";
|
||||
import { redis } from "../utils/server";
|
||||
|
||||
const XCX_BASE_URL = `${BASE_URL}/xcx/public/index.php`;
|
||||
|
||||
@ -33,8 +34,14 @@ export class XCXAPI {
|
||||
}
|
||||
|
||||
async getAdvProfile(uid: string) {
|
||||
const url = `/api/User/adv_profile?uid=${uid}`;
|
||||
return this.#fetch<XCXProfile>(url);
|
||||
const cacheProfile = await redis.get(`my-kaiqiuwang-profile:${uid}`);
|
||||
if (!cacheProfile) {
|
||||
const url = `/api/User/adv_profile?uid=${uid}`;
|
||||
const profile = await this.#fetch<XCXProfile>(url);
|
||||
await redis.setex(`my-kaiqiuwang-profile:${uid}`, 60 * 10, JSON.stringify(profile));
|
||||
return profile;
|
||||
}
|
||||
return JSON.parse(cacheProfile);
|
||||
}
|
||||
|
||||
async getPlayerTags(uid: string) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user