feat: add lottery reset functionality in admin user actions and API
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m20s

This commit is contained in:
2026-03-18 19:13:16 -04:00
parent d11e2e7b9a
commit 98f08ece11
3 changed files with 42 additions and 11 deletions
+34 -1
View File
@@ -19,6 +19,7 @@ export function AdminUserActions({ user }: { user: UserData }) {
const [points, setPoints] = useState(String(user.researchPoints)) const [points, setPoints] = useState(String(user.researchPoints))
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [resetUrl, setResetUrl] = useState<string | null>(null) const [resetUrl, setResetUrl] = useState<string | null>(null)
const [lotteryReset, setLotteryReset] = useState(false)
const [error, setError] = useState('') const [error, setError] = useState('')
async function handleSave() { 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 ( return (
<> <>
<button <button
@@ -103,7 +121,22 @@ export function AdminUserActions({ user }: { user: UserData }) {
</p> </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"> <div className="border-t border-surface-border pt-4">
<p className="text-sm text-slate-400 mb-2">Password reset</p> <p className="text-sm text-slate-400 mb-2">Password reset</p>
<button <button
+7 -1
View File
@@ -8,6 +8,7 @@ const schema = z.object({
balance: z.number().min(0).optional(), balance: z.number().min(0).optional(),
researchPoints: z.number().int().min(0).optional(), researchPoints: z.number().int().min(0).optional(),
isAdmin: z.boolean().optional(), isAdmin: z.boolean().optional(),
resetLotteryAt: z.boolean().optional(),
}) })
export async function PATCH(req: NextRequest, { params }: { params: { userId: string } }) { 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 }) return NextResponse.json({ error: 'Invalid request.' }, { status: 400 })
} }
const { resetLotteryAt, ...rest } = parsed.data
const updated = await prisma.user.update({ const updated = await prisma.user.update({
where: { id: params.userId }, where: { id: params.userId },
data: parsed.data, data: {
...rest,
...(resetLotteryAt ? { lastLotteryAt: null } : {}),
},
select: { id: true, username: true, balance: true, researchPoints: true, isAdmin: true }, select: { id: true, username: true, balance: true, researchPoints: true, isAdmin: true },
}) })
+1 -9
View File
@@ -2,7 +2,7 @@
import Link from 'next/link' import Link from 'next/link'
import { useSession, signOut } from 'next-auth/react' 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 { useState, useRef } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { formatCurrency } from '@/lib/utils' import { formatCurrency } from '@/lib/utils'
@@ -61,14 +61,6 @@ export function Navbar() {
<span className="font-bold text-lg hidden sm:block">HashEx</span> <span className="font-bold text-lg hidden sm:block">HashEx</span>
</Link> </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 */} {/* Search */}
<form onSubmit={handleSearch} className="flex-1 max-w-md"> <form onSubmit={handleSearch} className="flex-1 max-w-md">
<div className="relative"> <div className="relative">