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,52 @@
"use client";
import { useTranslations } from "next-intl";
import { StatusBadge } from "./StatusBadge";
import type { Redemption } from "@/lib/afc";
interface RedemptionHistoryTableProps {
redemptions: Redemption[];
}
export function RedemptionHistoryTable({ redemptions }: RedemptionHistoryTableProps) {
const t = useTranslations("afc");
if (redemptions.length === 0) {
return (
<p className="text-center text-gray-500 py-8">{t("no_redemptions")}</p>
);
}
return (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-white/10 text-gray-400">
<th className="text-left py-3 px-2 font-medium">{t("date")}</th>
<th className="text-left py-3 px-2 font-medium">{t("prize")}</th>
<th className="text-right py-3 px-2 font-medium">AFC</th>
<th className="text-center py-3 px-2 font-medium">{t("status")}</th>
</tr>
</thead>
<tbody>
{redemptions.map((r) => (
<tr key={r.id} className="border-b border-white/5 hover:bg-white/[0.02]">
<td className="py-3 px-2 text-gray-300">
{new Date(r.created_at).toLocaleDateString()}
</td>
<td className="py-3 px-2 text-white">
{r.prize_detail}
</td>
<td className="py-3 px-2 text-right text-red-400 font-medium">
-{r.amount_afc}
</td>
<td className="py-3 px-2 text-center">
<StatusBadge status={r.status} />
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}