feat: add AFC Store with MercadoPago purchases and prize redemption
Some checks failed
Deploy / deploy (push) Has been cancelled

Players can now buy AfterCoin with real money (MercadoPago Checkout Pro,
$15 MXN/AFC) and redeem AFC for gift cards or cash withdrawals. Admin
fulfills redemptions manually.

- Bridge: payments + redemptions tables, CRUD routes, PATCH auth
- Next.js API: verify-disk, balance, create-preference, webhook (idempotent
  minting with HMAC signature verification), redeem, payment/redemption history
- Frontend: hub, buy flow (4 packages + custom), redeem flow (gift cards +
  cash out), success/failure/pending pages, history with tabs, 8 components
- i18n: full English + Spanish translations
- Infra: nginx /api/afc/ → Next.js, docker-compose env vars, .env.example

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
consultoria-as
2026-02-26 02:26:13 +00:00
parent 7dc1d2e0e5
commit a76d513659
38 changed files with 2142 additions and 5 deletions

View File

@@ -0,0 +1,49 @@
"use client";
interface AfcPackageCardProps {
amount: number;
priceMxn: number;
popular?: boolean;
loading?: boolean;
onSelect: () => void;
}
export function AfcPackageCard({
amount,
priceMxn,
popular,
loading,
onSelect,
}: AfcPackageCardProps) {
return (
<button
onClick={onSelect}
disabled={loading}
className={`relative group block w-full text-left bg-gray-900 rounded-2xl p-6 border transition-all duration-200 hover:scale-[1.02] active:scale-[0.98] disabled:opacity-50 ${
popular
? "border-amber-500/50 shadow-lg shadow-amber-500/10"
: "border-white/5 hover:border-amber-500/30"
}`}
>
{popular && (
<span className="absolute -top-3 left-1/2 -translate-x-1/2 bg-amber-500 text-black text-xs font-bold px-3 py-1 rounded-full">
POPULAR
</span>
)}
<div className="flex items-center gap-4">
<div className="w-14 h-14 rounded-full bg-amber-500/10 border border-amber-500/30 flex items-center justify-center shrink-0 group-hover:bg-amber-500/20 transition-colors">
<span className="text-xl font-bold text-amber-400">{amount}</span>
</div>
<div className="flex-1">
<p className="text-white font-semibold text-lg">{amount} AFC</p>
<p className="text-gray-500 text-sm">AfterCoin</p>
</div>
<div className="text-right">
<p className="text-2xl font-bold text-white">${priceMxn}</p>
<p className="text-xs text-gray-500">MXN</p>
</div>
</div>
</button>
);
}