diff --git a/app/(dashboard)/dashboard/services/new/page.tsx b/app/(dashboard)/dashboard/services/new/page.tsx index 63becf1..012272e 100644 --- a/app/(dashboard)/dashboard/services/new/page.tsx +++ b/app/(dashboard)/dashboard/services/new/page.tsx @@ -15,7 +15,7 @@ import { Textarea } from "@/components/ui/textarea" import { getGameById, listGames } from "@/lib/api" import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" import { GameIcon } from "@/lib/game-icons" -import type { PlayerService } from "@/lib/types" +import type { Game, PlayerService } from "@/lib/types" import { useAuthStore } from "@/store/auth" import { usePlayerStore } from "@/store/players" import { useServiceStore } from "@/store/services" @@ -24,7 +24,7 @@ import { standardSchemaResolver } from "@hookform/resolvers/standard-schema" import { ArrowLeft } from "lucide-react" import Link from "next/link" import { useRouter, useSearchParams } from "next/navigation" -import { useEffect } from "react" +import { useEffect, useState } from "react" import { useForm, useWatch } from "react-hook-form" import { z } from "zod" @@ -97,7 +97,25 @@ export default function NewServicePage() { const selectedGameId = useWatch({ control, name: "gameId" }) const selectedUnit = useWatch({ control, name: "unit" }) - const games = listGames() + const [games, setGames] = useState([]) + + useEffect(() => { + let cancelled = false + + listGames() + .then((items) => { + if (cancelled) return + setGames(items) + }) + .catch(() => { + if (cancelled) return + setGames([]) + }) + + return () => { + cancelled = true + } + }, []) if (serviceId && !editingService) { return
服务不存在或当前身份不可编辑
@@ -108,7 +126,7 @@ export default function NewServicePage() { } const onSubmit = async (data: z.infer) => { - const game = getGameById(data.gameId) + const game = await getGameById(data.gameId) if (!game) return const payload: Omit = { diff --git a/app/(main)/community/page.tsx b/app/(main)/community/page.tsx index 7592347..cc580ee 100644 --- a/app/(main)/community/page.tsx +++ b/app/(main)/community/page.tsx @@ -6,12 +6,13 @@ import { Button } from "@/components/ui/button" import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card" import { listGames, listOrders, listPlayers, listPosts } from "@/lib/api" import { roleLabels } from "@/lib/constants" +import type { Game } from "@/lib/types" import { ClipboardList, Heart, MessageCircle, PenSquare, Pin } from "lucide-react" import Link from "next/link" -import { useState } from "react" +import { useEffect, useState } from "react" export default function CommunityPage() { - const games = listGames() + const [games, setGames] = useState([]) const posts = listPosts() const orders = listOrders() const players = listPlayers() @@ -19,6 +20,24 @@ export default function CommunityPage() { const [sortMode, setSortMode] = useState<"latest" | "hot">("latest") const [selectedGame, setSelectedGame] = useState(null) + useEffect(() => { + let cancelled = false + + listGames() + .then((items) => { + if (cancelled) return + setGames(items) + }) + .catch(() => { + if (cancelled) return + setGames([]) + }) + + return () => { + cancelled = true + } + }, []) + const filteredPosts = posts .filter((post) => { if (!selectedGame) return true diff --git a/app/(main)/page.tsx b/app/(main)/page.tsx index 4e7d274..01a8c57 100644 --- a/app/(main)/page.tsx +++ b/app/(main)/page.tsx @@ -8,8 +8,8 @@ import { GameIcon } from "@/lib/game-icons" import { ArrowRight, Search, ShoppingBag, Star } from "lucide-react" import Link from "next/link" -export default function HomePage() { - const games = listGames() +export default async function HomePage() { + const games = await listGames() const players = listPlayers() const shops = listShops() diff --git a/app/(main)/search/page.tsx b/app/(main)/search/page.tsx index 4fd51f1..453263a 100644 --- a/app/(main)/search/page.tsx +++ b/app/(main)/search/page.tsx @@ -374,7 +374,7 @@ function FilterSection({ function SearchPageContent() { const searchParams = useSearchParams() const router = useRouter() - const games = listGames() + const [games, setGames] = useState([]) const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "") const [selectedGames, setSelectedGames] = useState(() => { @@ -396,6 +396,24 @@ function SearchPageContent() { const [hasLoaded, setHasLoaded] = useState(false) const [error, setError] = useState(null) + useEffect(() => { + let cancelled = false + + listGames() + .then((items) => { + if (cancelled) return + setGames(items) + }) + .catch(() => { + if (cancelled) return + setGames([]) + }) + + return () => { + cancelled = true + } + }, []) + const replaceUrl = useCallback( (updater: (params: URLSearchParams) => void) => { const params = new URLSearchParams(searchParams.toString()) diff --git a/lib/api/games.ts b/lib/api/games.ts index 0eb5edb..80e9edf 100644 --- a/lib/api/games.ts +++ b/lib/api/games.ts @@ -1,9 +1,36 @@ -import { mockGames } from "@/lib/mock" +import { isApiError } from "@/lib/errors" +import type { Game } from "@/lib/types" -export function listGames() { - return mockGames +import { httpJson } from "./http" + +type Paginated = { + items: T[] + meta: { + total: number + offset: number + limit: number + } } -export function getGameById(gameId: string) { - return mockGames.find((game) => game.id === gameId) +export async function listGames(): Promise { + const res = await httpJson>("/api/v1/games?offset=0&limit=1000", { + cache: "no-store", + }) + return res.items +} + +export async function getGameById(gameId: string): Promise { + try { + return await httpJson(`/api/v1/games/${encodeURIComponent(gameId)}`, { + cache: "no-store", + }) + } catch (error) { + if (error instanceof Error && error.message === "UNAUTHORIZED") { + throw error + } + if (isApiError(error) && error.code === 404) { + return undefined + } + throw error + } }