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 { useFavPlayerStore, type FavPlayer } 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 } from "@logto/react";
|
import { useLogto, type IdTokenClaims } from "@logto/react";
|
||||||
import { DeleteOutlined, LoginOutlined, UploadOutlined } from "@ant-design/icons";
|
import { DeleteOutlined, LoginOutlined, SyncOutlined, UploadOutlined } from "@ant-design/icons";
|
||||||
import { useRequest } from "ahooks";
|
import { useRequest } from "ahooks";
|
||||||
import type { XCXProfile } from "../types";
|
import type { XCXProfile } from "../types";
|
||||||
|
import useAutoLogin from "../hooks/useAutoLogin";
|
||||||
|
|
||||||
enum SortType {
|
enum SortType {
|
||||||
DEFAULT = '注册时间',
|
DEFAULT = '注册时间',
|
||||||
@ -21,6 +22,8 @@ export function FavePlayersPage() {
|
|||||||
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);
|
||||||
|
const [claims, setClaims] = useState<IdTokenClaims>();
|
||||||
|
const { login } = useAutoLogin();
|
||||||
const localList = Object.values(favMap);
|
const localList = Object.values(favMap);
|
||||||
const favListRequest = useRequest<XCXProfile[], [string]>(async (aud: string) => {
|
const favListRequest = useRequest<XCXProfile[], [string]>(async (aud: string) => {
|
||||||
const res = await fetch(`/api/fav/${aud}`);
|
const res = await fetch(`/api/fav/${aud}`);
|
||||||
@ -47,6 +50,7 @@ export function FavePlayersPage() {
|
|||||||
if (!isAuthenticated) return;
|
if (!isAuthenticated) return;
|
||||||
const id = setTimeout(async () => {
|
const id = setTimeout(async () => {
|
||||||
const claims = await getIdTokenClaims();
|
const claims = await getIdTokenClaims();
|
||||||
|
setClaims(claims);
|
||||||
favListRequest.runAsync(claims?.aud!);
|
favListRequest.runAsync(claims?.aud!);
|
||||||
}, 300);
|
}, 300);
|
||||||
return () => clearTimeout(id);
|
return () => clearTimeout(id);
|
||||||
@ -64,14 +68,11 @@ export function FavePlayersPage() {
|
|||||||
const handleClearLocal = useCallback(() => {
|
const handleClearLocal = useCallback(() => {
|
||||||
list.forEach(e => unFav(e.uid));
|
list.forEach(e => unFav(e.uid));
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
|
||||||
setShowType(isAuthenticated ? ShowType.ACCOUNT : ShowType.LOCAL);
|
|
||||||
}, [isAuthenticated]);
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<Flex vertical gap={48}>
|
<Flex vertical gap={48}>
|
||||||
<Typography.Title>收藏的球员</Typography.Title>
|
<Typography.Title>收藏的球员</Typography.Title>
|
||||||
<div style={{ display: isAuthenticated ? 'block' : 'none' }}>
|
<Flex justify="center" align="center" gap={12} style={{ display: isAuthenticated ? '' : 'none' }}>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
optionType="button"
|
optionType="button"
|
||||||
value={showType}
|
value={showType}
|
||||||
@ -81,7 +82,11 @@ export function FavePlayersPage() {
|
|||||||
{ label: `${ShowType.ACCOUNT}(${favListRequest.data?.length ?? 0})`, value: ShowType.ACCOUNT}
|
{ 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 }}>
|
<div style={{ position: 'sticky', zIndex: 1 }}>
|
||||||
<Segmented
|
<Segmented
|
||||||
value={sortType}
|
value={sortType}
|
||||||
@ -93,7 +98,7 @@ export function FavePlayersPage() {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Spin spinning={favListRequest.loading}>
|
<Spin spinning={showType === ShowType.ACCOUNT && favListRequest.loading}>
|
||||||
{!favListRequest.loading && list.length === 0 ? (
|
{!favListRequest.loading && list.length === 0 ? (
|
||||||
<>
|
<>
|
||||||
<Divider>暂无收藏的球员</Divider>
|
<Divider>暂无收藏的球员</Divider>
|
||||||
@ -132,10 +137,7 @@ export function FavePlayersPage() {
|
|||||||
) }
|
) }
|
||||||
{ (showType === ShowType.LOCAL && !isAuthenticated && list.length > 0) && (
|
{ (showType === ShowType.LOCAL && !isAuthenticated && list.length > 0) && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => login(window.location.pathname)}
|
||||||
sessionStorage.setItem('redirect', window.location.pathname);
|
|
||||||
navigate('/user-center?autoSignIn=true');
|
|
||||||
}}
|
|
||||||
icon={<LoginOutlined />}
|
icon={<LoginOutlined />}
|
||||||
>登陆后可同步收藏球员</Button>
|
>登陆后可同步收藏球员</Button>
|
||||||
) }
|
) }
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { GamesData, XCXFindUserResp, XCXMember, XCXProfile, XCXTag } from "../types";
|
import type { GamesData, XCXFindUserResp, XCXMember, XCXProfile, XCXTag } from "../types";
|
||||||
import { BASE_URL } from "../utils/common";
|
import { BASE_URL } from "../utils/common";
|
||||||
|
import { redis } from "../utils/server";
|
||||||
|
|
||||||
const XCX_BASE_URL = `${BASE_URL}/xcx/public/index.php`;
|
const XCX_BASE_URL = `${BASE_URL}/xcx/public/index.php`;
|
||||||
|
|
||||||
@ -33,8 +34,14 @@ export class XCXAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getAdvProfile(uid: string) {
|
async getAdvProfile(uid: string) {
|
||||||
|
const cacheProfile = await redis.get(`my-kaiqiuwang-profile:${uid}`);
|
||||||
|
if (!cacheProfile) {
|
||||||
const url = `/api/User/adv_profile?uid=${uid}`;
|
const url = `/api/User/adv_profile?uid=${uid}`;
|
||||||
return this.#fetch<XCXProfile>(url);
|
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) {
|
async getPlayerTags(uid: string) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user