96 lines
2.6 KiB
TypeScript
96 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
import { useState } from "react";
|
|
|
|
import { api } from "@/lib/api";
|
|
|
|
type UserRow = {
|
|
id: number;
|
|
publicId: string;
|
|
username: string;
|
|
nickname: string;
|
|
email: string;
|
|
status: number;
|
|
createdAt: string;
|
|
};
|
|
|
|
export default function UsersPage() {
|
|
const [adjustForm, setAdjustForm] = useState({
|
|
userId: 1,
|
|
amountPoints: 100,
|
|
reason: "manual bonus",
|
|
});
|
|
const usersQuery = useQuery({
|
|
queryKey: ["admin-users"],
|
|
queryFn: () => api.get<UserRow[]>("/api/v1/admin/users"),
|
|
});
|
|
const adjustMutation = useMutation({
|
|
mutationFn: () =>
|
|
api.post(`/api/v1/admin/users/${adjustForm.userId}/wallet-adjust`, adjustForm),
|
|
});
|
|
|
|
return (
|
|
<div className="two-col-grid">
|
|
<section className="panel">
|
|
<h3>用户列表</h3>
|
|
<div className="list-grid">
|
|
{usersQuery.data?.map((user) => (
|
|
<div className="list-item" key={user.id}>
|
|
<strong>{user.nickname || user.username || user.publicId}</strong>
|
|
<div className="muted">
|
|
#{user.id} · {user.email} · 状态 {user.status}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<section className="panel">
|
|
<h3>人工调账</h3>
|
|
<div className="form-stack">
|
|
<label className="field-label">
|
|
用户 ID
|
|
<input
|
|
type="number"
|
|
value={adjustForm.userId}
|
|
onChange={(event) =>
|
|
setAdjustForm((previous) => ({
|
|
...previous,
|
|
userId: Number(event.target.value),
|
|
}))
|
|
}
|
|
/>
|
|
</label>
|
|
<label className="field-label">
|
|
调整积分
|
|
<input
|
|
type="number"
|
|
value={adjustForm.amountPoints}
|
|
onChange={(event) =>
|
|
setAdjustForm((previous) => ({
|
|
...previous,
|
|
amountPoints: Number(event.target.value),
|
|
}))
|
|
}
|
|
/>
|
|
</label>
|
|
<label className="field-label">
|
|
原因
|
|
<input
|
|
value={adjustForm.reason}
|
|
onChange={(event) =>
|
|
setAdjustForm((previous) => ({ ...previous, reason: event.target.value }))
|
|
}
|
|
/>
|
|
</label>
|
|
<button className="primary-button" onClick={() => adjustMutation.mutate()}>
|
|
执行调账
|
|
</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|
|
|