Use Habbo currency PNGs for admin credits, duckets, and diamonds icons.
Local Build and Deploy / deploy (push) Successful in 56s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-17 18:16:01 +02:00
1 parent b42a313825
commit d7405238ea
7 files changed
+118 -26

No files matched your search

+5 -4
View File
@@ -1,7 +1,8 @@
import { ArrowLeftRight, Coins, TrendingDown, TrendingUp } from "lucide-react"; import { ArrowLeftRight, TrendingDown, TrendingUp } from "lucide-react";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { RevenueChart } from "@/components/admin/revenue-chart"; import { RevenueChart } from "@/components/admin/revenue-chart";
import { StatsCard } from "@/components/admin/stats-card"; import { StatsCard } from "@/components/admin/stats-card";
import { CurrencyIcon } from "@/components/shared/currency-icon";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma"; import { prisma } from "@/lib/prisma";
@@ -161,19 +162,19 @@ export default async function EconomyPage() {
title="Credits in Circulation" title="Credits in Circulation"
value={data.totalCredits.toLocaleString()} value={data.totalCredits.toLocaleString()}
description="Total credits across all users" description="Total credits across all users"
icon={<Coins className="h-4 w-4" />} icon={<CurrencyIcon kind="credits" size={16} />}
/> />
<StatsCard <StatsCard
title="Pixels in Circulation" title="Pixels in Circulation"
value={data.totalPixels.toLocaleString()} value={data.totalPixels.toLocaleString()}
description="Total pixels across all users" description="Total pixels across all users"
icon={<TrendingUp className="h-4 w-4" />} icon={<CurrencyIcon kind="pixels" size={16} />}
/> />
<StatsCard <StatsCard
title="Points in Circulation" title="Points in Circulation"
value={data.totalPoints.toLocaleString()} value={data.totalPoints.toLocaleString()}
description="Total points across all users" description="Total points across all users"
icon={<TrendingDown className="h-4 w-4" />} icon={<CurrencyIcon kind="diamonds" size={16} />}
/> />
<StatsCard <StatsCard
title="Weekly Trades" title="Weekly Trades"
@@ -47,6 +47,10 @@ import {
import { BulkImportItems } from "@/components/admin/catalog/bulk-import-items"; import { BulkImportItems } from "@/components/admin/catalog/bulk-import-items";
import { ItemsShopPreview } from "@/components/admin/catalog/items-shop-preview"; import { ItemsShopPreview } from "@/components/admin/catalog/items-shop-preview";
import { QuickAddFurni } from "@/components/admin/catalog/quick-add-furni"; import { QuickAddFurni } from "@/components/admin/catalog/quick-add-furni";
import {
CurrencyIcon,
currencyKindFromPointsType,
} from "@/components/shared/currency-icon";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
@@ -1257,15 +1261,24 @@ export function CatalogItemsTable({
</span> </span>
</div> </div>
) : ( ) : (
<div className="text-xs"> <div className="text-xs flex flex-wrap items-center gap-1.5">
{item.costCredits > 0 && ( {item.costCredits > 0 && (
<span className="text-[var(--admin-warning)]"> <span className="inline-flex items-center gap-0.5 text-[var(--admin-warning)]">
{item.costCredits}c{" "} <CurrencyIcon kind="credits" size={12} />
{item.costCredits}
</span> </span>
)} )}
{item.costPoints > 0 && ( {item.costPoints > 0 && (
<span className={pts.color}> <span
{item.costPoints} {pts.label}{" "} className={`inline-flex items-center gap-0.5 ${pts.color}`}
>
<CurrencyIcon
kind={currencyKindFromPointsType(
item.pointsType,
)}
size={12}
/>
{item.costPoints}
</span> </span>
)} )}
{item.costCredits === 0 && {item.costCredits === 0 &&
@@ -2,7 +2,6 @@
import { import {
Check, Check,
Coins,
Copy, Copy,
KeyRound, KeyRound,
MessageCircle, MessageCircle,
@@ -19,6 +18,7 @@ import {
sendCredits, sendCredits,
unmuteUser, unmuteUser,
} from "@/actions/users"; } from "@/actions/users";
import { CurrencyIcon } from "@/components/shared/currency-icon";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
@@ -229,7 +229,8 @@ export function UserQuickActions({
disabled={isPending} disabled={isPending}
size="sm" size="sm"
> >
<Coins className="mr-1.5 h-3.5 w-3.5" /> Send Credits <CurrencyIcon kind="credits" size={14} className="mr-1.5" />{" "}
Send Credits
</Button> </Button>
</div> </div>
</div> </div>
@@ -1,20 +1,17 @@
import { import {
ArrowLeftRight, ArrowLeftRight,
Coins,
Diamond,
DoorOpen, DoorOpen,
Eye, Eye,
Gem,
MessageSquare, MessageSquare,
Pencil, Pencil,
Shield, Shield,
Sparkles,
Wifi, Wifi,
WifiOff, WifiOff,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { AdminBreadcrumb } from "@/components/admin/admin-breadcrumb"; import { AdminBreadcrumb } from "@/components/admin/admin-breadcrumb";
import { UserWatchToggle } from "@/components/admin/user-watch-toggle"; import { UserWatchToggle } from "@/components/admin/user-watch-toggle";
import { CurrencyIcon } from "@/components/shared/currency-icon";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { getAvatarUrl } from "@/lib/imager"; import { getAvatarUrl } from "@/lib/imager";
@@ -159,28 +156,28 @@ export function UserHero({
{/* Currency row */} {/* Currency row */}
<div className="flex flex-wrap gap-3 pt-1"> <div className="flex flex-wrap gap-3 pt-1">
<div className="flex items-center gap-1.5 text-sm"> <div className="flex items-center gap-1.5 text-sm">
<Coins className="h-4 w-4 text-[var(--admin-warning)]" /> <CurrencyIcon kind="credits" size={18} />
<span className="font-semibold"> <span className="font-semibold">
{user.credits.toLocaleString()} {user.credits.toLocaleString()}
</span> </span>
<span className="text-muted-foreground text-xs">credits</span> <span className="text-muted-foreground text-xs">credits</span>
</div> </div>
<div className="flex items-center gap-1.5 text-sm"> <div className="flex items-center gap-1.5 text-sm">
<Diamond className="h-4 w-4 text-[var(--admin-info)]" /> <CurrencyIcon kind="diamonds" size={18} />
<span className="font-semibold"> <span className="font-semibold">
{diamonds.toLocaleString()} {diamonds.toLocaleString()}
</span> </span>
<span className="text-muted-foreground text-xs">diamonds</span> <span className="text-muted-foreground text-xs">diamonds</span>
</div> </div>
<div className="flex items-center gap-1.5 text-sm"> <div className="flex items-center gap-1.5 text-sm">
<Sparkles className="h-4 w-4 text-[var(--admin-accent-text)]" /> <CurrencyIcon kind="duckets" size={18} />
<span className="font-semibold"> <span className="font-semibold">
{duckets.toLocaleString()} {duckets.toLocaleString()}
</span> </span>
<span className="text-muted-foreground text-xs">duckets</span> <span className="text-muted-foreground text-xs">duckets</span>
</div> </div>
<div className="flex items-center gap-1.5 text-sm"> <div className="flex items-center gap-1.5 text-sm">
<Gem className="h-4 w-4 text-[var(--admin-error)]" /> <CurrencyIcon kind="pixels" size={18} />
<span className="font-semibold"> <span className="font-semibold">
{user.pixels.toLocaleString()} {user.pixels.toLocaleString()}
</span> </span>
+17 -3
View File
@@ -11,6 +11,7 @@ import {
bulkUnban, bulkUnban,
} from "@/actions/bulk-users"; } from "@/actions/bulk-users";
import { DataTable } from "@/components/admin/data-table"; import { DataTable } from "@/components/admin/data-table";
import { CurrencyIcon } from "@/components/shared/currency-icon";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -436,9 +437,22 @@ export function UsersTable({ data, canEdit, canBan }: UsersTableProps) {
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="credits">Credits</SelectItem> <SelectItem value="credits">
<SelectItem value="pixels">Pixels (Duckets)</SelectItem> <span className="inline-flex items-center gap-2">
<SelectItem value="points">Points (Diamonds)</SelectItem> <CurrencyIcon kind="credits" size={14} /> Credits
</span>
</SelectItem>
<SelectItem value="pixels">
<span className="inline-flex items-center gap-2">
<CurrencyIcon kind="pixels" size={14} /> Pixels (Duckets)
</span>
</SelectItem>
<SelectItem value="points">
<span className="inline-flex items-center gap-2">
<CurrencyIcon kind="diamonds" size={14} /> Points
(Diamonds)
</span>
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@@ -3,6 +3,10 @@
import { Crown, Image as ImageIcon, Package, Sparkles } from "lucide-react"; import { Crown, Image as ImageIcon, Package, Sparkles } from "lucide-react";
import type { CatalogItemData } from "@/app/admin/catalog/[id]/catalog-items-table/types"; import type { CatalogItemData } from "@/app/admin/catalog/[id]/catalog-items-table/types";
import { getPointsLabel } from "@/app/admin/catalog/[id]/catalog-items-table/types"; import { getPointsLabel } from "@/app/admin/catalog/[id]/catalog-items-table/types";
import {
CurrencyIcon,
currencyKindFromPointsType,
} from "@/components/shared/currency-icon";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { getHabboCdnIconUrl, getLocalIconUrl } from "@/lib/furni/classname"; import { getHabboCdnIconUrl, getLocalIconUrl } from "@/lib/furni/classname";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -113,13 +117,20 @@ export function ItemsShopPreview({
</p> </p>
<div className="mt-0.5 flex items-center gap-1.5 text-[10px] font-mono"> <div className="mt-0.5 flex items-center gap-1.5 text-[10px] font-mono">
{item.costCredits > 0 && ( {item.costCredits > 0 && (
<span className="text-[var(--admin-warning)] dark:text-[var(--admin-warning)]"> <span className="inline-flex items-center gap-0.5 text-[var(--admin-warning)] dark:text-[var(--admin-warning)]">
{item.costCredits}c <CurrencyIcon kind="credits" size={12} />
{item.costCredits}
</span> </span>
)} )}
{item.costPoints > 0 && ( {item.costPoints > 0 && (
<span className={points.color}> <span
{item.costPoints} {points.label.slice(0, 3)} className={`inline-flex items-center gap-0.5 ${points.color}`}
>
<CurrencyIcon
kind={currencyKindFromPointsType(item.pointsType)}
size={12}
/>
{item.costPoints}
</span> </span>
)} )}
{item.costCredits === 0 && item.costPoints === 0 && ( {item.costCredits === 0 && item.costPoints === 0 && (
+55
View File
@@ -0,0 +1,55 @@
/**
* Habbo-style currency icons used across admin (and reusable on public).
* Files live at /assets/images/icons/currency/{name}.png
*/
export type CurrencyKind =
| "credits"
| "duckets"
| "diamonds"
| "pixels"
| "points";
const CURRENCY_FILES: Record<CurrencyKind, string> = {
credits: "credits",
duckets: "duckets",
diamonds: "diamonds",
// No dedicated pixels asset — activity points share the duckets icon.
pixels: "duckets",
points: "duckets",
};
/** Map emulator `points_type` (catalog / users_currency) to an icon. */
export function currencyKindFromPointsType(type: number): CurrencyKind {
if (type === 5) return "diamonds";
return "duckets";
}
export function currencyIconSrc(kind: CurrencyKind): string {
return `/assets/images/icons/currency/${CURRENCY_FILES[kind]}.png`;
}
export function CurrencyIcon({
kind,
size = 16,
className = "",
alt,
}: {
kind: CurrencyKind;
size?: number;
className?: string;
alt?: string;
}) {
return (
// biome-ignore lint/performance/noImgElement: small pixel art asset, not Next Image optimized
<img
src={currencyIconSrc(kind)}
alt={alt ?? kind}
width={size}
height={size}
className={`inline-block shrink-0 object-contain image-pixelated ${className}`.trim()}
style={{ width: size, height: size, imageRendering: "pixelated" }}
draggable={false}
/>
);
}