[www] staff gifting

This commit is contained in:
Devin Zuczek
2026-09-22 15:24:01 -04:00
parent 533644fcf1
commit f64b04544a
7 changed files with 519 additions and 45 deletions
+84
View File
@@ -1172,7 +1172,11 @@ function PlayerPage({
function StaffPlayerActions({ account, navigate }: { account: PublicAccount; navigate: Navigate }) {
const [tokens, setTokens] = useState('')
const [confirmingClear, setConfirmingClear] = useState(false)
const [customItemId, setCustomItemId] = useState('')
const [xp, setXp] = useState('')
const gift = useAction()
const xpGift = useAction()
const customGift = useAction()
const usernameChange = useAction()
const clearPassword = useAction()
const base = `/api/staff/players/${account.accountId}`
@@ -1218,6 +1222,86 @@ function StaffPlayerActions({ account, navigate }: { account: PublicAccount; nav
{gift.done && <p className="ok">{gift.done}</p>}
</form>
<form
onSubmit={(e) => {
e.preventDefault()
void xpGift.run(async () => {
const amount = Number(xp)
const res = await call<{ level: number; levelsGained: number }>(`${base}/gift-xp`, {
authed: true,
json: { amount },
})
setXp('')
const levels =
res.levelsGained > 0
? ` They went up ${res.levelsGained} level${res.levelsGained === 1 ? '' : 's'}, to ${res.level}.`
: ` They're level ${res.level}.`
return `Sent ${amount.toLocaleString()} XP.${levels}`
})
}}
>
<label>
Gift XP
<span className="staff-gift">
<input
type="number"
min={1}
step={1}
inputMode="numeric"
value={xp}
required
onChange={(e) => setXp(e.target.value)}
/>
<button type="submit" disabled={xpGift.pending}>
{xpGift.pending ? 'Sending…' : 'Send'}
</button>
</span>
<span className="hint">
Arrives as a gift box. Levels it crosses count, but don&apos;t pay their level-up
rewards.
</span>
</label>
{xpGift.error && <p className="error">{xpGift.error}</p>}
{xpGift.done && <p className="ok">{xpGift.done}</p>}
</form>
<form
onSubmit={(e) => {
e.preventDefault()
void customGift.run(async () => {
const res = await call<{ name: string }>(`${base}/gift-custom-item`, {
authed: true,
json: { customAvatarItemId: customItemId.trim() },
})
setCustomItemId('')
return `Sent “${res.name}” to @${account.username}.`
})
}}
>
<label>
Gift custom item
<span className="staff-gift">
<input
type="text"
value={customItemId}
placeholder="Custom avatar item id"
spellCheck={false}
required
onChange={(e) => setCustomItemId(e.target.value)}
/>
<button type="submit" disabled={customGift.pending}>
{customGift.pending ? 'Sending…' : 'Send'}
</button>
</span>
<span className="hint">
Arrives as a gift box, as if bought from the store. Free to them, and the creator
isn&apos;t paid.
</span>
</label>
{customGift.error && <p className="error">{customGift.error}</p>}
{customGift.done && <p className="ok">{customGift.done}</p>}
</form>
<div className="mod-filter-actions staff-actions">
<button
type="submit"
+5
View File
@@ -1568,6 +1568,11 @@ button[type='submit']:disabled {
max-width: 180px;
}
/* A custom item id is a GUID, far wider than a token amount. */
.staff-gift input[type='text'] {
max-width: 360px;
}
.staff-actions {
flex-wrap: wrap;
margin-top: 8px;