Files
EpicNext-Cms/src/app/banned/page.tsx
T
Simo 22d53d0e9c Add security middleware, audit log, alerts, PayPal, cron, radio + apps
Security (launch blockers):
- src/middleware.ts (edge): forwards x-pathname + real client IP.
- access-guard.ts (Node, from root layout): routes non-staff to /maintenance
  when maintenance mode is on, banned users to /banned. New /banned + /maintenance
  pages (the consumers the admin toggle was missing). Admin layout enforces
  force_staff_2fa before /admin.
- staff-activity.ts audit log wired into ban/lift/give-currency/set-rank actions.

Infra (parallel agents): alert service (alert_logs + Discord embed + email),
PayPal top-up (create/capture API routes + /shop/topup), cron worker
(scripts/jobs-worker.ts via croner: emulator-ping->alert, maintenance-check,
bans-cleanup), social connections page, admin radio settings/banners/ranks.
Public radio subsystem: /radio (+schedule, shouts+post, contests, giveaways,
apply, leaderboard) and /apply/staff + /apply/team submission forms. Radio nav
link added. .env.example documents the new optional vars.

(radio song-requests dropped: its table is a stub in AtomCMS — columns added by
un-modeled alter-migrations.)

Verified: tsc exit 0, vitest 48/48, next build exit 0 (82 page routes).
2026-06-28 15:10:19 +02:00

52 lines
1.5 KiB
TypeScript

import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
export const dynamic = "force-dynamic";
const FAR_FUTURE = Math.floor(Date.now() / 1000) + 50 * 365 * 24 * 3600;
export default async function BannedPage() {
const session = await auth();
let reason = "";
let expire = 0;
if (session?.user?.id) {
try {
const now = Math.floor(Date.now() / 1000);
const ban = await prisma.ban.findFirst({
where: { userId: Number(session.user.id), banExpire: { gt: now } },
orderBy: { banExpire: "desc" },
select: { banReason: true, banExpire: true },
});
if (ban) {
reason = ban.banReason;
expire = ban.banExpire;
}
} catch {
// show generic message
}
}
const expiryText =
expire === 0
? ""
: expire > FAR_FUTURE
? "This ban is permanent."
: `Expires ${new Date(expire * 1000).toISOString().slice(0, 16).replace("T", " ")}.`;
return (
<main style={{ maxWidth: 540, margin: "2rem auto" }}>
<div className="card" style={{ padding: "1.75rem", textAlign: "center" }}>
<h1 style={{ marginTop: 0, color: "var(--color-danger)" }}>You are banned</h1>
<p>Your account has been suspended from the hotel.</p>
{reason ? (
<p>
<strong>Reason:</strong> {reason}
</p>
) : null}
{expiryText ? <p className="muted">{expiryText}</p> : null}
<p className="muted">If you believe this is a mistake, contact the staff team.</p>
</div>
</main>
);
}