feat(cms): improve catalog, editorial recovery and operations
CI / check (push) Successful in 52s
CI / deploy (push) Successful in 2m10s
CI / publish-container (push) Failing after 1m18s

This commit is contained in:
Simo committed 2026-09-09 19:36:15 +02:00
1 parent 2fead134e6
commit c389c3893d
122 files changed
+8137 -703

No files matched your search

+78
View File
@@ -247,3 +247,81 @@
background: var(--color-primary);
border-radius: 999px;
}
.shortcutHint {
display: block;
margin-top: 4px;
font-size: 0.75rem;
font-weight: 400;
color: var(--color-text-muted);
}
.shortcut {
min-height: 64px;
text-align: center;
overflow-wrap: anywhere;
}
.shortcut:focus-visible,
.friend:focus-visible,
.room:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 3px;
}
.event {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 16px;
}
.eventDetails {
min-width: 0;
flex: 1 1 220px;
overflow-wrap: anywhere;
}
.eventDetails h2 {
margin: 8px 0;
font-size: 1.15rem;
}
.eventDetails p {
margin: 0;
font-size: 0.85rem;
}
.eventPhase {
display: inline-block;
border-radius: 999px;
padding: 4px 10px;
color: var(--color-text-readable);
background: color-mix(
in srgb,
var(--color-primary) 14%,
var(--color-surface)
);
font-size: 0.75rem;
font-weight: 700;
}
@media (max-width: 400px) {
.hero {
gap: 12px;
padding: 14px;
}
.avatar {
width: 64px;
height: 100px;
}
.identity {
min-width: 0;
}
.identity h1 {
font-size: 1.4rem;
}
.wallet {
gap: 8px;
}
.currency {
padding: 10px;
}
.event > a {
width: 100%;
text-align: center;
}
}
+63 -8
View File
@@ -10,7 +10,7 @@ import { ProfileImage } from "@/components/shared/profile-image";
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
import { SurfaceCard } from "@/components/surface-card";
import { auth } from "@/lib/auth";
import { avatarImageUrl } from "@/lib/format";
import { avatarImageUrl, slugify } from "@/lib/format";
import { loadUserDashboard } from "@/lib/services/user-dashboard";
import CopyReferralButton from "./CopyReferralButton";
import styles from "./dashboard.module.css";
@@ -75,10 +75,11 @@ export default async function MePage({
referralsRows,
friends,
currencyRows,
dashboardEvent,
} = data;
const userSettings = userSettingsRows[0];
const friendCount = Number(friendCountRows[0]?.value ?? 0);
const unreadCount = Number(unreadCountRows[0]?.value ?? 0);
const unreadCount = Number(unreadCountRows?.[0]?.value ?? 0);
const needed = Math.max(1, Number.parseInt(neededRaw ?? "5", 10) || 5);
const rewardAmount = Number.parseInt(rewardAmountRaw ?? "30", 10) || 0;
const rewardCurrency = (
@@ -173,12 +174,20 @@ export default async function MePage({
{ href: "/shop", label: t("shop"), count: 0 },
].map((link) => (
<Link key={link.href} href={link.href} className={styles.shortcut}>
{link.label}
<span>
{link.label}
{link.href === "/messages" && (
<span className={styles.shortcutHint}>
{unreadCountRows === null
? t("messagesUnavailable")
: unreadCount > 0
? t("unread", { count: unreadCount })
: t("messagesCaughtUp")}
</span>
)}
</span>
{link.count > 0 && (
<span
className={styles.count}
title={t("unread", { count: link.count })}
>
<span className={styles.count} aria-hidden="true">
{link.count > 99 ? "99+" : link.count}
</span>
)}
@@ -199,6 +208,48 @@ export default async function MePage({
</SurfaceCard>
))}
</section>
<SurfaceCard
title={t("eventTitle")}
actionHref="/events"
actionLabel={t("allEvents")}
bodyClassName="p-5"
>
{dashboardEvent.unavailable ? (
<p className="muted" role="status">
{t("eventsUnavailable")}
</p>
) : dashboardEvent.event ? (
<div className={styles.event}>
<div className={styles.eventDetails}>
<span className={styles.eventPhase}>
{t(
dashboardEvent.event.phase === "ongoing"
? "eventOngoing"
: "eventUpcoming",
)}
</span>
<h2>{dashboardEvent.event.title}</h2>
<p className="muted">
{dashboardEvent.event.typeName} ·{" "}
<time dateTime={dashboardEvent.event.startsAt.toISOString()}>
{format.dateTime(dashboardEvent.event.startsAt, {
dateStyle: "medium",
timeStyle: "short",
})}
</time>
</p>
</div>
<Link
href={`/events/${slugify(dashboardEvent.event.title)}-${dashboardEvent.event.id}`}
className="btn btn-outline"
>
{t("eventDetails")} →
</Link>
</div>
) : (
<p className="muted">{t("noUpcomingEvents")}</p>
)}
</SurfaceCard>
<div className={styles.columns}>
<div className={styles.stack}>
<SurfaceCard
@@ -236,7 +287,11 @@ export default async function MePage({
bodyClassName="p-5"
>
<p className="muted">{t("roomsHint")}</p>
{recentRooms.length === 0 ? (
{recentRooms === null ? (
<p className="muted" role="status">
{t("roomsUnavailable")}
</p>
) : recentRooms.length === 0 ? (
<p className="muted">{t("noRooms")}</p>
) : (
<div className={styles.rooms}>