feat(games): fetch games from backend

This commit is contained in:
zetaloop
2026-02-28 16:23:30 +08:00
parent 6dd21e1090
commit f4365668ab
5 changed files with 96 additions and 14 deletions
@@ -15,7 +15,7 @@ import { Textarea } from "@/components/ui/textarea"
import { getGameById, listGames } from "@/lib/api" import { getGameById, listGames } from "@/lib/api"
import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop"
import { GameIcon } from "@/lib/game-icons" 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 { useAuthStore } from "@/store/auth"
import { usePlayerStore } from "@/store/players" import { usePlayerStore } from "@/store/players"
import { useServiceStore } from "@/store/services" import { useServiceStore } from "@/store/services"
@@ -24,7 +24,7 @@ import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"
import { ArrowLeft } from "lucide-react" import { ArrowLeft } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation" import { useRouter, useSearchParams } from "next/navigation"
import { useEffect } from "react" import { useEffect, useState } from "react"
import { useForm, useWatch } from "react-hook-form" import { useForm, useWatch } from "react-hook-form"
import { z } from "zod" import { z } from "zod"
@@ -97,7 +97,25 @@ export default function NewServicePage() {
const selectedGameId = useWatch({ control, name: "gameId" }) const selectedGameId = useWatch({ control, name: "gameId" })
const selectedUnit = useWatch({ control, name: "unit" }) const selectedUnit = useWatch({ control, name: "unit" })
const games = listGames() const [games, setGames] = useState<Game[]>([])
useEffect(() => {
let cancelled = false
listGames()
.then((items) => {
if (cancelled) return
setGames(items)
})
.catch(() => {
if (cancelled) return
setGames([])
})
return () => {
cancelled = true
}
}, [])
if (serviceId && !editingService) { if (serviceId && !editingService) {
return <div className="text-sm text-muted-foreground"></div> return <div className="text-sm text-muted-foreground"></div>
@@ -108,7 +126,7 @@ export default function NewServicePage() {
} }
const onSubmit = async (data: z.infer<typeof serviceSchema>) => { const onSubmit = async (data: z.infer<typeof serviceSchema>) => {
const game = getGameById(data.gameId) const game = await getGameById(data.gameId)
if (!game) return if (!game) return
const payload: Omit<PlayerService, "id"> = { const payload: Omit<PlayerService, "id"> = {
+21 -2
View File
@@ -6,12 +6,13 @@ import { Button } from "@/components/ui/button"
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card" import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
import { listGames, listOrders, listPlayers, listPosts } from "@/lib/api" import { listGames, listOrders, listPlayers, listPosts } from "@/lib/api"
import { roleLabels } from "@/lib/constants" import { roleLabels } from "@/lib/constants"
import type { Game } from "@/lib/types"
import { ClipboardList, Heart, MessageCircle, PenSquare, Pin } from "lucide-react" import { ClipboardList, Heart, MessageCircle, PenSquare, Pin } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { useState } from "react" import { useEffect, useState } from "react"
export default function CommunityPage() { export default function CommunityPage() {
const games = listGames() const [games, setGames] = useState<Game[]>([])
const posts = listPosts() const posts = listPosts()
const orders = listOrders() const orders = listOrders()
const players = listPlayers() const players = listPlayers()
@@ -19,6 +20,24 @@ export default function CommunityPage() {
const [sortMode, setSortMode] = useState<"latest" | "hot">("latest") const [sortMode, setSortMode] = useState<"latest" | "hot">("latest")
const [selectedGame, setSelectedGame] = useState<string | null>(null) const [selectedGame, setSelectedGame] = useState<string | null>(null)
useEffect(() => {
let cancelled = false
listGames()
.then((items) => {
if (cancelled) return
setGames(items)
})
.catch(() => {
if (cancelled) return
setGames([])
})
return () => {
cancelled = true
}
}, [])
const filteredPosts = posts const filteredPosts = posts
.filter((post) => { .filter((post) => {
if (!selectedGame) return true if (!selectedGame) return true
+2 -2
View File
@@ -8,8 +8,8 @@ import { GameIcon } from "@/lib/game-icons"
import { ArrowRight, Search, ShoppingBag, Star } from "lucide-react" import { ArrowRight, Search, ShoppingBag, Star } from "lucide-react"
import Link from "next/link" import Link from "next/link"
export default function HomePage() { export default async function HomePage() {
const games = listGames() const games = await listGames()
const players = listPlayers() const players = listPlayers()
const shops = listShops() const shops = listShops()
+19 -1
View File
@@ -374,7 +374,7 @@ function FilterSection({
function SearchPageContent() { function SearchPageContent() {
const searchParams = useSearchParams() const searchParams = useSearchParams()
const router = useRouter() const router = useRouter()
const games = listGames() const [games, setGames] = useState<Game[]>([])
const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "") const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "")
const [selectedGames, setSelectedGames] = useState<string[]>(() => { const [selectedGames, setSelectedGames] = useState<string[]>(() => {
@@ -396,6 +396,24 @@ function SearchPageContent() {
const [hasLoaded, setHasLoaded] = useState(false) const [hasLoaded, setHasLoaded] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
useEffect(() => {
let cancelled = false
listGames()
.then((items) => {
if (cancelled) return
setGames(items)
})
.catch(() => {
if (cancelled) return
setGames([])
})
return () => {
cancelled = true
}
}, [])
const replaceUrl = useCallback( const replaceUrl = useCallback(
(updater: (params: URLSearchParams) => void) => { (updater: (params: URLSearchParams) => void) => {
const params = new URLSearchParams(searchParams.toString()) const params = new URLSearchParams(searchParams.toString())
+32 -5
View File
@@ -1,9 +1,36 @@
import { mockGames } from "@/lib/mock" import { isApiError } from "@/lib/errors"
import type { Game } from "@/lib/types"
export function listGames() { import { httpJson } from "./http"
return mockGames
type Paginated<T> = {
items: T[]
meta: {
total: number
offset: number
limit: number
}
} }
export function getGameById(gameId: string) { export async function listGames(): Promise<Game[]> {
return mockGames.find((game) => game.id === gameId) const res = await httpJson<Paginated<Game>>("/api/v1/games?offset=0&limit=1000", {
cache: "no-store",
})
return res.items
}
export async function getGameById(gameId: string): Promise<Game | undefined> {
try {
return await httpJson<Game>(`/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
}
} }