init: 毛孩子计划 小程序 + Go 后端 + 内嵌后台
- pets-fe: 微信原生小程序(首页/计划/记录/报告/社区/引导), 服务端驱动、无假数据;弹层改用 scroll-view,打开时隐藏自定义 tabBar - pets-be: Gin + GORM(MySQL, sundynix_ 前缀) + MinIO,统一响应/分页, 微信 code2session 登录,provider-neutral AI(DeepSeek),go:embed React 后台 - 修复:分段选择类型不匹配(字符串 vs 数字)导致选不中 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import { useState } from 'react'
|
||||
import { api } from '@/lib/api'
|
||||
import { usePaged } from '@/lib/usePaged'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Pager } from '@/components/Pager'
|
||||
|
||||
const empty = {
|
||||
id: 0, icon: '📘', title: '', description: '', content: '',
|
||||
category: '', related_sheet_type: '', published: true,
|
||||
}
|
||||
|
||||
export default function Articles() {
|
||||
const { page, setPage, data, totalPages, reload } = usePaged<any>(api.articles)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [form, setForm] = useState<any>(empty)
|
||||
|
||||
function edit(a: any) {
|
||||
setForm({ ...a })
|
||||
setOpen(true)
|
||||
}
|
||||
function create() {
|
||||
setForm({ ...empty })
|
||||
setOpen(true)
|
||||
}
|
||||
async function save() {
|
||||
await api.saveArticle(form)
|
||||
setOpen(false)
|
||||
reload()
|
||||
}
|
||||
async function del(id: number) {
|
||||
await api.deleteArticle(id)
|
||||
reload()
|
||||
}
|
||||
|
||||
const set = (k: string) => (e: any) => setForm({ ...form, [k]: e.target.value })
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="text-2xl font-bold">文章管理</h1>
|
||||
<Button onClick={create}>新建文章</Button>
|
||||
</div>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>标题</TableHead>
|
||||
<TableHead>分类</TableHead>
|
||||
<TableHead>关联弹层</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.list?.map((a) => (
|
||||
<TableRow key={a.id}>
|
||||
<TableCell>{a.id}</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
<span className="mr-1">{a.icon}</span>
|
||||
{a.title}
|
||||
</TableCell>
|
||||
<TableCell>{a.category || '—'}</TableCell>
|
||||
<TableCell>{a.related_sheet_type || '—'}</TableCell>
|
||||
<TableCell>
|
||||
{a.published ? <Badge variant="success">已发布</Badge> : <Badge variant="muted">草稿</Badge>}
|
||||
</TableCell>
|
||||
<TableCell className="text-right space-x-2 whitespace-nowrap">
|
||||
<Button variant="outline" size="sm" onClick={() => edit(a)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm" onClick={() => del(a.id)}>
|
||||
删除
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Pager page={page} totalPages={totalPages} total={data?.total ?? 0} onChange={setPage} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{form.id ? '编辑文章' : '新建文章'}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label>图标(emoji)</Label>
|
||||
<Input value={form.icon} onChange={set('icon')} />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label>分类</Label>
|
||||
<Input value={form.category} onChange={set('category')} />
|
||||
</div>
|
||||
<div className="space-y-1.5 col-span-2">
|
||||
<Label>标题</Label>
|
||||
<Input value={form.title} onChange={set('title')} />
|
||||
</div>
|
||||
<div className="space-y-1.5 col-span-2">
|
||||
<Label>简介</Label>
|
||||
<Input value={form.description} onChange={set('description')} />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label>关联弹层类型</Label>
|
||||
<Input value={form.related_sheet_type} onChange={set('related_sheet_type')} />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label>发布状态</Label>
|
||||
<select
|
||||
className="flex h-9 w-full rounded-md border border-input bg-card px-3 text-sm"
|
||||
value={form.published ? '1' : '0'}
|
||||
onChange={(e) => setForm({ ...form, published: e.target.value === '1' })}
|
||||
>
|
||||
<option value="1">已发布</option>
|
||||
<option value="0">草稿</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-1.5 col-span-2">
|
||||
<Label>正文</Label>
|
||||
<textarea
|
||||
className="flex min-h-24 w-full rounded-md border border-input bg-card px-3 py-2 text-sm"
|
||||
value={form.content}
|
||||
onChange={set('content')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={save}>保存</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { api } from '@/lib/api'
|
||||
import { usePaged } from '@/lib/usePaged'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Pager } from '@/components/Pager'
|
||||
|
||||
export default function Comments() {
|
||||
const { page, setPage, data, totalPages, reload } = usePaged<any>(api.comments)
|
||||
|
||||
async function del(id: number) {
|
||||
await api.deleteComment(id)
|
||||
reload()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">评论管理</h1>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>帖子ID</TableHead>
|
||||
<TableHead>作者</TableHead>
|
||||
<TableHead>内容</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.list?.map((c) => (
|
||||
<TableRow key={c.id}>
|
||||
<TableCell>{c.id}</TableCell>
|
||||
<TableCell>{c.post_id}</TableCell>
|
||||
<TableCell className="whitespace-nowrap">{c.author_name}</TableCell>
|
||||
<TableCell className="max-w-sm truncate">{c.content}</TableCell>
|
||||
<TableCell>
|
||||
{c.status === 'deleted' ? <Badge variant="destructive">已删除</Badge> : <Badge variant="success">正常</Badge>}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{c.status !== 'deleted' && (
|
||||
<Button variant="destructive" size="sm" onClick={() => del(c.id)}>
|
||||
删除
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Pager page={page} totalPages={totalPages} total={data?.total ?? 0} onChange={setPage} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Users, PawPrint, MessageSquare, Activity } from 'lucide-react'
|
||||
import { api } from '@/lib/api'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
type Stats = { users: number; pets: number; posts: number; records: number }
|
||||
|
||||
export default function Dashboard() {
|
||||
const [stats, setStats] = useState<Stats | null>(null)
|
||||
useEffect(() => {
|
||||
api.stats().then(setStats).catch(() => {})
|
||||
}, [])
|
||||
|
||||
const cards = [
|
||||
{ label: '用户', value: stats?.users, icon: Users },
|
||||
{ label: '宠物', value: stats?.pets, icon: PawPrint },
|
||||
{ label: '帖子', value: stats?.posts, icon: MessageSquare },
|
||||
{ label: '健康记录', value: stats?.records, icon: Activity },
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">概览</h1>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{cards.map((c) => (
|
||||
<Card key={c.label}>
|
||||
<CardHeader className="flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm text-muted-foreground">{c.label}</CardTitle>
|
||||
<c.icon className="h-4 w-4 text-primary" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold">{c.value ?? '—'}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { api, setToken } from '@/lib/api'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
export default function Login() {
|
||||
const [username, setUsername] = useState('sundynix')
|
||||
const [password, setPassword] = useState('sundynix')
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
|
||||
async function submit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
setError('')
|
||||
try {
|
||||
const res = await api.login(username, password)
|
||||
setToken(res.token)
|
||||
navigate('/')
|
||||
} catch (err: any) {
|
||||
setError(err.message || '登录失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center p-4">
|
||||
<Card className="w-full max-w-sm">
|
||||
<CardHeader>
|
||||
<div className="text-3xl text-center mb-2">🐾</div>
|
||||
<CardTitle className="text-center text-xl">毛孩子计划 · 后台管理</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={submit} className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label>账号</Label>
|
||||
<Input value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label>密码</Label>
|
||||
<Input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
</div>
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? '登录中...' : '登录'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { api } from '@/lib/api'
|
||||
import { usePaged } from '@/lib/usePaged'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Pager } from '@/components/Pager'
|
||||
|
||||
export default function Members() {
|
||||
const { page, setPage, data, totalPages } = usePaged<any>(api.memberships)
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">会员管理</h1>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>用户ID</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>类型</TableHead>
|
||||
<TableHead>价格</TableHead>
|
||||
<TableHead>到期</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.list?.map((m) => (
|
||||
<TableRow key={m.id}>
|
||||
<TableCell>{m.id}</TableCell>
|
||||
<TableCell>{m.user_id}</TableCell>
|
||||
<TableCell>
|
||||
{m.status === 'active' ? <Badge variant="success">生效中</Badge> : <Badge variant="muted">{m.status}</Badge>}
|
||||
</TableCell>
|
||||
<TableCell>{m.plan_type || '—'}</TableCell>
|
||||
<TableCell>¥{m.price}</TableCell>
|
||||
<TableCell>{m.end_date ? String(m.end_date).slice(0, 10) : '—'}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Pager page={page} totalPages={totalPages} total={data?.total ?? 0} onChange={setPage} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { api } from '@/lib/api'
|
||||
import { usePaged } from '@/lib/usePaged'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Pager } from '@/components/Pager'
|
||||
|
||||
export default function Pets() {
|
||||
const { page, setPage, data, totalPages } = usePaged<any>(api.pets)
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">宠物管理</h1>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>宠物</TableHead>
|
||||
<TableHead>类型</TableHead>
|
||||
<TableHead>阶段</TableHead>
|
||||
<TableHead>体重</TableHead>
|
||||
<TableHead>主人ID</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.list?.map((p) => (
|
||||
<TableRow key={p.id}>
|
||||
<TableCell>{p.id}</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
<span className="mr-1">{p.emoji}</span>
|
||||
{p.name}
|
||||
</TableCell>
|
||||
<TableCell>{p.type}</TableCell>
|
||||
<TableCell>{p.stage}</TableCell>
|
||||
<TableCell>{p.weight}</TableCell>
|
||||
<TableCell>{p.user_id}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Pager page={page} totalPages={totalPages} total={data?.total ?? 0} onChange={setPage} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { api } from '@/lib/api'
|
||||
import { usePaged } from '@/lib/usePaged'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Pager } from '@/components/Pager'
|
||||
|
||||
const statusMap: Record<string, { label: string; variant: any }> = {
|
||||
published: { label: '已发布', variant: 'success' },
|
||||
hidden: { label: '已隐藏', variant: 'muted' },
|
||||
deleted: { label: '已删除', variant: 'destructive' },
|
||||
}
|
||||
|
||||
export default function Posts() {
|
||||
const { page, setPage, data, totalPages, reload } = usePaged<any>(api.posts)
|
||||
|
||||
async function setStatus(id: number, status: string) {
|
||||
await api.setPostStatus(id, status)
|
||||
reload()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">帖子审核</h1>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>作者</TableHead>
|
||||
<TableHead>内容</TableHead>
|
||||
<TableHead>赞/评</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.list?.map((p) => (
|
||||
<TableRow key={p.id}>
|
||||
<TableCell>{p.id}</TableCell>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
<span className="mr-1">{p.author_emoji}</span>
|
||||
{p.author_name}
|
||||
</TableCell>
|
||||
<TableCell className="max-w-sm truncate">{p.content}</TableCell>
|
||||
<TableCell className="whitespace-nowrap">
|
||||
{p.like_count} / {p.comment_count}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={statusMap[p.status]?.variant}>{statusMap[p.status]?.label ?? p.status}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right space-x-2 whitespace-nowrap">
|
||||
{p.status !== 'published' && (
|
||||
<Button variant="outline" size="sm" onClick={() => setStatus(p.id, 'published')}>
|
||||
恢复
|
||||
</Button>
|
||||
)}
|
||||
{p.status === 'published' && (
|
||||
<Button variant="outline" size="sm" onClick={() => setStatus(p.id, 'hidden')}>
|
||||
隐藏
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="destructive" size="sm" onClick={() => setStatus(p.id, 'deleted')}>
|
||||
删除
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Pager page={page} totalPages={totalPages} total={data?.total ?? 0} onChange={setPage} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useState } from 'react'
|
||||
import { api } from '@/lib/api'
|
||||
import { usePaged } from '@/lib/usePaged'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Pager } from '@/components/Pager'
|
||||
|
||||
export default function Users() {
|
||||
const [keyword, setKeyword] = useState('')
|
||||
const [query, setQuery] = useState('')
|
||||
const { page, setPage, data, totalPages, reload } = usePaged<any>(api.users, { keyword: query })
|
||||
|
||||
async function toggle(u: any) {
|
||||
await api.setUserDisabled(u.id, !u.disabled)
|
||||
reload()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">用户管理</h1>
|
||||
<div className="flex gap-2 mb-4">
|
||||
<Input
|
||||
placeholder="搜索昵称"
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
className="max-w-xs"
|
||||
onKeyDown={(e) => e.key === 'Enter' && (setPage(1), setQuery(keyword))}
|
||||
/>
|
||||
<Button onClick={() => (setPage(1), setQuery(keyword))}>搜索</Button>
|
||||
</div>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>昵称</TableHead>
|
||||
<TableHead>手机号</TableHead>
|
||||
<TableHead>建档</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.list?.map((u) => (
|
||||
<TableRow key={u.id}>
|
||||
<TableCell>{u.id}</TableCell>
|
||||
<TableCell className="font-medium">{u.nickname}</TableCell>
|
||||
<TableCell>{u.phone || '—'}</TableCell>
|
||||
<TableCell>{u.onboarded ? '已建档' : '未建档'}</TableCell>
|
||||
<TableCell>
|
||||
{u.disabled ? <Badge variant="destructive">已禁用</Badge> : <Badge variant="success">正常</Badge>}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button variant="outline" size="sm" onClick={() => toggle(u)}>
|
||||
{u.disabled ? '启用' : '禁用'}
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Pager page={page} totalPages={totalPages} total={data?.total ?? 0} onChange={setPage} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user