From 936a5e9fb62daec9573275646cf4299c54caeeeb Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 18:53:11 +0300 Subject: [PATCH 01/19] Update ticket.js --- src/commands/Ticket/ticket.js | 412 +++++++++++++++++----------------- 1 file changed, 204 insertions(+), 208 deletions(-) diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js index 982747b118..4c24a12b16 100644 --- a/src/commands/Ticket/ticket.js +++ b/src/commands/Ticket/ticket.js @@ -1,6 +1,7 @@ +```js import { getColor } from '../../config/bot.js'; -import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, MessageFlags } from 'discord.js'; -import { createEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; +import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, MessageFlags } from 'discord.js'; +import { createEmbed, successEmbed } from '../../utils/embeds.js'; import { getGuildConfig, setGuildConfig } from '../../services/config/guildConfig.js'; import { InteractionHelper } from '../../utils/interactionHelper.js'; import { logger } from '../../utils/logger.js'; @@ -16,100 +17,87 @@ export default { .addSubcommand((subcommand) => subcommand .setName("setup") - .setDescription( - "Sets up the ticket creation panel in a specified channel.", - ) + .setDescription("Sets up the ticket creation panel in a specified channel.") .addChannelOption((option) => option -.setName("panel_channel") - .setDescription( - "The channel where the ticket panel will be sent.", - ) + .setName("panel_channel") + .setDescription("The channel where the ticket panel will be sent.") .addChannelTypes(ChannelType.GuildText) - .setRequired(true), + .setRequired(true) ) - .addStringOption((option) => option .setName("panel_message") - .setDescription( - "The main message/description for the ticket panel.", - ) - .setRequired(true), + .setDescription("The main message/description for the ticket panel.") + .setRequired(true) ) .addStringOption((option) => option .setName("button_label") - .setDescription( - "The label for the ticket creation button (default: Create Ticket)", - ) - .setRequired(false), + .setDescription("The label for the ticket creation button.") + .setRequired(false) ) .addChannelOption((option) => option .setName("category") - .setDescription( - "The category where new tickets will be created (optional).", - ) + .setDescription("The category where new tickets will be created.") .addChannelTypes(ChannelType.GuildCategory) - .setRequired(false), + .setRequired(false) ) .addChannelOption((option) => option .setName("closed_category") - .setDescription( - "The category where closed tickets will be moved (optional).", - ) + .setDescription("The category where closed tickets will be moved.") .addChannelTypes(ChannelType.GuildCategory) - .setRequired(false), + .setRequired(false) ) .addRoleOption((option) => option .setName("staff_role") - .setDescription( - "The role that can access tickets (optional).", - ) - .setRequired(false), + .setDescription("The role that can access tickets and be pinged.") + .setRequired(false) ) .addIntegerOption((option) => option .setName("max_tickets_per_user") - .setDescription("Maximum number of tickets a user can create (default: 3)") + .setDescription("Maximum number of tickets a user can create.") .setMinValue(1) .setMaxValue(10) - .setRequired(false), + .setRequired(false) ) .addBooleanOption((option) => option .setName("dm_on_close") - .setDescription("Send DM to user when their ticket is closed (default: true)") - .setRequired(false), - ), + .setDescription("Send a DM to the user when their ticket is closed.") + .setRequired(false) + ) ) .addSubcommand((subcommand) => subcommand .setName("dashboard") - .setDescription("Open the interactive ticket system dashboard"), + .setDescription("Open the interactive ticket system dashboard") ), + category: "ticket", async execute(interaction, config, client) { - const deferred = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral }); - if (!deferred) { - return; - } + const deferred = await InteractionHelper.safeDefer(interaction, { + flags: MessageFlags.Ephemeral + }); + + if (!deferred) return; - if ( - !interaction.member.permissions.has( - PermissionFlagsBits.ManageChannels, - ) - ) { + if (!interaction.member.permissions.has(PermissionFlagsBits.ManageChannels)) { logger.warn('Ticket command permission denied', { userId: interaction.user.id, guildId: interaction.guildId, commandName: 'ticket' }); - return await replyUserError(interaction, { type: ErrorTypes.PERMISSION, message: 'You need the `Manage Channels` permission for this action.' }); + + return await replyUserError(interaction, { + type: ErrorTypes.PERMISSION, + message: 'You need the `Manage Channels` permission for this action.' + }); } const subcommand = interaction.options.getSubcommand(); @@ -118,182 +106,190 @@ export default { return ticketConfig.execute(interaction, config, client); } - if (subcommand === "setup") { - const existingConfig = await getGuildConfig(client, interaction.guildId); - if (existingConfig?.ticketPanelChannelId) { - return await replyUserError(interaction, { type: ErrorTypes.UNKNOWN, message: `This server already has a ticket system set up (panel in <#${existingConfig.ticketPanelChannelId}>).\n\nOnly one ticket system is supported per server. Use \`/ticket dashboard\` to edit or update the existing setup, or select **Delete System** from the dashboard to remove it and start fresh.` }); - } + if (subcommand !== "setup") return; - const panelChannel = - interaction.options.getChannel("panel_channel"); - const categoryChannel = interaction.options.getChannel("category"); - const closedCategoryChannel = interaction.options.getChannel("closed_category"); - const staffRole = interaction.options.getRole("staff_role"); -const panelMessage = interaction.options.getString("panel_message") || "Click the button below to create a support ticket."; - const buttonLabel = - interaction.options.getString("button_label") || -"Create Ticket"; - const maxTicketsPerUser = interaction.options.getInteger("max_tickets_per_user") || 3; -const dmOnClose = interaction.options.getBoolean("dm_on_close") !== false; - - const setupEmbed = createEmbed({ - title: "Support Tickets", -description: panelMessage, - color: getColor('info') + const existingConfig = await getGuildConfig(client, interaction.guildId); + + if (existingConfig?.ticketPanelChannelId) { + return await replyUserError(interaction, { + type: ErrorTypes.UNKNOWN, + message: `This server already has a ticket system set up (panel in <#${existingConfig.ticketPanelChannelId}>).\n\nUse \`/ticket dashboard\` to edit the existing setup, or select **Delete System** from the dashboard to remove it.` }); + } - const ticketButton = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId("create_ticket") -.setLabel(buttonLabel) - .setStyle(ButtonStyle.Primary) - .setEmoji("📩"), - ); + const panelChannel = interaction.options.getChannel("panel_channel"); + const categoryChannel = interaction.options.getChannel("category"); + const closedCategoryChannel = interaction.options.getChannel("closed_category"); + const staffRole = interaction.options.getRole("staff_role"); - try { - const sentPanel = await panelChannel.send({ - embeds: [setupEmbed], - components: [ticketButton], - }); + const panelMessage = interaction.options.getString("panel_message") + || "Click the button below to create a support ticket."; - if (client.db && interaction.guildId) { - const currentConfig = existingConfig; - currentConfig.ticketCategoryId = categoryChannel ? categoryChannel.id : null; - currentConfig.ticketClosedCategoryId = closedCategoryChannel ? closedCategoryChannel.id : null; - currentConfig.ticketStaffRoleId = staffRole ? staffRole.id : null; - currentConfig.ticketPanelChannelId = panelChannel.id; - currentConfig.ticketPanelMessageId = sentPanel?.id || null; - currentConfig.ticketPanelMessage = panelMessage; - currentConfig.ticketButtonLabel = buttonLabel; - currentConfig.maxTicketsPerUser = maxTicketsPerUser; - currentConfig.dmOnClose = dmOnClose; - - await setGuildConfig(client, interaction.guildId, currentConfig); - logger.info('Ticket configuration saved', { - guildId: interaction.guildId, - categoryId: categoryChannel?.id, - closedCategoryId: closedCategoryChannel?.id, - staffRoleId: staffRole?.id, - maxTickets: maxTicketsPerUser, - dmOnClose: dmOnClose, - }); - } else { - logger.error('Ticket setup: database unavailable, panel sent but configuration was NOT saved', { - guildId: interaction.guildId, - }); - } + const buttonLabel = interaction.options.getString("button_label") + || "Create Ticket"; - let successMessage = `The ticket creation panel has been sent to ${panelChannel}.`; - - if (categoryChannel) { - successMessage += `New tickets will be created in the **${categoryChannel.name}** category.`; - } else { - successMessage += 'New tickets will be created in a new "Tickets" category.'; - } - - if (closedCategoryChannel) { - successMessage += `Closed tickets will be moved to **${closedCategoryChannel.name}**.`; - } - - if (staffRole) { - successMessage += `**${staffRole.name}** role will have access to tickets.`; - } - - successMessage += `\n\n**Max Tickets Per User:** ${maxTicketsPerUser}\n**DM on Close:** ${dmOnClose ? 'Enabled' : 'Disabled'}`; - - await InteractionHelper.safeEditReply(interaction, { - embeds: [ - successEmbed( - "Ticket Panel Set Up", - successMessage, - ), - ], - }); + const maxTicketsPerUser = interaction.options.getInteger("max_tickets_per_user") || 3; + const dmOnClose = interaction.options.getBoolean("dm_on_close") !== false; + + const setupEmbed = createEmbed({ + title: "🎫 Support Tickets", + description: `${panelMessage}\n\nClick the button below and our team will help you as soon as possible.`, + color: "#5865F2", + thumbnail: client.user.displayAvatarURL({ + extension: "png", + size: 256 + }), + image: "https://placehold.co/1200x300/5865F2/FFFFFF.png?text=SUPPORT+TICKETS", + footer: { + text: "Support Center • We're here to help" + } + }); + + const ticketButton = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("create_ticket") + .setLabel(buttonLabel) + .setStyle(ButtonStyle.Primary) + .setEmoji("📩") + ); + + try { + const sentPanel = await panelChannel.send({ + embeds: [setupEmbed], + components: [ticketButton] + }); + + if (client.db && interaction.guildId) { + const currentConfig = existingConfig; + + currentConfig.ticketCategoryId = categoryChannel?.id || null; + currentConfig.ticketClosedCategoryId = closedCategoryChannel?.id || null; + currentConfig.ticketStaffRoleId = staffRole?.id || null; + currentConfig.ticketPanelChannelId = panelChannel.id; + currentConfig.ticketPanelMessageId = sentPanel.id; + currentConfig.ticketPanelMessage = panelMessage; + currentConfig.ticketButtonLabel = buttonLabel; + currentConfig.maxTicketsPerUser = maxTicketsPerUser; + currentConfig.dmOnClose = dmOnClose; - logger.info('Ticket panel setup completed', { - userId: interaction.user.id, - userTag: interaction.user.tag, + await setGuildConfig(client, interaction.guildId, currentConfig); + + logger.info('Ticket configuration saved', { guildId: interaction.guildId, - panelChannelId: panelChannel.id, categoryId: categoryChannel?.id, closedCategoryId: closedCategoryChannel?.id, staffRoleId: staffRole?.id, maxTickets: maxTicketsPerUser, - dmOnClose: dmOnClose, - commandName: 'ticket_setup' + dmOnClose }); - - const logEmbed = createEmbed({ - title: "Ticket System Setup (Configuration Log)", - description: `The ticket panel was set up in ${panelChannel} by ${interaction.user}.`, - color: getColor('warning') - }) - .addFields( - { - name: "Panel Channel", - value: panelChannel.toString(), - inline: true, - }, - { - name: "Ticket Category", - value: categoryChannel - ? categoryChannel.toString() - : "None specified.", - inline: true, - }, - { - name: "Closed Category", - value: closedCategoryChannel - ? closedCategoryChannel.toString() - : "None specified.", - inline: true, - }, - { - name: "Staff Role", - value: staffRole - ? staffRole.toString() - : "None specified.", - inline: true, - }, - { - name: "Max Tickets Per User", - value: maxTicketsPerUser.toString(), - inline: true, - }, - { - name: "DM on Close", - value: dmOnClose ? 'Enabled' : 'Disabled', - inline: true, - }, - { - name: "Moderator", - value: `${interaction.user.tag} (${interaction.user.id})`, - inline: false, - }, - ); - - } catch (error) { - logger.error('Ticket setup error', { - error: error.message, - stack: error.stack, - userId: interaction.user.id, - guildId: interaction.guildId, - commandName: 'ticket_setup' + } else { + logger.error('Ticket setup: database unavailable; configuration was not saved', { + guildId: interaction.guildId }); - if (interaction.deferred || interaction.replied) { - await replyUserError(interaction, { type: ErrorTypes.UNKNOWN, message: 'Could not send the ticket panel or save configuration. Check the bot\'s permissions (especially the ability to send messages in the target channel) and database connection.' }).catch(err => { - logger.error('Failed to send error reply', { - error: err.message, - guildId: interaction.guildId - }); - }); - } else { - await handleInteractionError(interaction, error, { - commandName: 'ticket_setup', - source: 'ticket_setup_command' - }); + } + + let successMessage = `The ticket panel has been sent to ${panelChannel}.\n\n`; + successMessage += categoryChannel + ? `**Ticket Category:** ${categoryChannel.name}\n` + : '**Ticket Category:** Default Tickets category\n'; + + if (closedCategoryChannel) { + successMessage += `**Closed Category:** ${closedCategoryChannel.name}\n`; + } + + successMessage += staffRole + ? `**Staff Role:** ${staffRole} (will be pinged when a ticket opens)\n` + : '**Staff Role:** Not configured\n'; + + successMessage += `**Max Tickets Per User:** ${maxTicketsPerUser}\n`; + successMessage += `**DM on Close:** ${dmOnClose ? 'Enabled' : 'Disabled'}`; + + await InteractionHelper.safeEditReply(interaction, { + embeds: [ + successEmbed("Ticket Panel Set Up", successMessage) + ] + }); + + const logEmbed = createEmbed({ + title: "Ticket System Setup", + description: `The ticket panel was set up in ${panelChannel} by ${interaction.user}.`, + color: getColor('warning') + }).addFields( + { + name: "Panel Channel", + value: panelChannel.toString(), + inline: true + }, + { + name: "Ticket Category", + value: categoryChannel?.toString() || "None specified", + inline: true + }, + { + name: "Closed Category", + value: closedCategoryChannel?.toString() || "None specified", + inline: true + }, + { + name: "Staff Role", + value: staffRole?.toString() || "None specified", + inline: true + }, + { + name: "Max Tickets Per User", + value: String(maxTicketsPerUser), + inline: true + }, + { + name: "DM on Close", + value: dmOnClose ? "Enabled" : "Disabled", + inline: true + }, + { + name: "Moderator", + value: `${interaction.user.tag} (${interaction.user.id})`, + inline: false } + ); + + logger.info('Ticket panel setup completed', { + userId: interaction.user.id, + guildId: interaction.guildId, + panelChannelId: panelChannel.id, + categoryId: categoryChannel?.id, + closedCategoryId: closedCategoryChannel?.id, + staffRoleId: staffRole?.id, + maxTickets: maxTicketsPerUser, + dmOnClose, + commandName: 'ticket_setup' + }); + + } catch (error) { + logger.error('Ticket setup error', { + error: error.message, + stack: error.stack, + userId: interaction.user.id, + guildId: interaction.guildId, + commandName: 'ticket_setup' + }); + + if (interaction.deferred || interaction.replied) { + await replyUserError(interaction, { + type: ErrorTypes.UNKNOWN, + message: "Could not send the ticket panel or save configuration. Check the bot's channel permissions and database connection." + }).catch(err => { + logger.error('Failed to send error reply', { + error: err.message, + guildId: interaction.guildId + }); + }); + } else { + await handleInteractionError(interaction, error, { + commandName: 'ticket_setup', + source: 'ticket_setup_command' + }); } } } -}; \ No newline at end of file +}; +``` From cfded2a6825aa0e34f9ffdf11e8997346cb0891b Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 19:06:36 +0300 Subject: [PATCH 02/19] Improve ticket panel and dashboard embeds --- .../Ticket/modules/ticket_dashboard.js | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/commands/Ticket/modules/ticket_dashboard.js b/src/commands/Ticket/modules/ticket_dashboard.js index 17f3f43f88..ae7cb22844 100644 --- a/src/commands/Ticket/modules/ticket_dashboard.js +++ b/src/commands/Ticket/modules/ticket_dashboard.js @@ -79,11 +79,19 @@ async function persistPanelMessageId(client, guildId, guildConfig, messageId) { } } -function buildPanelEmbed(config) { - return new EmbedBuilder() - .setTitle('Support Tickets') - .setDescription(config.ticketPanelMessage || 'Click the button below to create a support ticket.') - .setColor(getColor('info')); +function buildPanelEmbed(config, client = null) { + const embed = new EmbedBuilder() + .setTitle('🎫 Support Center') + .setDescription(`${config.ticketPanelMessage || 'Click the button below to create a support ticket.'}\n\n**Need help?** Press the button below and our team will get back to you as soon as possible.`) + .setColor('#5865F2') + .setFooter({ text: "SUPPORT CENTER • We're here to help" }); + + if (client?.user) { + embed.setThumbnail(client.user.displayAvatarURL({ extension: 'png', size: 256 })); + } + + embed.setImage('https://placehold.co/1200x300/5865F2/FFFFFF.png?text=SUPPORT+TICKETS'); + return embed; } function buildPanelButtonRow(config) { @@ -107,7 +115,7 @@ async function repostTicketPanel(client, guild, guildConfig, guildId) { } const sentPanel = await channel.send({ - embeds: [buildPanelEmbed(guildConfig)], + embeds: [buildPanelEmbed(guildConfig, client)], components: [buildPanelButtonRow(guildConfig)], }); @@ -148,9 +156,10 @@ function buildDashboardEmbed(config, guild, panelStatus = null, ticketStats = nu : '`No ratings yet`'; return new EmbedBuilder() - .setTitle('🎫 Ticket System Dashboard') - .setDescription(`Manage ticket system settings for **${guild.name}**.\nSelect an option below to modify a setting.`) - .setColor(getColor('info')) + .setTitle('🎫 Support Center Dashboard') + .setDescription(`Manage the ticket system for **${guild.name}**.\nChoose a setting below to customize your support experience.`) + .setColor('#5865F2') + .setThumbnail(guild.client.user.displayAvatarURL({ extension: 'png', size: 256 })) .addFields( { name: 'Panel Status', value: panelStatusValue, inline: false }, { name: 'Panel Channel', value: panelChannel, inline: true }, @@ -170,7 +179,7 @@ function buildDashboardEmbed(config, guild, panelStatus = null, ticketStats = nu { name: 'Avg Close Time', value: avgCloseTime, inline: true }, { name: 'Feedback Rating', value: feedbackSummary, inline: true }, ) - .setFooter({ text: 'Select an option below • Dashboard closes after 10 minutes of inactivity' }) + .setFooter({ text: 'SUPPORT CENTER • Select an option below • Closes after 10 minutes of inactivity' }) .setTimestamp(); } @@ -245,7 +254,7 @@ async function updateLivePanel(client, guild, config, guildId) { if (!panelStatus.exists || !panelStatus.message) return false; await panelStatus.message.edit({ - embeds: [buildPanelEmbed(config)], + embeds: [buildPanelEmbed(config, client)], components: [buildPanelButtonRow(config)], }); return true; @@ -271,6 +280,8 @@ export default { } const panelStatus = await getTicketPanelStatus(client, interaction.guild, guildConfig); + // Keep the existing panel in sync with the current embed design. + await updateLivePanel(client, interaction.guild, guildConfig, guildId); if (panelStatus.recoveredId) { await persistPanelMessageId(client, guildId, guildConfig, panelStatus.recoveredId); } From 156c36836c7e9765832e04009bc504e69f7c0f84 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 19:06:55 +0300 Subject: [PATCH 03/19] Make ticket opener and staff mentions explicit --- src/services/ticket.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/services/ticket.js b/src/services/ticket.js index 158a364d1c..4e0f2920e6 100644 --- a/src/services/ticket.js +++ b/src/services/ticket.js @@ -203,10 +203,15 @@ export async function createTicket(guild, member, categoryId, reason = 'No reaso const staffMention = config.ticketStaffRoleId ? ` <@&${config.ticketStaffRoleId}>` : ''; const messageContent = `${member.toString()}${staffMention}`; - const ticketMessage = await channel.send({ + const ticketMessage = await channel.send({ content: messageContent, + // Explicitly allow only the ticket opener and configured staff role to be mentioned. + allowedMentions: { + users: [member.id], + roles: config.ticketStaffRoleId ? [config.ticketStaffRoleId] : [], + }, embeds: [embed], - components: [row] + components: [row], }); await ticketMessage.pin().catch(() => {}); From 381938858e2cccf5a85b58c5804a512517a16276 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 19:12:18 +0300 Subject: [PATCH 04/19] Polish initial ticket setup panel embed --- src/commands/Ticket/ticket.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js index 4c24a12b16..ce219f284a 100644 --- a/src/commands/Ticket/ticket.js +++ b/src/commands/Ticket/ticket.js @@ -132,8 +132,8 @@ export default { const dmOnClose = interaction.options.getBoolean("dm_on_close") !== false; const setupEmbed = createEmbed({ - title: "🎫 Support Tickets", - description: `${panelMessage}\n\nClick the button below and our team will help you as soon as possible.`, + title: "🎫 Support Center", + description: `${panelMessage}\n\n**Need help?** Click the button below and our team will get back to you as soon as possible.`, color: "#5865F2", thumbnail: client.user.displayAvatarURL({ extension: "png", @@ -141,7 +141,7 @@ export default { }), image: "https://placehold.co/1200x300/5865F2/FFFFFF.png?text=SUPPORT+TICKETS", footer: { - text: "Support Center • We're here to help" + text: "SUPPORT CENTER • We're here to help" } }); From 95dee1244c9dd863e2090ea667c223b9ec04b06e Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 19:27:32 +0300 Subject: [PATCH 05/19] Fix embed builder color preset selection and stale interactions --- src/commands/Tools/embedbuilder.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/commands/Tools/embedbuilder.js b/src/commands/Tools/embedbuilder.js index 3da94f3dc1..0b5740b339 100644 --- a/src/commands/Tools/embedbuilder.js +++ b/src/commands/Tools/embedbuilder.js @@ -259,13 +259,14 @@ async function handleSetColor(selectInteraction, rootInteraction, state) { .setCustomId('eb_color_pick') .setPlaceholder('Choose a color...') .addOptions( - COLOR_PRESETS.map(c => - new StringSelectMenuOptionBuilder() + COLOR_PRESETS.map(c => { + const option = new StringSelectMenuOptionBuilder() .setLabel(c.label) .setValue(c.value) - .setEmoji(c.emoji) - .setDescription(c.value !== '__custom__' ? c.value : 'Enter your own #RRGGBB value'), - ), + .setDescription(c.value !== '__custom__' ? c.value : 'Enter your own #RRGGBB value'); + if (c.emoji) option.setEmoji(c.emoji); + return option; + }), ); await selectInteraction.followUp({ @@ -1127,6 +1128,9 @@ export default { await ci.deferUpdate(); } } catch (error) { + // A component can expire while a user is interacting with an old dashboard. + // Ignore Discord's expected stale/already-acknowledged interaction errors. + if (error?.code === 10062 || error?.code === 40060) return; logger.error('Error in embedbuilder collector:', error); const msg = error instanceof TitanBotError From eedbc9610cc7bb0af5b4deaf63863648023f4dc1 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 19:27:34 +0300 Subject: [PATCH 06/19] Ignore expired dashboard component interactions --- src/utils/dashboardSession.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/dashboardSession.js b/src/utils/dashboardSession.js index 688fe1d066..cc017a4b16 100644 --- a/src/utils/dashboardSession.js +++ b/src/utils/dashboardSession.js @@ -15,7 +15,8 @@ function wrapHandler(handler, interactionLabel = 'dashboard') { try { await handler(componentInteraction); } catch (error) { - if (error?.code === 40060) return; + // Expired or already-acknowledged component interactions are no longer actionable. + if (error?.code === 10062 || error?.code === 40060) return; if (error instanceof TitanBotError) { logger.debug(`${interactionLabel} error: ${error.message}`); From 193d60f41c0d40111aa9d9788eec72064a978a25 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:02:06 +0300 Subject: [PATCH 07/19] Update ticket panel to purchase-only --- src/commands/Ticket/modules/ticket_dashboard.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/commands/Ticket/modules/ticket_dashboard.js b/src/commands/Ticket/modules/ticket_dashboard.js index ae7cb22844..8e7f1522f0 100644 --- a/src/commands/Ticket/modules/ticket_dashboard.js +++ b/src/commands/Ticket/modules/ticket_dashboard.js @@ -81,16 +81,15 @@ async function persistPanelMessageId(client, guildId, guildConfig, messageId) { function buildPanelEmbed(config, client = null) { const embed = new EmbedBuilder() - .setTitle('🎫 Support Center') - .setDescription(`${config.ticketPanelMessage || 'Click the button below to create a support ticket.'}\n\n**Need help?** Press the button below and our team will get back to you as soon as possible.`) + .setTitle('Support Tickets') + .setDescription('Purchase') .setColor('#5865F2') - .setFooter({ text: "SUPPORT CENTER • We're here to help" }); + .setFooter({ text: ' ' }); if (client?.user) { embed.setThumbnail(client.user.displayAvatarURL({ extension: 'png', size: 256 })); } - embed.setImage('https://placehold.co/1200x300/5865F2/FFFFFF.png?text=SUPPORT+TICKETS'); return embed; } @@ -98,9 +97,9 @@ function buildPanelButtonRow(config) { return new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId('create_ticket') - .setLabel(config.ticketButtonLabel || 'Create Ticket') + .setLabel('Purchase') .setStyle(ButtonStyle.Primary) - .setEmoji('📩'), + .setEmoji('🛒'), ); } From a8f69ec52b55e76bc8c66f73d0f6ee5749d6cbd7 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:02:20 +0300 Subject: [PATCH 08/19] Set new ticket setup panel to purchase only --- src/commands/Ticket/ticket.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js index ce219f284a..1e3248b846 100644 --- a/src/commands/Ticket/ticket.js +++ b/src/commands/Ticket/ticket.js @@ -1,4 +1,3 @@ -```js import { getColor } from '../../config/bot.js'; import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, MessageFlags } from 'discord.js'; import { createEmbed, successEmbed } from '../../utils/embeds.js'; @@ -122,26 +121,24 @@ export default { const closedCategoryChannel = interaction.options.getChannel("closed_category"); const staffRole = interaction.options.getRole("staff_role"); - const panelMessage = interaction.options.getString("panel_message") - || "Click the button below to create a support ticket."; + const panelMessage = "Purchase"; - const buttonLabel = interaction.options.getString("button_label") - || "Create Ticket"; + const buttonLabel = "Purchase"; const maxTicketsPerUser = interaction.options.getInteger("max_tickets_per_user") || 3; const dmOnClose = interaction.options.getBoolean("dm_on_close") !== false; const setupEmbed = createEmbed({ - title: "🎫 Support Center", - description: `${panelMessage}\n\n**Need help?** Click the button below and our team will get back to you as soon as possible.`, + title: "Support Tickets", + description: "Purchase", color: "#5865F2", thumbnail: client.user.displayAvatarURL({ extension: "png", size: 256 }), - image: "https://placehold.co/1200x300/5865F2/FFFFFF.png?text=SUPPORT+TICKETS", + footer: { - text: "SUPPORT CENTER • We're here to help" + text: " " } }); @@ -150,7 +147,7 @@ export default { .setCustomId("create_ticket") .setLabel(buttonLabel) .setStyle(ButtonStyle.Primary) - .setEmoji("📩") + .setEmoji("🛒") ); try { From 4a2af7b7c6cf5a957a37bc0e89e0917536f12703 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:36:13 +0300 Subject: [PATCH 09/19] Update ticket.js --- src/commands/Ticket/ticket.js | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js index 1e3248b846..5849dbc058 100644 --- a/src/commands/Ticket/ticket.js +++ b/src/commands/Ticket/ticket.js @@ -128,20 +128,34 @@ export default { const maxTicketsPerUser = interaction.options.getInteger("max_tickets_per_user") || 3; const dmOnClose = interaction.options.getBoolean("dm_on_close") !== false; + // ====== CYANSTAR PANEL (edit these 3 values) ====== + const PANEL_COLOR = "#00E5FF"; + const PANEL_THUMBNAIL = "https://YOUR_LOGO_URL.png"; // or: client.user.displayAvatarURL({ extension: "png", size: 256 }) + const PANEL_BANNER = "https://YOUR_BANNER_URL.png"; // optional, set to null to remove + // =================================================== + const setupEmbed = createEmbed({ - title: "Support Tickets", - description: "Purchase", - color: "#5865F2", - thumbnail: client.user.displayAvatarURL({ - extension: "png", - size: 256 - }), - + title: "CyanStar Support", + description: [ + "Press the button below to open a ticket and choose what you need:", + "", + "<:481292fortnite:1562373392869498880> **Fortnite**", + "", + "<:2320spotify:1562422108179398726> **Spotify**", + "", + "<:discord:1562367615467528252> **Discord**" + ].join("\n"), + color: PANEL_COLOR, + thumbnail: PANEL_THUMBNAIL, footer: { - text: " " + text: "CyanStar • Support" } }); + if (PANEL_BANNER) { + setupEmbed.setImage(PANEL_BANNER); + } + const ticketButton = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId("create_ticket") @@ -289,4 +303,3 @@ export default { } } }; -``` From 92402e52436f5905183dc2a9d4e815c658baca70 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:44:32 +0300 Subject: [PATCH 10/19] Add purchase category selection to ticket button --- src/handlers/ticketButtons.js | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/handlers/ticketButtons.js b/src/handlers/ticketButtons.js index 76909a6d1c..12803dd710 100644 --- a/src/handlers/ticketButtons.js +++ b/src/handlers/ticketButtons.js @@ -117,39 +117,39 @@ const createTicketHandler = { const config = await getGuildConfig(client, interaction.guildId); const maxTicketsPerUser = config.maxTicketsPerUser || 3; - const { getUserTicketCount } = await import('../services/ticket.js'); const currentTicketCount = await getUserTicketCount(interaction.guildId, interaction.user.id); - + if (currentTicketCount >= maxTicketsPerUser) { return await replyUserError(interaction, { type: ErrorTypes.UNKNOWN, message: `You have reached the maximum number of open tickets (${maxTicketsPerUser}).\n\nPlease close your existing tickets before creating a new one.\n\n**Current Tickets:** ${currentTicketCount}/${maxTicketsPerUser}` }); } - - const modal = new ModalBuilder() - .setCustomId('create_ticket_modal') - .setTitle('Create a Ticket'); - - const reasonInput = new TextInputBuilder() - .setCustomId('reason') - .setLabel('Why are you creating this ticket?') - .setStyle(TextInputStyle.Paragraph) - .setPlaceholder('Describe your issue...') - .setRequired(true) - .setMaxLength(1000); - - const actionRow = new ActionRowBuilder().addComponents(reasonInput); - modal.addComponents(actionRow); - await interaction.showModal(modal); + const { StringSelectMenuBuilder, EmbedBuilder, ActionRowBuilder: RowBuilder } = await import('discord.js'); + const menu = new StringSelectMenuBuilder() + .setCustomId('purchase_category') + .setPlaceholder('Choose what you want to purchase') + .addOptions( + { label: 'Fortnite', value: 'Fortnite', emoji: '🎮', description: 'Fortnite purchases' }, + { label: 'Spotify', value: 'Spotify', emoji: '🎵', description: 'Spotify purchases' }, + { label: 'Discord', value: 'Discord', emoji: '💬', description: 'Discord purchases' } + ); + + await interaction.reply({ + embeds: [new EmbedBuilder() + .setTitle('🛒 Choose a Purchase Category') + .setDescription('Select Fortnite, Spotify, or Discord to continue with your purchase ticket.') + .setColor('#00D9E8')], + components: [new RowBuilder().addComponents(menu)], + ephemeral: true + }); } catch (error) { - logger.error('Error creating ticket modal:', error); + logger.error('Error starting purchase ticket:', error); if (!interaction.replied && !interaction.deferred) { - await replyUserError(interaction, { type: ErrorTypes.UNKNOWN, message: 'Could not open ticket creation form.' }); + await replyUserError(interaction, { type: ErrorTypes.UNKNOWN, message: 'Could not start purchase ticket creation.' }); } } } }; - const createTicketModalHandler = { name: 'create_ticket_modal', async execute(interaction, client) { From 9793dce2545d8277a339bec314e5e286f278edce Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:44:46 +0300 Subject: [PATCH 11/19] Add purchase category select menu --- .../selectMenus/ticket/purchaseCategory.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/interactions/selectMenus/ticket/purchaseCategory.js diff --git a/src/interactions/selectMenus/ticket/purchaseCategory.js b/src/interactions/selectMenus/ticket/purchaseCategory.js new file mode 100644 index 0000000000..61579e2724 --- /dev/null +++ b/src/interactions/selectMenus/ticket/purchaseCategory.js @@ -0,0 +1,35 @@ +import { ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, MessageFlags } from 'discord.js'; +import { logger } from '../../../utils/logger.js'; + +export default { + name: 'purchase_category', + async execute(interaction) { + const category = interaction.values?.[0]; + if (!['Fortnite', 'Spotify', 'Discord'].includes(category)) { + await interaction.reply({ content: 'Please choose a valid purchase category.', flags: MessageFlags.Ephemeral }); + return; + } + + const modal = new ModalBuilder() + .setCustomId(`purchase_ticket_modal:${category}`) + .setTitle(`${category} Purchase Ticket`); + + const details = new TextInputBuilder() + .setCustomId('details') + .setLabel('What would you like to purchase?') + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder('Include the product and any details we need.') + .setRequired(true) + .setMaxLength(1000); + + modal.addComponents(new ActionRowBuilder().addComponents(details)); + try { + await interaction.showModal(modal); + } catch (error) { + logger.error('Could not show purchase ticket modal:', error); + if (!interaction.replied && !interaction.deferred) { + await interaction.reply({ content: 'Could not open the purchase form. Please try again.', flags: MessageFlags.Ephemeral }); + } + } + } +}; From 13237c1b617b476de090b61ff760d93407075753 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:45:01 +0300 Subject: [PATCH 12/19] Add purchase ticket modal handler --- .../modals/ticket/purchaseTicket.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/interactions/modals/ticket/purchaseTicket.js diff --git a/src/interactions/modals/ticket/purchaseTicket.js b/src/interactions/modals/ticket/purchaseTicket.js new file mode 100644 index 0000000000..c07b33971e --- /dev/null +++ b/src/interactions/modals/ticket/purchaseTicket.js @@ -0,0 +1,43 @@ +import { MessageFlags } from 'discord.js'; +import { createTicket } from '../../../services/ticket.js'; +import { getGuildConfig } from '../../../services/config/guildConfig.js'; +import { successEmbed } from '../../../utils/embeds.js'; +import { logger } from '../../../utils/logger.js'; +import { handleInteractionError } from '../../../utils/errorHandler.js'; + +export default { + name: 'purchase_ticket_modal', + async execute(interaction, client, args = []) { + try { + if (!interaction.inGuild()) { + await interaction.reply({ content: 'This can only be used in a server.', flags: MessageFlags.Ephemeral }); + return; + } + + const category = args[0]; + if (!['Fortnite', 'Spotify', 'Discord'].includes(category)) { + await interaction.reply({ content: 'Invalid purchase category. Please start again from the purchase button.', flags: MessageFlags.Ephemeral }); + return; + } + + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); + const details = interaction.fields.getTextInputValue('details').trim(); + const config = await getGuildConfig(client, interaction.guildId); + const { channel } = await createTicket( + interaction.guild, + interaction.member, + config.ticketCategoryId || null, + details, + 'none', + category + ); + + await interaction.editReply({ + embeds: [successEmbed('Purchase Ticket Created', `Your **${category}** purchase ticket is ready: ${channel}`)] + }); + } catch (error) { + logger.error('Error submitting purchase ticket form:', error); + await handleInteractionError(interaction, error, { type: 'modal', handler: 'purchase_ticket_modal', customId: interaction.customId }); + } + } +}; From e7db61307f922a5a4e38ea5a008f13c1fd1af9fc Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:45:44 +0300 Subject: [PATCH 13/19] Style purchase tickets for CyanStar --- src/services/ticket.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/services/ticket.js b/src/services/ticket.js index 4e0f2920e6..2646a9e87c 100644 --- a/src/services/ticket.js +++ b/src/services/ticket.js @@ -79,7 +79,7 @@ export const getUserTicketCount = wrapServiceBoundary(async function getUserTick context: {}, }); -export async function createTicket(guild, member, categoryId, reason = 'No reason provided', priority = 'none') { +export async function createTicket(guild, member, categoryId, reason = 'No reason provided', priority = 'none', productCategory = null) { try { const config = await getGuildConfig(guild.client, guild.id); const ticketConfig = config.tickets || {}; @@ -118,7 +118,8 @@ export async function createTicket(guild, member, categoryId, reason = 'No reaso const ticketNumber = await getNextTicketNumber(guild.id); - let channelName = `ticket-${ticketNumber}`; + const categorySlug = ['Fortnite', 'Spotify', 'Discord'].includes(productCategory) ? `${productCategory.toLowerCase()}-` : ''; + let channelName = `${categorySlug}ticket-${ticketNumber}`; if (priority !== 'none') { const priorityInfo = PRIORITY_MAP[priority]; @@ -173,9 +174,10 @@ export async function createTicket(guild, member, categoryId, reason = 'No reaso const priorityInfo = PRIORITY_MAP[priority] || PRIORITY_MAP.none; const embed = createEmbed({ - title: `Ticket #${ticketNumber}`, - description: `${member.toString()}, thanks for creating a ticket!\n\n**Reason:** ${reason}\n**Priority:** ${priorityInfo.emoji} ${priorityInfo.label}`, - color: priorityInfo.color, + title: productCategory ? `${productCategory} Purchase #${ticketNumber}` : `Ticket #${ticketNumber}`, + description: `${member.toString()}, welcome to your CyanStar purchase ticket!\n\n**Category:** ${productCategory || 'General'}\n**Purchase details:** ${reason}\n**Priority:** ${priorityInfo.emoji} ${priorityInfo.label}\n\nThe CyanStar Support Team will assist you here.`, + color: productCategory ? '#00D9E8' : priorityInfo.color, + thumbnail: guild.client.user?.displayAvatarURL({ extension: 'png', size: 256 }), fields: [ { name: 'Status', value: '🟢 Open', inline: true }, { name: 'Claimed By', value: 'Not claimed', inline: true }, From 04fd021fe05825790795dc713a8ff374bdc5e6b6 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:46:20 +0300 Subject: [PATCH 14/19] Set CyanStar purchase panel message and cyan theme --- src/commands/Ticket/modules/ticket_dashboard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/Ticket/modules/ticket_dashboard.js b/src/commands/Ticket/modules/ticket_dashboard.js index 8e7f1522f0..c5ac00e079 100644 --- a/src/commands/Ticket/modules/ticket_dashboard.js +++ b/src/commands/Ticket/modules/ticket_dashboard.js @@ -82,8 +82,8 @@ async function persistPanelMessageId(client, guildId, guildConfig, messageId) { function buildPanelEmbed(config, client = null) { const embed = new EmbedBuilder() .setTitle('Support Tickets') - .setDescription('Purchase') - .setColor('#5865F2') + .setDescription('💲 Purchase') + .setColor('#00D9E8') .setFooter({ text: ' ' }); if (client?.user) { From 52ebcf2664a82a30efd19e017f83be591d1f01c7 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:46:29 +0300 Subject: [PATCH 15/19] Simplify CyanStar ticket panel to purchase only --- src/commands/Ticket/ticket.js | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js index 5849dbc058..d7508b6155 100644 --- a/src/commands/Ticket/ticket.js +++ b/src/commands/Ticket/ticket.js @@ -135,27 +135,12 @@ export default { // =================================================== const setupEmbed = createEmbed({ - title: "CyanStar Support", - description: [ - "Press the button below to open a ticket and choose what you need:", - "", - "<:481292fortnite:1562373392869498880> **Fortnite**", - "", - "<:2320spotify:1562422108179398726> **Spotify**", - "", - "<:discord:1562367615467528252> **Discord**" - ].join("\n"), - color: PANEL_COLOR, - thumbnail: PANEL_THUMBNAIL, - footer: { - text: "CyanStar • Support" - } + title: "Support Tickets", + description: "💲 Purchase", + color: "#00D9E8", + thumbnail: PANEL_THUMBNAIL }); - if (PANEL_BANNER) { - setupEmbed.setImage(PANEL_BANNER); - } - const ticketButton = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId("create_ticket") From deb6ce4b8f4ce40a6f2b7700bfd4519e6d328181 Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:46:53 +0300 Subject: [PATCH 16/19] Use bot avatar for CyanStar ticket panel thumbnail --- src/commands/Ticket/ticket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js index d7508b6155..3703767ad8 100644 --- a/src/commands/Ticket/ticket.js +++ b/src/commands/Ticket/ticket.js @@ -130,7 +130,7 @@ export default { // ====== CYANSTAR PANEL (edit these 3 values) ====== const PANEL_COLOR = "#00E5FF"; - const PANEL_THUMBNAIL = "https://YOUR_LOGO_URL.png"; // or: client.user.displayAvatarURL({ extension: "png", size: 256 }) + const PANEL_THUMBNAIL = client.user.displayAvatarURL({ extension: "png", size: 256 }); const PANEL_BANNER = "https://YOUR_BANNER_URL.png"; // optional, set to null to remove // =================================================== From fa7c7e8b26f0ae878332a1387523ef9ad9e6af1b Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 20:46:57 +0300 Subject: [PATCH 17/19] Keep ticket controls compatible with category ticket titles --- src/services/ticket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/ticket.js b/src/services/ticket.js index 2646a9e87c..f18e8cc918 100644 --- a/src/services/ticket.js +++ b/src/services/ticket.js @@ -174,7 +174,7 @@ export async function createTicket(guild, member, categoryId, reason = 'No reaso const priorityInfo = PRIORITY_MAP[priority] || PRIORITY_MAP.none; const embed = createEmbed({ - title: productCategory ? `${productCategory} Purchase #${ticketNumber}` : `Ticket #${ticketNumber}`, + title: productCategory ? `Ticket #${ticketNumber} • ${productCategory} Purchase` : `Ticket #${ticketNumber}`, description: `${member.toString()}, welcome to your CyanStar purchase ticket!\n\n**Category:** ${productCategory || 'General'}\n**Purchase details:** ${reason}\n**Priority:** ${priorityInfo.emoji} ${priorityInfo.label}\n\nThe CyanStar Support Team will assist you here.`, color: productCategory ? '#00D9E8' : priorityInfo.color, thumbnail: guild.client.user?.displayAvatarURL({ extension: 'png', size: 256 }), From 2d2f9bf5e8c727a3584f3f808c5f4900e48f833d Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 21:06:37 +0300 Subject: [PATCH 18/19] Fix ticket dashboard modal interaction timeout --- src/commands/Ticket/modules/ticket_dashboard.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/Ticket/modules/ticket_dashboard.js b/src/commands/Ticket/modules/ticket_dashboard.js index c5ac00e079..6811d41d46 100644 --- a/src/commands/Ticket/modules/ticket_dashboard.js +++ b/src/commands/Ticket/modules/ticket_dashboard.js @@ -386,13 +386,17 @@ async function handlePanelMessage(selectInteraction, rootInteraction, guildConfi if (!submitted) return; + // Acknowledge the modal submission immediately so longer database/panel updates + // cannot exceed Discord's interaction response deadline. + await submitted.deferReply({ flags: MessageFlags.Ephemeral }); + const newMessage = submitted.fields.getTextInputValue('panel_msg_input').trim(); guildConfig.ticketPanelMessage = newMessage; await setGuildConfig(client, guildId, guildConfig); const panelUpdated = await updateLivePanel(client, rootInteraction.guild, guildConfig, guildId); - await submitted.reply({ + await submitted.editReply({ embeds: [ successEmbed( '✅ Panel Message Updated', From 9a7002563cf936549e904645f6f10b45ae8ada9f Mon Sep 17 00:00:00 2001 From: hazem-kk <7az3ma7m3dk@gmail.com> Date: Thu, 22 Oct 2026 21:15:24 +0300 Subject: [PATCH 19/19] Acknowledge ticket deletion modal reliably --- src/commands/Ticket/modules/ticket_dashboard.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands/Ticket/modules/ticket_dashboard.js b/src/commands/Ticket/modules/ticket_dashboard.js index 6811d41d46..35b221a702 100644 --- a/src/commands/Ticket/modules/ticket_dashboard.js +++ b/src/commands/Ticket/modules/ticket_dashboard.js @@ -957,7 +957,9 @@ async function handleDeleteSystem(btnInteraction, rootInteraction, guildConfig, return; } - await submitted.deferUpdate(); + // Acknowledge the modal submission with an ephemeral reply. This is more + // reliable here than deferUpdate and leaves followUp available for the result. + await submitted.deferReply({ flags: MessageFlags.Ephemeral }); const keysToDelete = [ 'ticketPanelChannelId',