refactor: add leveling view layer

Create leveling.view.ts with UI logic extracted from leaderboard command:
- getLeaderboardEmbed() for leaderboard display (XP and Balance)
- getMedalEmoji() helper for ranking medals (🥇🥈🥉)
- formatLeaderEntry() helper for entry formatting with null safety

Updated leaderboard.ts to use view functions instead of inline formatting.
This commit is contained in:
syntaxbullet
2025-12-24 22:09:04 +01:00
parent 7d6912cdee
commit 1523a392c2
2 changed files with 51 additions and 8 deletions

View File

@@ -3,7 +3,8 @@ import { SlashCommandBuilder } from "discord.js";
import { DrizzleClient } from "@/lib/DrizzleClient"; import { DrizzleClient } from "@/lib/DrizzleClient";
import { users } from "@/db/schema"; import { users } from "@/db/schema";
import { desc } from "drizzle-orm"; import { desc } from "drizzle-orm";
import { createWarningEmbed, createBaseEmbed } from "@lib/embeds"; import { createWarningEmbed } from "@lib/embeds";
import { getLeaderboardEmbed } from "@/modules/leveling/leveling.view";
export const leaderboard = createCommand({ export const leaderboard = createCommand({
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
@@ -34,13 +35,7 @@ export const leaderboard = createCommand({
return; return;
} }
const description = leaders.map((user, index) => { const embed = getLeaderboardEmbed(leaders, isXp ? 'xp' : 'balance');
const medal = index === 0 ? "🥇" : index === 1 ? "🥈" : index === 2 ? "🥉" : `${index + 1}.`;
const value = isXp ? `Lvl ${user.level} (${user.xp} XP)` : `${user.balance} 🪙`;
return `${medal} **${user.username}** — ${value}`;
}).join("\n");
const embed = createBaseEmbed(isXp ? "🏆 XP Leaderboard" : "💰 Richest Players", description, "Gold");
await interaction.editReply({ embeds: [embed] }); await interaction.editReply({ embeds: [embed] });
} }

View File

@@ -0,0 +1,48 @@
import { EmbedBuilder } from "discord.js";
/**
* User data for leaderboard display
*/
interface LeaderboardUser {
username: string;
level: number | null;
xp: bigint | null;
balance: bigint | null;
}
/**
* Returns the appropriate medal emoji for a ranking position
*/
function getMedalEmoji(index: number): string {
if (index === 0) return "🥇";
if (index === 1) return "🥈";
if (index === 2) return "🥉";
return `${index + 1}.`;
}
/**
* Formats a single leaderboard entry based on type
*/
function formatLeaderEntry(user: LeaderboardUser, index: number, type: 'xp' | 'balance'): string {
const medal = getMedalEmoji(index);
const value = type === 'xp'
? `Lvl ${user.level ?? 1} (${user.xp ?? 0n} XP)`
: `${user.balance ?? 0n} 🪙`;
return `${medal} **${user.username}** — ${value}`;
}
/**
* Creates a leaderboard embed for either XP or Balance rankings
*/
export function getLeaderboardEmbed(leaders: LeaderboardUser[], type: 'xp' | 'balance'): EmbedBuilder {
const description = leaders.map((user, index) =>
formatLeaderEntry(user, index, type)
).join("\n");
const title = type === 'xp' ? "🏆 XP Leaderboard" : "💰 Richest Players";
return new EmbedBuilder()
.setTitle(title)
.setDescription(description)
.setColor(0xFFD700); // Gold
}