Skip to main content
Glama
page.tsx5.71 kB
"use client" import { Ranking } from "@repo/db/types" import { Button } from "@repo/ui/components/ui/button" import { Input } from "@repo/ui/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@repo/ui/components/ui/select" import { Skeleton } from "@repo/ui/components/ui/skeleton" import { Plus, RefreshCw,Search } from "lucide-react" import { useState } from "react" import { DataTable } from "@/components/admin/data-table" import { trpc } from "@/lib/trpc/client" import { columns } from "./components/columns" import { CreateRankingDialog } from "./components/create-ranking-dialog" export type RankingSource = "github" | "openmcp" | "producthunt" export type RankingType = "daily" | "weekly" | "monthly" | "yearly" export default function RankingsPage() { const [open, setOpen] = useState(false) const [searchParams, setSearchParams] = useState({ query: "", source: "all" as "all" | RankingSource, type: "all" as "all" | RankingType, status: "all" as "all" | boolean, page: 1, limit: 10, }) const { data, isLoading, refetch } = trpc.rankings.search.useQuery({ query: searchParams.query, source: searchParams.source === "all" ? undefined : searchParams.source, page: searchParams.page, limit: searchParams.limit, }) const rankings = (data?.data || []).map(item => ({ ...item, recordsCount: item.recordsCount || 0, })) as Ranking[] const handleSearch = () => { setSearchParams(prev => ({ ...prev, page: 1 })) refetch() } const handleReset = () => { setSearchParams({ query: "", source: "all", type: "all", status: "all", page: 1, limit: 10, }) refetch() } if (isLoading) { return <TableSkeleton /> } return ( <div className="container mx-auto py-6 space-y-6"> <div className="flex items-center justify-between"> <div> <h1 className="text-3xl font-bold tracking-tight">排行榜管理</h1> <p className="text-muted-foreground">管理来自 GitHub、OpenMCP 和 ProductHunt 的排行榜数据</p> </div> <Button onClick={() => setOpen(true)} className="gap-2"> <Plus className="h-4 w-4" /> 创建排行榜 </Button> <CreateRankingDialog open={open} onOpenChange={setOpen} /> </div> <div className="flex flex-col sm:flex-row items-center space-y-2 sm:space-y-0 sm:space-x-2"> <Input placeholder="搜索排行榜名称或描述" value={searchParams.query} onChange={(e) => setSearchParams(prev => ({ ...prev, query: e.target.value }))} className="max-w-sm" /> <Select value={searchParams.source} onValueChange={(value: "all" | RankingSource) => setSearchParams(prev => ({ ...prev, source: value })) } > <SelectTrigger className="w-[180px]"> <SelectValue placeholder="选择来源" /> </SelectTrigger> <SelectContent> <SelectItem value="all">全部来源</SelectItem> <SelectItem value="github">GitHub</SelectItem> <SelectItem value="openmcp">OpenMCP</SelectItem> <SelectItem value="producthunt">ProductHunt</SelectItem> </SelectContent> </Select> <Select value={searchParams.type} onValueChange={(value: "all" | RankingType) => setSearchParams(prev => ({ ...prev, type: value })) } > <SelectTrigger className="w-[180px]"> <SelectValue placeholder="选择类型" /> </SelectTrigger> <SelectContent> <SelectItem value="all">全部类型</SelectItem> <SelectItem value="daily">每日</SelectItem> <SelectItem value="weekly">每周</SelectItem> <SelectItem value="monthly">每月</SelectItem> <SelectItem value="yearly">每年</SelectItem> </SelectContent> </Select> <Select value={String(searchParams.status)} onValueChange={(value) => setSearchParams(prev => ({ ...prev, status: value === "all" ? "all" : value === "true", })) } > <SelectTrigger className="w-[180px]"> <SelectValue placeholder="选择状态" /> </SelectTrigger> <SelectContent> <SelectItem value="all">全部状态</SelectItem> <SelectItem value="true">启用</SelectItem> <SelectItem value="false">禁用</SelectItem> </SelectContent> </Select> <Button onClick={handleSearch}> <Search className="mr-2 h-4 w-4" /> 搜索 </Button> <Button variant="outline" onClick={handleReset}> <RefreshCw className="mr-2 h-4 w-4" /> 重置 </Button> </div> <DataTable data={rankings} columns={columns} loading={isLoading} pagination={{ page: searchParams.page, limit: searchParams.limit, total: data?.pagination.total || 0, onPageChange: (page: number) => setSearchParams(prev => ({ ...prev, page })), }} /> </div> ) } function TableSkeleton() { return ( <div className="space-y-4"> <div className="flex items-center justify-between"> <Skeleton className="h-10 w-[250px]" /> <Skeleton className="h-10 w-[200px]" /> </div> <div className="border rounded-lg"> <Skeleton className="h-[500px] w-full" /> </div> </div> ) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/metacode0602/open-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server