feat(cms): improve catalog, editorial recovery and operations
This commit is contained in:
1 parent
2fead134e6
commit
c389c3893d
122 files changed
+8137
-703
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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}>
|
||||
|
||||
Reference in new issue
Block a user