feat: add lottery reset functionality in admin user actions and API
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m20s
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m20s
This commit is contained in:
@@ -19,6 +19,7 @@ export function AdminUserActions({ user }: { user: UserData }) {
|
||||
const [points, setPoints] = useState(String(user.researchPoints))
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [resetUrl, setResetUrl] = useState<string | null>(null)
|
||||
const [lotteryReset, setLotteryReset] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
async function handleSave() {
|
||||
@@ -57,6 +58,23 @@ export function AdminUserActions({ user }: { user: UserData }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResetLottery() {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
const res = await fetch(`/api/admin/users/${user.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ resetLotteryAt: true }),
|
||||
})
|
||||
const data = await res.json()
|
||||
setLoading(false)
|
||||
if (!res.ok) {
|
||||
setError(data.error ?? 'Reset failed.')
|
||||
} else {
|
||||
setLotteryReset(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
@@ -103,7 +121,22 @@ export function AdminUserActions({ user }: { user: UserData }) {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Reset URL section */}
|
||||
{/* Lucky Dip reset */}
|
||||
<div className="border-t border-surface-border pt-4">
|
||||
<p className="text-sm text-slate-400 mb-2">Lucky Dip</p>
|
||||
<button
|
||||
onClick={handleResetLottery}
|
||||
disabled={loading || lotteryReset}
|
||||
className="text-sm bg-indigo-600/20 hover:bg-indigo-600/30 text-indigo-400 border border-indigo-500/30 px-4 py-1.5 rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
{lotteryReset ? 'Reset ✓' : 'Reset today\'s play'}
|
||||
</button>
|
||||
{lotteryReset && (
|
||||
<p className="text-xs text-slate-500 mt-1">Player can play again today.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Password reset */}
|
||||
<div className="border-t border-surface-border pt-4">
|
||||
<p className="text-sm text-slate-400 mb-2">Password reset</p>
|
||||
<button
|
||||
|
||||
@@ -8,6 +8,7 @@ const schema = z.object({
|
||||
balance: z.number().min(0).optional(),
|
||||
researchPoints: z.number().int().min(0).optional(),
|
||||
isAdmin: z.boolean().optional(),
|
||||
resetLotteryAt: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export async function PATCH(req: NextRequest, { params }: { params: { userId: string } }) {
|
||||
@@ -22,9 +23,14 @@ export async function PATCH(req: NextRequest, { params }: { params: { userId: st
|
||||
return NextResponse.json({ error: 'Invalid request.' }, { status: 400 })
|
||||
}
|
||||
|
||||
const { resetLotteryAt, ...rest } = parsed.data
|
||||
|
||||
const updated = await prisma.user.update({
|
||||
where: { id: params.userId },
|
||||
data: parsed.data,
|
||||
data: {
|
||||
...rest,
|
||||
...(resetLotteryAt ? { lastLotteryAt: null } : {}),
|
||||
},
|
||||
select: { id: true, username: true, balance: true, researchPoints: true, isAdmin: true },
|
||||
})
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useSession, signOut } from 'next-auth/react'
|
||||
import { TrendingUp, Search, User, LogOut, Shield, Trophy, BarChart2 } from 'lucide-react'
|
||||
import { TrendingUp, Search, User, LogOut, Shield, Trophy } from 'lucide-react'
|
||||
import { useState, useRef } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
@@ -61,14 +61,6 @@ export function Navbar() {
|
||||
<span className="font-bold text-lg hidden sm:block">HashEx</span>
|
||||
</Link>
|
||||
|
||||
{/* Markets link */}
|
||||
<Link
|
||||
href="/stocks"
|
||||
className="text-slate-400 hover:text-slate-200 transition-colors text-sm hidden sm:block shrink-0"
|
||||
>
|
||||
Markets
|
||||
</Link>
|
||||
|
||||
{/* Search */}
|
||||
<form onSubmit={handleSearch} className="flex-1 max-w-md">
|
||||
<div className="relative">
|
||||
|
||||
Reference in New Issue
Block a user