7534b9cbbb
Portado de home/views/recruit.py: - lib/recruit-claim.ts: getRecruitRewards (con marca de reclamadas), getRecruitedLevel80Count, y claimRecruitReward con las mismas validaciones que Django (recompensa existe, no reclamada, amigos reclutados a nivel 80 suficientes, anti-abuso por IP compartida), entrega por SOAP `.send items` (éxito si "Mail sent to") y registro en home_claimedreward. Extra de seguridad: valida que el personaje sea de la cuenta. - API POST /api/recruit/claim (401 sin sesión). - UI /recruit (pública): rejilla de recompensas con estado reclamada/elegible/bloqueada, selector de personaje y contador de amigos a nivel 80. Enlace en el footer. - i18n es/en: namespace RecruitClaim + Nav.recruit. Verificado: build OK, /recruit 200, claim 401 sin sesión. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
20 lines
932 B
TypeScript
20 lines
932 B
TypeScript
import { getSession } from '@/lib/session'
|
|
import { claimRecruitReward } from '@/lib/recruit-claim'
|
|
|
|
export async function POST(request: Request) {
|
|
const session = await getSession()
|
|
if (!session.accountId || !session.username) {
|
|
return Response.json({ success: false, message: 'notAuthenticated' }, { status: 401 })
|
|
}
|
|
|
|
let b: Record<string, unknown> = {}
|
|
try { b = await request.json() } catch { return Response.json({ success: false, message: 'invalidRequest' }, { status: 400 }) }
|
|
const rewardId = Number(b.rewardId)
|
|
const character = String(b.character ?? '').trim()
|
|
if (!rewardId || !character) return Response.json({ success: false, message: 'selectRewardAndCharacter' })
|
|
|
|
const ip = (request.headers.get('x-forwarded-for') || '').split(',')[0].trim() || '127.0.0.1'
|
|
const result = await claimRecruitReward(session.accountId, session.username, rewardId, character, ip)
|
|
return Response.json(result)
|
|
}
|