feat(games): fetch games from backend
This commit is contained in:
@@ -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"> = {
|
||||||
|
|||||||
@@ -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
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user