refactor: Extract UI component creation into new view files for lootdrop, trade, item wizard, and enrollment.
This commit is contained in:
@@ -1,24 +1,18 @@
|
||||
import {
|
||||
type Interaction,
|
||||
ButtonInteraction,
|
||||
ModalSubmitInteraction,
|
||||
StringSelectMenuInteraction,
|
||||
type Interaction,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
StringSelectMenuBuilder,
|
||||
ModalBuilder,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
ThreadChannel,
|
||||
TextChannel,
|
||||
EmbedBuilder
|
||||
} from "discord.js";
|
||||
import { TradeService } from "./trade.service";
|
||||
import { inventoryService } from "@/modules/inventory/inventory.service";
|
||||
import { createErrorEmbed, createWarningEmbed, createSuccessEmbed, createInfoEmbed, createBaseEmbed } from "@lib/embeds";
|
||||
import { createErrorEmbed, createWarningEmbed, createSuccessEmbed, createInfoEmbed } from "@lib/embeds";
|
||||
import { getTradeDashboard, getTradeMoneyModal, getItemSelectMenu, getTradeCompletedEmbed } from "./trade.view";
|
||||
|
||||
|
||||
const EMBED_COLOR = 0xFFD700; // Gold
|
||||
|
||||
export async function handleTradeInteraction(interaction: Interaction) {
|
||||
if (!interaction.isButton() && !interaction.isStringSelectMenu() && !interaction.isModalSubmit()) return;
|
||||
@@ -91,20 +85,7 @@ async function handleLock(interaction: ButtonInteraction | StringSelectMenuInter
|
||||
|
||||
async function handleAddMoneyClick(interaction: Interaction) {
|
||||
if (!interaction.isButton()) return;
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId('trade_money_modal')
|
||||
.setTitle('Add Money');
|
||||
|
||||
const input = new TextInputBuilder()
|
||||
.setCustomId('amount')
|
||||
.setLabel("Amount to trade")
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder("100")
|
||||
.setRequired(true);
|
||||
|
||||
const row = new ActionRowBuilder<TextInputBuilder>().addComponents(input);
|
||||
modal.addComponents(row);
|
||||
|
||||
const modal = getTradeMoneyModal();
|
||||
await interaction.showModal(modal);
|
||||
}
|
||||
|
||||
@@ -131,17 +112,11 @@ async function handleAddItemClick(interaction: ButtonInteraction, threadId: stri
|
||||
const options = inventory.slice(0, 25).map(entry => ({
|
||||
label: `${entry.item.name} (${entry.quantity})`,
|
||||
value: entry.item.id.toString(),
|
||||
description: `Rarity: ${entry.item.rarity}`
|
||||
description: `Rarity: ${entry.item.rarity} `
|
||||
}));
|
||||
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId('trade_select_item')
|
||||
.setPlaceholder('Select an item to add')
|
||||
.addOptions(options);
|
||||
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(select);
|
||||
|
||||
await interaction.reply({ content: "Select an item to add:", components: [row], ephemeral: true });
|
||||
const { components } = getItemSelectMenu(options, 'trade_select_item', 'Select an item to add');
|
||||
await interaction.reply({ content: "Select an item to add:", components, ephemeral: true });
|
||||
}
|
||||
|
||||
async function handleItemSelect(interaction: StringSelectMenuInteraction, threadId: string) {
|
||||
@@ -175,14 +150,8 @@ async function handleRemoveItemClick(interaction: ButtonInteraction, threadId: s
|
||||
value: i.id.toString(),
|
||||
}));
|
||||
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId('trade_remove_item_select')
|
||||
.setPlaceholder('Select an item to remove')
|
||||
.addOptions(options);
|
||||
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(select);
|
||||
|
||||
await interaction.reply({ content: "Select an item to remove:", components: [row], ephemeral: true });
|
||||
const { components } = getItemSelectMenu(options, 'trade_remove_item_select', 'Select an item to remove');
|
||||
await interaction.reply({ content: "Select an item to remove:", components, ephemeral: true });
|
||||
}
|
||||
|
||||
async function handleRemoveItemSelect(interaction: StringSelectMenuInteraction, threadId: string) {
|
||||
@@ -207,13 +176,7 @@ export async function updateTradeDashboard(interaction: Interaction, threadId: s
|
||||
// Execute Trade
|
||||
try {
|
||||
await TradeService.executeTrade(threadId);
|
||||
const embed = createBaseEmbed("✅ Trade Completed", undefined, "Green")
|
||||
.addFields(
|
||||
{ name: session.userA.username, value: formatOffer(session.userA), inline: true },
|
||||
{ name: session.userB.username, value: formatOffer(session.userB), inline: true }
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
const embed = getTradeCompletedEmbed(session);
|
||||
await updateDashboardMessage(interaction, { embeds: [embed], components: [] });
|
||||
|
||||
// Notify and Schedule Cleanup
|
||||
@@ -221,7 +184,7 @@ export async function updateTradeDashboard(interaction: Interaction, threadId: s
|
||||
const successEmbed = createSuccessEmbed("Trade executed successfully. Items and funds have been transferred.", "Trade Complete");
|
||||
await scheduleThreadCleanup(
|
||||
interaction.channel,
|
||||
`🎉 Trade successful! <@${session.userA.id}> <@${session.userB.id}>\nThis thread will be deleted in 10 seconds.`,
|
||||
`🎉 Trade successful! < @${session.userA.id}> <@${session.userB.id}>\nThis thread will be deleted in 10 seconds.`,
|
||||
10000,
|
||||
successEmbed
|
||||
);
|
||||
@@ -244,31 +207,8 @@ export async function updateTradeDashboard(interaction: Interaction, threadId: s
|
||||
}
|
||||
|
||||
// Build Status Embed
|
||||
const embed = createBaseEmbed("🤝 Trading Session", undefined, EMBED_COLOR)
|
||||
.addFields(
|
||||
{
|
||||
name: `${session.userA.username} ${session.userA.locked ? '✅ (Ready)' : '✏️ (Editing)'}`,
|
||||
value: formatOffer(session.userA),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: `${session.userB.username} ${session.userB.locked ? '✅ (Ready)' : '✏️ (Editing)'}`,
|
||||
value: formatOffer(session.userB),
|
||||
inline: true
|
||||
}
|
||||
)
|
||||
.setFooter({ text: "Both parties must click Lock to confirm trade." });
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents(
|
||||
new ButtonBuilder().setCustomId('trade_add_item').setLabel('Add Item').setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder().setCustomId('trade_add_money').setLabel('Add Money').setStyle(ButtonStyle.Success),
|
||||
new ButtonBuilder().setCustomId('trade_remove_item').setLabel('Remove Item').setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder().setCustomId('trade_lock').setLabel('Lock / Unlock').setStyle(ButtonStyle.Primary),
|
||||
new ButtonBuilder().setCustomId('trade_cancel').setLabel('Cancel').setStyle(ButtonStyle.Danger),
|
||||
);
|
||||
|
||||
await updateDashboardMessage(interaction, { embeds: [embed], components: [row] });
|
||||
const { embeds, components } = getTradeDashboard(session);
|
||||
await updateDashboardMessage(interaction, { embeds, components });
|
||||
}
|
||||
|
||||
async function updateDashboardMessage(interaction: Interaction, payload: any) {
|
||||
@@ -296,17 +236,7 @@ async function updateDashboardMessage(interaction: Interaction, payload: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatOffer(participant: any) {
|
||||
let text = "";
|
||||
if (participant.offer.money > 0n) {
|
||||
text += `💰 ${participant.offer.money} 🪙\n`;
|
||||
}
|
||||
if (participant.offer.items.length > 0) {
|
||||
text += participant.offer.items.map((i: any) => `- ${i.name} (x${i.quantity})`).join("\n");
|
||||
}
|
||||
if (text === "") text = "*Empty Offer*";
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
async function scheduleThreadCleanup(channel: ThreadChannel | TextChannel, message: string, delayMs: number = 10000, embed?: EmbedBuilder) {
|
||||
try {
|
||||
@@ -318,7 +248,7 @@ async function scheduleThreadCleanup(channel: ThreadChannel | TextChannel, messa
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
if (channel.isThread()) {
|
||||
console.log(`Deleting thread: ${channel.id}`);
|
||||
console.log(`Deleting thread: ${channel.id} `);
|
||||
await channel.delete("Trade Session Ended");
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
85
src/modules/trade/trade.view.ts
Normal file
85
src/modules/trade/trade.view.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ModalBuilder, StringSelectMenuBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
|
||||
import { createBaseEmbed } from "@lib/embeds";
|
||||
import type { TradeSession, TradeParticipant } from "./trade.types";
|
||||
|
||||
const EMBED_COLOR = 0xFFD700; // Gold
|
||||
|
||||
function formatOffer(participant: TradeParticipant) {
|
||||
let text = "";
|
||||
if (participant.offer.money > 0n) {
|
||||
text += `💰 ${participant.offer.money} 🪙\n`;
|
||||
}
|
||||
if (participant.offer.items.length > 0) {
|
||||
text += participant.offer.items.map((i) => `- ${i.name} (x${i.quantity})`).join("\n");
|
||||
}
|
||||
if (text === "") text = "*Empty Offer*";
|
||||
return text;
|
||||
}
|
||||
|
||||
export function getTradeDashboard(session: TradeSession) {
|
||||
const embed = createBaseEmbed("🤝 Trading Session", undefined, EMBED_COLOR)
|
||||
.addFields(
|
||||
{
|
||||
name: `${session.userA.username} ${session.userA.locked ? '✅ (Ready)' : '✏️ (Editing)'}`,
|
||||
value: formatOffer(session.userA),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: `${session.userB.username} ${session.userB.locked ? '✅ (Ready)' : '✏️ (Editing)'}`,
|
||||
value: formatOffer(session.userB),
|
||||
inline: true
|
||||
}
|
||||
)
|
||||
.setFooter({ text: "Both parties must click Lock to confirm trade." });
|
||||
|
||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents(
|
||||
new ButtonBuilder().setCustomId('trade_add_item').setLabel('Add Item').setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder().setCustomId('trade_add_money').setLabel('Add Money').setStyle(ButtonStyle.Success),
|
||||
new ButtonBuilder().setCustomId('trade_remove_item').setLabel('Remove Item').setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder().setCustomId('trade_lock').setLabel('Lock / Unlock').setStyle(ButtonStyle.Primary),
|
||||
new ButtonBuilder().setCustomId('trade_cancel').setLabel('Cancel').setStyle(ButtonStyle.Danger),
|
||||
);
|
||||
|
||||
return { embeds: [embed], components: [row] };
|
||||
}
|
||||
|
||||
export function getTradeCompletedEmbed(session: TradeSession) {
|
||||
const embed = createBaseEmbed("✅ Trade Completed", undefined, "Green")
|
||||
.addFields(
|
||||
{ name: session.userA.username, value: formatOffer(session.userA), inline: true },
|
||||
{ name: session.userB.username, value: formatOffer(session.userB), inline: true }
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
export function getTradeMoneyModal() {
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId('trade_money_modal')
|
||||
.setTitle('Add Money');
|
||||
|
||||
const input = new TextInputBuilder()
|
||||
.setCustomId('amount')
|
||||
.setLabel("Amount to trade")
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder("100")
|
||||
.setRequired(true);
|
||||
|
||||
const row = new ActionRowBuilder<TextInputBuilder>().addComponents(input);
|
||||
modal.addComponents(row);
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
export function getItemSelectMenu(items: { label: string, value: string, description?: string }[], customId: string, placeholder: string) {
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId(customId)
|
||||
.setPlaceholder(placeholder)
|
||||
.addOptions(items);
|
||||
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(select);
|
||||
|
||||
return { components: [row] };
|
||||
}
|
||||
Reference in New Issue
Block a user