fix: use currency icons in public balances
This commit is contained in:
1 parent
adc201bfb6
commit
06cd0cfe42
6 files changed
+72
-29
No files matched your search
@@ -2,6 +2,7 @@ import { desc, eq } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { buyBadge } from "@/actions/draw-badge";
|
||||
import { ContentCard, EmptyState, StatBlock } from "@/components/public/ui";
|
||||
import { CurrencyIcon } from "@/components/shared/currency-icon";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db, User, WebsiteDrawbadges } from "@/lib/db";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
@@ -193,7 +194,7 @@ export default async function DrawBadgePage({
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<h3 style={{ margin: 0 }}>{b.badgeName}</h3>
|
||||
<span className="currency">
|
||||
<span className="coin credits">cr</span>
|
||||
<CurrencyIcon kind="credits" size={22} alt="" />
|
||||
{price.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,10 @@ import { desc, eq, inArray } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ContentCard, EmptyState, RankBadge } from "@/components/public/ui";
|
||||
import {
|
||||
CurrencyIcon,
|
||||
type CurrencyKind,
|
||||
} from "@/components/shared/currency-icon";
|
||||
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
|
||||
import { db, User, UsersCurrency, UsersSettings } from "@/lib/db";
|
||||
|
||||
@@ -10,12 +14,12 @@ export const metadata = { title: "Leaderboard" };
|
||||
// Currency type ids (see UsersCurrency in src/db/schema.ts):
|
||||
// Credits = -1 (lives on users.credits), Duckets = 0, Diamonds = 5.
|
||||
const TABS = [
|
||||
{ key: "credits", label: "Credits", coin: "credits" },
|
||||
{ key: "diamonds", label: "Diamonds", coin: "diamonds" },
|
||||
{ key: "duckets", label: "Duckets", coin: "duckets" },
|
||||
{ key: "achievement", label: "Achievement", coin: "" },
|
||||
{ key: "respects", label: "Respects", coin: "" },
|
||||
{ key: "onlinetime", label: "Online Time", coin: "" },
|
||||
{ key: "credits", label: "Credits" },
|
||||
{ key: "diamonds", label: "Diamonds" },
|
||||
{ key: "duckets", label: "Duckets" },
|
||||
{ key: "achievement", label: "Achievement" },
|
||||
{ key: "respects", label: "Respects" },
|
||||
{ key: "onlinetime", label: "Online Time" },
|
||||
] as const;
|
||||
|
||||
type TabKey = (typeof TABS)[number]["key"];
|
||||
@@ -155,7 +159,6 @@ export default async function LeaderboardPage({
|
||||
const active: TabKey = TABS.some((tab) => tab.key === type)
|
||||
? (type as TabKey)
|
||||
: "credits";
|
||||
const activeTab = TABS.find((tab) => tab.key === active) ?? TABS[0];
|
||||
|
||||
const rows = await (active === "credits"
|
||||
? loadCreditsRows()
|
||||
@@ -166,10 +169,10 @@ export default async function LeaderboardPage({
|
||||
SETTINGS_FIELD[active as keyof typeof SETTINGS_FIELD],
|
||||
));
|
||||
|
||||
const valueOnly =
|
||||
active === "achievement" ||
|
||||
active === "respects" ||
|
||||
active === "onlinetime";
|
||||
const activeCurrency: CurrencyKind | null =
|
||||
active === "credits" || active === "diamonds" || active === "duckets"
|
||||
? active
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
@@ -226,16 +229,20 @@ export default async function LeaderboardPage({
|
||||
</Link>
|
||||
</td>
|
||||
<td style={{ textAlign: "right" }}>
|
||||
{valueOnly ? (
|
||||
<strong>{formatValue(active, row.value)}</strong>
|
||||
) : (
|
||||
{activeCurrency ? (
|
||||
<span
|
||||
className="currency"
|
||||
style={{ display: "inline-flex" }}
|
||||
>
|
||||
<span className={`coin ${activeTab.coin}`} />
|
||||
<CurrencyIcon
|
||||
kind={activeCurrency}
|
||||
size={22}
|
||||
alt=""
|
||||
/>
|
||||
{row.value.toLocaleString()}
|
||||
</span>
|
||||
) : (
|
||||
<strong>{formatValue(active, row.value)}</strong>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
OnlineBadge,
|
||||
RankBadge,
|
||||
} from "@/components/public/ui";
|
||||
import { CurrencyIcon } from "@/components/shared/currency-icon";
|
||||
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
|
||||
import { db, User } from "@/lib/db";
|
||||
|
||||
@@ -69,7 +70,7 @@ export default async function RankingsPage() {
|
||||
<Link href={`/u/${u.username}`}>{u.username}</Link>
|
||||
</h3>
|
||||
<p className="currency" style={{ margin: "0 0 0.3rem" }}>
|
||||
<span className="coin credits">c</span>
|
||||
<CurrencyIcon kind="credits" size={22} alt="" />
|
||||
{u.credits.toLocaleString()}
|
||||
</p>
|
||||
<OnlineBadge online={u.online === "1"} />
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getTranslations } from "next-intl/server";
|
||||
import type { CSSProperties } from "react";
|
||||
import { buyShopArticle } from "@/actions/shop";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { CurrencyIcon } from "@/components/shared/currency-icon";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { db, WebsiteShopArticles, WebsiteShopCategories } from "@/lib/db";
|
||||
import { excerpt } from "@/lib/format";
|
||||
@@ -277,19 +278,19 @@ export default async function ShopPage({
|
||||
>
|
||||
{a.credits ? (
|
||||
<span className="currency">
|
||||
<span className="coin credits">cr</span>
|
||||
<CurrencyIcon kind="credits" size={22} alt="" />
|
||||
{a.credits.toLocaleString()}
|
||||
</span>
|
||||
) : null}
|
||||
{a.duckets ? (
|
||||
<span className="currency">
|
||||
<span className="coin duckets">du</span>
|
||||
<CurrencyIcon kind="duckets" size={22} alt="" />
|
||||
{a.duckets.toLocaleString()}
|
||||
</span>
|
||||
) : null}
|
||||
{a.diamonds ? (
|
||||
<span className="currency">
|
||||
<span className="coin diamonds">di</span>
|
||||
<CurrencyIcon kind="diamonds" size={22} alt="" />
|
||||
{a.diamonds.toLocaleString()}
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -620,15 +620,6 @@ tbody tr:hover {
|
||||
border: 1px solid color-mix(in srgb, var(--color-text-muted) 15%, transparent);
|
||||
color: var(--color-pill-text-readable, var(--color-text-readable));
|
||||
}
|
||||
.currency .coin {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 999px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #fff;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
.nav-credit-icon {
|
||||
background: #e9b124 url("/assets/images/icons/currency/credits.png") center
|
||||
no-repeat;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { CurrencyIcon } from "./currency-icon";
|
||||
|
||||
const publicCurrencyFiles = [
|
||||
"src/app/(site)/rankings/page.tsx",
|
||||
"src/app/(site)/leaderboard/page.tsx",
|
||||
"src/app/(site)/shop/page.tsx",
|
||||
"src/app/(site)/draw-badge/page.tsx",
|
||||
];
|
||||
|
||||
function source(path: string): string {
|
||||
return readFileSync(resolve(process.cwd(), path), "utf8");
|
||||
}
|
||||
|
||||
describe("public currency presentation contract", () => {
|
||||
it.each([
|
||||
["credits", "/assets/images/icons/currency/credits.png"],
|
||||
["duckets", "/assets/images/icons/currency/duckets.png"],
|
||||
["diamonds", "/assets/images/icons/currency/diamonds.png"],
|
||||
] as const)("renders the %s asset", (kind, expectedSrc) => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CurrencyIcon kind={kind} size={22} alt="" />,
|
||||
);
|
||||
expect(html).toContain(expectedSrc);
|
||||
expect(html).toContain('width="22"');
|
||||
expect(html).toContain('height="22"');
|
||||
expect(html).toContain('alt=""');
|
||||
});
|
||||
|
||||
it.each(publicCurrencyFiles)("removes placeholder coins from %s", (path) => {
|
||||
const contents = source(path);
|
||||
expect(contents).not.toMatch(/className=(?:"coin|\{`coin)/);
|
||||
expect(contents).toContain("<CurrencyIcon");
|
||||
});
|
||||
|
||||
it("removes the obsolete coin CSS contract", () => {
|
||||
expect(source("src/app/globals.css")).not.toContain(".currency .coin");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user