From 505d9c0168565fadf7e2c64c3a1198899ffe3163 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sun, 1 Mar 2026 22:36:50 +0800 Subject: [PATCH] feat(favorites): migrate to backend API --- app/(main)/user/[id]/page.tsx | 96 +++------------------------------- components/favorite-button.tsx | 72 ++++++++++++++++++++++--- lib/api/favorites.ts | 63 +++++++++++++++++----- 3 files changed, 120 insertions(+), 111 deletions(-) diff --git a/app/(main)/user/[id]/page.tsx b/app/(main)/user/[id]/page.tsx index 2404c58..6a389f7 100644 --- a/app/(main)/user/[id]/page.tsx +++ b/app/(main)/user/[id]/page.tsx @@ -2,14 +2,8 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { - getUserById, - listFavoritesByUser, - listPlayers, - listPostsByAuthor, - listShops, -} from "@/lib/api" -import { MessageSquare, Star, ThumbsUp } from "lucide-react" +import { getUserById, listPostsByAuthor } from "@/lib/api" +import { MessageSquare, ThumbsUp } from "lucide-react" import Link from "next/link" import { notFound } from "next/navigation" @@ -21,20 +15,8 @@ export default async function UserProfilePage({ params }: { params: Promise<{ id notFound() } - const [userPosts, userFavorites, players, shops] = await Promise.all([ - listPostsByAuthor(id), - listFavoritesByUser(user.id), - listPlayers(), - listShops(), - ]) - const favoritePlayers = userFavorites - .filter((f) => f.targetType === "player") - .map((f) => players.find((p) => p.id === f.targetId)) - .filter((p): p is NonNullable => p != null) - const favoriteShops = userFavorites - .filter((f) => f.targetType === "shop") - .map((f) => shops.find((s) => s.id === f.targetId)) - .filter((s): s is NonNullable => s != null) + const userPosts = await listPostsByAuthor(id) + const favoriteCountText = "—" return (
@@ -48,7 +30,7 @@ export default async function UserProfilePage({ params }: { params: Promise<{ id

{user.bio || "这个人很懒,什么都没写~"}

{userPosts.length} 帖子 - {userFavorites.length} 收藏 + {favoriteCountText} 收藏
@@ -95,73 +77,7 @@ export default async function UserProfilePage({ params }: { params: Promise<{ id - {favoritePlayers.length > 0 && ( -
-

收藏的打手

-
- {favoritePlayers.map((player) => ( - - - - - - {player.user.nickname[0]} - -
-

{player.user.nickname}

-
- - {player.rating} - · - {player.totalOrders}单 -
-
- - {player.status === "available" ? "可接单" : "忙碌"} - -
-
- - ))} -
-
- )} - - {favoriteShops.length > 0 && ( -
-

收藏的店铺

-
- {favoriteShops.map((shop) => ( - - - - - - {shop.name[0]} - -
-

{shop.name}

-
- - {shop.rating} - · - {shop.totalOrders}单 -
-
-
-
- - ))} -
-
- )} - - {favoritePlayers.length === 0 && favoriteShops.length === 0 && ( -
暂无收藏
- )} +
暂不支持查看用户收藏
diff --git a/components/favorite-button.tsx b/components/favorite-button.tsx index 1d8ad09..c7761c7 100644 --- a/components/favorite-button.tsx +++ b/components/favorite-button.tsx @@ -1,10 +1,13 @@ "use client" import { Button } from "@/components/ui/button" +import { addFavorite, isFavorited, listFavorites, removeFavorite } from "@/lib/api/favorites" +import { toApiError } from "@/lib/errors" +import { notifyInfo } from "@/lib/toast" import { useRequireAuth } from "@/lib/use-require-auth" import { useAuthStore } from "@/store/auth" -import { useFavoriteStore } from "@/store/favorites" import { Heart } from "lucide-react" +import { useEffect, useState } from "react" interface FavoriteButtonProps { initialFavorited: boolean @@ -15,21 +18,76 @@ interface FavoriteButtonProps { export function FavoriteButton({ initialFavorited, targetType, targetId }: FavoriteButtonProps) { const { requireAuth } = useRequireAuth() const userId = useAuthStore((s) => s.user?.id) - const favoritedInStore = useFavoriteStore((state) => - userId ? state.isFavorited(userId, targetType, targetId) : false, - ) - const toggleFavorite = useFavoriteStore((state) => state.toggleFavorite) - const favorited = userId ? favoritedInStore : initialFavorited + const [favorited, setFavorited] = useState(initialFavorited) + const [pending, setPending] = useState(false) + + useEffect(() => { + let cancelled = false + + if (!userId) { + void Promise.resolve().then(() => { + if (cancelled) return + setFavorited(initialFavorited) + }) + return () => { + cancelled = true + } + } + + isFavorited(userId, targetType, targetId) + .then((value) => { + if (cancelled) return + setFavorited(value) + }) + .catch((err: unknown) => { + if (cancelled) return + if (err instanceof Error && err.message === "UNAUTHORIZED") return + notifyInfo(toApiError(err).msg) + }) + + return () => { + cancelled = true + } + }, [initialFavorited, targetId, targetType, userId]) return (