diff --git a/src/lib/handlers/ComponentInteractionHandler.ts b/src/lib/handlers/ComponentInteractionHandler.ts index edaeb62..b48501e 100644 --- a/src/lib/handlers/ComponentInteractionHandler.ts +++ b/src/lib/handlers/ComponentInteractionHandler.ts @@ -1,11 +1,14 @@ -import { ButtonInteraction, StringSelectMenuInteraction, ModalSubmitInteraction } from "discord.js"; +import { ButtonInteraction, StringSelectMenuInteraction, ModalSubmitInteraction, MessageFlags } from "discord.js"; import { logger } from "@lib/logger"; +import { UserError } from "@lib/errors"; +import { createErrorEmbed } from "@lib/embeds"; type ComponentInteraction = ButtonInteraction | StringSelectMenuInteraction | ModalSubmitInteraction; /** * Handles component interactions (buttons, select menus, modals) * Routes to appropriate handlers based on customId patterns + * Provides centralized error handling with UserError differentiation */ export class ComponentInteractionHandler { static async handle(interaction: ComponentInteraction): Promise { @@ -17,12 +20,59 @@ export class ComponentInteractionHandler { const handlerMethod = module[route.method]; if (typeof handlerMethod === 'function') { - await handlerMethod(interaction); - return; + try { + await handlerMethod(interaction); + return; + } catch (error) { + await this.handleError(interaction, error, route.method); + return; + } } else { logger.error(`Handler method ${route.method} not found in module`); } } } } + + /** + * Handles errors from interaction handlers + * Differentiates between UserError (user-facing) and system errors + */ + private static async handleError( + interaction: ComponentInteraction, + error: unknown, + handlerName: string + ): Promise { + const isUserError = error instanceof UserError; + + // Determine error message + const errorMessage = isUserError + ? (error as Error).message + : 'An unexpected error occurred. Please try again later.'; + + // Log system errors (non-user errors) for debugging + if (!isUserError) { + logger.error(`Error in ${handlerName}:`, error); + } + + const errorEmbed = createErrorEmbed(errorMessage); + + try { + // Handle different interaction states + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + embeds: [errorEmbed], + flags: MessageFlags.Ephemeral + }); + } else { + await interaction.reply({ + embeds: [errorEmbed], + flags: MessageFlags.Ephemeral + }); + } + } catch (replyError) { + // If we can't send a reply, log it + logger.error(`Failed to send error response in ${handlerName}:`, replyError); + } + } } diff --git a/src/lib/interaction.routes.ts b/src/lib/interaction.routes.ts index 4bf6fc8..9a4699b 100644 --- a/src/lib/interaction.routes.ts +++ b/src/lib/interaction.routes.ts @@ -19,11 +19,14 @@ interface InteractionRoute { } export const interactionRoutes: InteractionRoute[] = [ + // --- TRADE MODULE --- { predicate: (i) => i.customId.startsWith("trade_") || i.customId === "amount", handler: () => import("@/modules/trade/trade.interaction"), method: 'handleTradeInteraction' }, + + // --- ECONOMY MODULE --- { predicate: (i) => i.isButton() && i.customId.startsWith("shop_buy_"), handler: () => import("@/modules/economy/shop.interaction"), @@ -34,16 +37,22 @@ export const interactionRoutes: InteractionRoute[] = [ handler: () => import("@/modules/economy/lootdrop.interaction"), method: 'handleLootdropInteraction' }, + + // --- ADMIN MODULE --- { predicate: (i) => i.customId.startsWith("createitem_"), handler: () => import("@/modules/admin/item_wizard"), method: 'handleItemWizardInteraction' }, + + // --- USER MODULE --- { predicate: (i) => i.isButton() && i.customId === "enrollment", handler: () => import("@/modules/user/enrollment.interaction"), method: 'handleEnrollmentInteraction' }, + + // --- FEEDBACK MODULE --- { predicate: (i) => i.customId.startsWith("feedback_"), handler: () => import("@/modules/feedback/feedback.interaction"),