feat: shop buy, forum replies, tickets, messages, sessions, and UX hardening
Deploy / release (push) Skipped
Deploy / deploy (push) Failing after 4m23s

Complete remaining product gaps: credit-based shop purchases, guild thread replies, help ticket detail/reply/close, offline message compose, sign-out-everywhere via JWT version, ads delete confirm, soft-fail feedback, rate limits, loading states, and single auth() in site layout.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-21 21:21:02 +02:00
1 parent ed7db6e048
commit 803e8f36c1
47 files changed
+2793 -358

No files matched your search

+47 -1
View File
@@ -1,5 +1,6 @@
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import type { CSSProperties } from "react";
import { applyTeam } from "@/actions/applications";
import { ContentCard, EmptyState } from "@/components/public/ui";
import { auth } from "@/lib/auth";
@@ -11,11 +12,45 @@ export const dynamic = "force-dynamic";
// Canonical Habbo badge image CDN (same base used by the profile page).
const BADGE_IMG_BASE = "https://images.habbo.com/c_images/album1584";
export default async function ApplyTeamPage() {
function feedbackStyle(tone: "success" | "error"): CSSProperties {
const accent =
tone === "error" ? "var(--color-danger)" : "var(--color-primary)";
return {
margin: 0,
padding: "0.85rem 1rem",
borderRadius: "var(--radius-md)",
border: `1px solid ${accent}`,
color: "var(--color-text-readable, var(--color-text))",
fontSize: "0.9rem",
fontWeight: 600,
background: "var(--color-surface)",
borderLeft: `4px solid ${accent}`,
};
}
export default async function ApplyTeamPage({
searchParams,
}: {
searchParams: Promise<{ submitted?: string; error?: string }>;
}) {
const session = await auth();
if (!session?.user?.id) redirect("/login");
const t = await getTranslations("pages.applyTeam");
const { submitted, error } = await searchParams;
const errorMessage =
error === "empty"
? t("errors.empty")
: error === "duplicate"
? t("errors.duplicate")
: error === "ratelimit"
? t("errors.ratelimit")
: error === "invalid"
? t("errors.invalid")
: error
? t("errors.error")
: null;
const userId = Number(session.user.id);
const hotelName = (await siteSettings.get("hotel_name", "Atom")) ?? "Atom";
@@ -40,6 +75,17 @@ export default async function ApplyTeamPage() {
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
{submitted === "1" ? (
<div role="status" style={feedbackStyle("success")}>
{t("success.submitted")}
</div>
) : null}
{errorMessage ? (
<div role="alert" style={feedbackStyle("error")}>
{errorMessage}
</div>
) : null}
<ContentCard
icon="🤝"
title={t("title", { hotel: hotelName })}