@@ -15,9 +15,6 @@ import {
1515 MessageFlags ,
1616 ComponentType ,
1717 EmbedBuilder ,
18- LabelBuilder ,
19- FileUploadBuilder ,
20- TextDisplayBuilder ,
2118} from 'discord.js' ;
2219import { InteractionHelper } from '../../../utils/interactionHelper.js' ;
2320import { successEmbed , infoEmbed } from '../../../utils/embeds.js' ;
@@ -29,20 +26,10 @@ import { getGuildTicketStats } from '../../../utils/database/tickets.js';
2926import { getUserTicketCount } from '../../../services/ticket.js' ;
3027import {
3128 getTicketPanelStatus ,
32- messageHasButtonCustomIdPrefix ,
29+ messageHasButtonCustomId ,
3330 formatPanelStatusField ,
3431} from '../../../utils/panelStatus.js' ;
3532import { startDashboardSession } from '../../../utils/dashboardSession.js' ;
36- import {
37- MAX_TICKET_PANEL_BUTTONS ,
38- buildTicketPanelEmbed ,
39- buildTicketPanelButtonRows ,
40- formatPanelButtonCombinedValue ,
41- getTicketPanelButtons ,
42- normalizeTicketPanelConfig ,
43- parsePanelButtonCombinedFields ,
44- validatePanelImageUrl ,
45- } from '../../../utils/ticketPanel.js' ;
4633
4734function buildButtonRow ( guildConfig , guildId , disabled = false , panelStatus = null ) {
4835 const dmEnabled = guildConfig . dmOnClose !== false ;
@@ -93,6 +80,23 @@ async function persistPanelMessageId(client, guildId, guildConfig, messageId) {
9380 }
9481}
9582
83+ function buildPanelEmbed ( config ) {
84+ return new EmbedBuilder ( )
85+ . setTitle ( 'Support Tickets' )
86+ . setDescription ( config . ticketPanelMessage || 'Click the button below to create a support ticket.' )
87+ . setColor ( getColor ( 'info' ) ) ;
88+ }
89+
90+ function buildPanelButtonRow ( config ) {
91+ return new ActionRowBuilder ( ) . addComponents (
92+ new ButtonBuilder ( )
93+ . setCustomId ( 'create_ticket' )
94+ . setLabel ( config . ticketButtonLabel || 'Create Ticket' )
95+ . setStyle ( ButtonStyle . Primary )
96+ . setEmoji ( '📩' ) ,
97+ ) ;
98+ }
99+
96100async function repostTicketPanel ( client , guild , guildConfig , guildId ) {
97101 const channel = await guild . channels . fetch ( guildConfig . ticketPanelChannelId ) . catch ( ( ) => null ) ;
98102 if ( ! channel ) {
@@ -104,8 +108,8 @@ async function repostTicketPanel(client, guild, guildConfig, guildId) {
104108 }
105109
106110 const sentPanel = await channel . send ( {
107- embeds : [ buildTicketPanelEmbed ( guildConfig ) ] ,
108- components : buildTicketPanelButtonRows ( guildConfig ) ,
111+ embeds : [ buildPanelEmbed ( guildConfig ) ] ,
112+ components : [ buildPanelButtonRow ( guildConfig ) ] ,
109113 } ) ;
110114
111115 await persistPanelMessageId ( client , guildId , guildConfig , sentPanel . id ) ;
@@ -134,7 +138,7 @@ function buildDashboardEmbed(config, guild, panelStatus = null, ticketStats = nu
134138
135139 const rawMsg = config . ticketPanelMessage || 'Click the button below to create a support ticket.' ;
136140 const panelMsg = `\`${ rawMsg . length > 60 ? rawMsg . substring ( 0 , 60 ) + '…' : rawMsg } \`` ;
137- const panelBanner = config . ticketPanelImage ? `[View banner]( ${ config . ticketPanelImage } )` : '`Not set`' ;
141+ const btnLabel = `\` ${ config . ticketButtonLabel || 'Create Ticket' } \`` ;
138142
139143 let panelStatusValue = formatPanelStatusField ( panelStatus ) ;
140144
@@ -157,7 +161,7 @@ function buildDashboardEmbed(config, guild, panelStatus = null, ticketStats = nu
157161 { name : 'Closed Tickets Category' , value : closedCategory , inline : true } ,
158162 { name : '\u200B' , value : '\u200B' , inline : true } ,
159163 { name : 'Panel Message' , value : panelMsg , inline : false } ,
160- { name : 'Panel Banner ' , value : panelBanner , inline : true } ,
164+ { name : 'Button Label ' , value : btnLabel , inline : true } ,
161165 { name : 'Max Tickets/User' , value : String ( config . maxTicketsPerUser || 3 ) , inline : true } ,
162166 { name : 'DM on Close' , value : config . dmOnClose !== false ? 'Enabled' : 'Disabled' , inline : true } ,
163167 { name : 'Ticket Logs Channel' , value : ticketLogsChannel , inline : true } ,
@@ -182,14 +186,9 @@ function buildSelectMenu(guildId) {
182186 . setValue ( 'panel_message' )
183187 . setEmoji ( '📝' ) ,
184188 new StringSelectMenuOptionBuilder ( )
185- . setLabel ( 'Edit Panel Banner' )
186- . setDescription ( 'Add or remove a banner image on the ticket panel embed' )
187- . setValue ( 'panel_banner' )
188- . setEmoji ( '🖼️' ) ,
189- new StringSelectMenuOptionBuilder ( )
190- . setLabel ( 'Manage Panel Buttons' )
191- . setDescription ( 'Set emoji + label for up to 5 panel buttons in one modal' )
192- . setValue ( 'panel_buttons' )
189+ . setLabel ( 'Edit Button Label' )
190+ . setDescription ( 'Change the label on the Create Ticket button' )
191+ . setValue ( 'button_label' )
193192 . setEmoji ( '🏷️' ) ,
194193 new StringSelectMenuOptionBuilder ( )
195194 . setLabel ( 'Change Open Tickets Category' )
@@ -247,8 +246,8 @@ async function updateLivePanel(client, guild, config, guildId) {
247246 if ( ! panelStatus . exists || ! panelStatus . message ) return false ;
248247
249248 await panelStatus . message . edit ( {
250- embeds : [ buildTicketPanelEmbed ( config ) ] ,
251- components : buildTicketPanelButtonRows ( config ) ,
249+ embeds : [ buildPanelEmbed ( config ) ] ,
250+ components : [ buildPanelButtonRow ( config ) ] ,
252251 } ) ;
253252 return true ;
254253 } catch ( error ) {
@@ -298,11 +297,8 @@ export default {
298297 case 'panel_message' :
299298 await handlePanelMessage ( selectInteraction , interaction , guildConfig , guildId , client ) ;
300299 break ;
301- case 'panel_banner' :
302- await handlePanelBanner ( selectInteraction , interaction , guildConfig , guildId , client ) ;
303- break ;
304- case 'panel_buttons' :
305- await handlePanelButtons ( selectInteraction , interaction , guildConfig , guildId , client ) ;
300+ case 'button_label' :
301+ await handleButtonLabel ( selectInteraction , interaction , guildConfig , guildId , client ) ;
306302 break ;
307303 case 'staff_role' :
308304 await handleStaffRole ( selectInteraction , interaction , guildConfig , guildId , client ) ;
@@ -373,7 +369,7 @@ async function handlePanelMessage(selectInteraction, rootInteraction, guildConfi
373369
374370 const submitted = await selectInteraction
375371 . awaitModalSubmit ( {
376- filter : ( i ) =>
372+ filter : i =>
377373 i . customId === 'ticket_cfg_panel_msg' && i . user . id === selectInteraction . user . id ,
378374 time : 120_000 ,
379375 } )
@@ -404,188 +400,51 @@ async function handlePanelMessage(selectInteraction, rootInteraction, guildConfi
404400 await refreshDashboard ( rootInteraction , guildConfig , guildId , client ) ;
405401}
406402
407- async function handlePanelBanner ( selectInteraction , rootInteraction , guildConfig , guildId , client ) {
408- const modal = new ModalBuilder ( )
409- . setCustomId ( 'ticket_cfg_panel_banner' )
410- . setTitle ( '🖼️ Edit Panel Banner' ) ;
411-
412- const imageHint = new TextDisplayBuilder ( ) . setContent (
413- 'Provide a direct image URL **or** upload a file below. If both are given, the uploaded file takes priority. Leave the URL blank and skip the upload to remove the banner.\n\n**Note:** Uploaded files use Discord\'s CDN and may stop working over time. For a permanent panel banner, use a hosted URL (Imgur, your website, etc.).' ,
414- ) ;
415-
416- const urlLabel = new LabelBuilder ( )
417- . setLabel ( 'Image URL (optional)' )
418- . setTextInputComponent (
419- new TextInputBuilder ( )
420- . setCustomId ( 'panel_banner_input' )
421- . setPlaceholder ( 'https://example.com/banner.png' )
422- . setStyle ( TextInputStyle . Short )
423- . setValue ( guildConfig . ticketPanelImage || '' )
424- . setRequired ( false ) ,
425- ) ;
426-
427- const uploadLabel = new LabelBuilder ( )
428- . setLabel ( 'Or upload an image file (optional)' )
429- . setFileUploadComponent (
430- new FileUploadBuilder ( ) . setCustomId ( 'panel_banner_upload' ) . setRequired ( false ) ,
431- ) ;
432-
433- modal . addTextDisplayComponents ( imageHint ) . addLabelComponents ( urlLabel , uploadLabel ) ;
434-
435- try {
436- await selectInteraction . showModal ( modal ) ;
437- } catch {
438- return ;
439- }
440-
441- const submitted = await selectInteraction
442- . awaitModalSubmit ( {
443- filter : ( i ) =>
444- i . customId === 'ticket_cfg_panel_banner' && i . user . id === selectInteraction . user . id ,
445- time : 120_000 ,
446- } )
447- . catch ( ( ) => null ) ;
448-
449- if ( ! submitted ) return ;
450-
451- const uploadedFiles = submitted . fields . getUploadedFiles ( 'panel_banner_upload' ) ;
452- let imageUrl =
453- uploadedFiles ?. at ( 0 ) ?. url ?? submitted . fields . getTextInputValue ( 'panel_banner_input' ) . trim ( ) ;
454-
455- const validation = validatePanelImageUrl ( imageUrl ) ;
456- if ( ! validation . ok ) {
457- await replyUserError ( submitted , { type : ErrorTypes . VALIDATION , message : validation . error } ) ;
458- return ;
459- }
460-
461- if ( validation . url ) {
462- guildConfig . ticketPanelImage = validation . url ;
463- } else {
464- delete guildConfig . ticketPanelImage ;
465- }
466-
467- await client . db . set ( getGuildConfigKey ( guildId ) , guildConfig ) ;
468-
469- const panelUpdated = await updateLivePanel ( client , rootInteraction . guild , guildConfig , guildId ) ;
470-
471- await submitted . reply ( {
472- embeds : [
473- successEmbed (
474- validation . url ? '✅ Panel Banner Updated' : '✅ Panel Banner Removed' ,
475- ( validation . url
476- ? 'The panel banner has been updated.'
477- : 'The panel banner has been removed.' ) +
478- ( panelUpdated
479- ? '\nThe live ticket panel has also been refreshed.'
480- : '\n> **Note:** The live panel could not be located. Use **Repost Panel** on the dashboard to restore it.' ) ,
481- ) ,
482- ] ,
483- flags : MessageFlags . Ephemeral ,
484- } ) ;
485-
486- await refreshDashboard ( rootInteraction , guildConfig , guildId , client ) ;
487- }
488-
489- function buildPanelButtonModal ( guildConfig ) {
490- const currentButtons = getTicketPanelButtons ( guildConfig ) ;
403+ async function handleButtonLabel ( selectInteraction , rootInteraction , guildConfig , guildId , client ) {
491404 const modal = new ModalBuilder ( )
492- . setCustomId ( 'ticket_cfg_panel_buttons' )
493- . setTitle ( 'Manage Panel Buttons' ) ;
494-
495- const labelComponents = [ ] ;
496-
497- for ( let index = 1 ; index <= MAX_TICKET_PANEL_BUTTONS ; index += 1 ) {
498- const button = currentButtons [ index - 1 ] ;
499- const label = new LabelBuilder ( )
500- . setLabel ( index === 1 ? 'Button 1 (required)' : `Button ${ index } (optional)` )
501- . setTextInputComponent (
405+ . setCustomId ( 'ticket_cfg_btn_label' )
406+ . setTitle ( '🏷️ Edit Button Label' )
407+ . addComponents (
408+ new ActionRowBuilder ( ) . addComponents (
502409 new TextInputBuilder ( )
503- . setCustomId ( `btn_${ index } _combined` )
410+ . setCustomId ( 'btn_label_input' )
411+ . setLabel ( 'Button Label (max 80 characters)' )
504412 . setStyle ( TextInputStyle . Short )
505- . setValue ( button ? formatPanelButtonCombinedValue ( button ) : '' )
506- . setMaxLength ( 90 )
507- . setRequired ( index === 1 )
508- . setPlaceholder (
509- index === 1
510- ? '📩 Create Ticket or ticket Help'
511- : '💳 Billing or support General' ,
512- ) ,
513- ) ;
514-
515- if ( index === 1 ) {
516- label . setDescription (
517- 'Emoji + label. Paste 📩 Support, or type keywords like ticket, billing, support, help, bug, staff, mail.' ,
518- ) ;
519- }
520-
521- labelComponents . push ( label ) ;
522- }
523-
524- modal . addLabelComponents ( ...labelComponents ) ;
525- return modal ;
526- }
527-
528- async function handlePanelButtons ( selectInteraction , rootInteraction , guildConfig , guildId , client ) {
529- try {
530- await selectInteraction . showModal ( buildPanelButtonModal ( guildConfig ) ) ;
531- } catch ( error ) {
532- logger . warn ( 'Panel button modal v2 unavailable, falling back to classic layout:' , error . message ) ;
533-
534- const currentButtons = getTicketPanelButtons ( guildConfig ) ;
535- const fallbackModal = new ModalBuilder ( )
536- . setCustomId ( 'ticket_cfg_panel_buttons' )
537- . setTitle ( 'Manage Panel Buttons' ) ;
538-
539- for ( let index = 1 ; index <= MAX_TICKET_PANEL_BUTTONS ; index += 1 ) {
540- const button = currentButtons [ index - 1 ] ;
541- fallbackModal . addComponents (
542- new ActionRowBuilder ( ) . addComponents (
543- new TextInputBuilder ( )
544- . setCustomId ( `btn_${ index } _combined` )
545- . setLabel ( index === 1 ? 'Button 1 (required)' : `Button ${ index } (optional)` )
546- . setStyle ( TextInputStyle . Short )
547- . setValue ( button ? formatPanelButtonCombinedValue ( button ) : '' )
548- . setMaxLength ( 90 )
549- . setRequired ( index === 1 )
550- . setPlaceholder ( index === 1 ? '📩 Create Ticket' : '💳 Billing' ) ,
551- ) ,
552- ) ;
553- }
413+ . setValue ( guildConfig . ticketButtonLabel || 'Create Ticket' )
414+ . setMaxLength ( 80 )
415+ . setMinLength ( 1 )
416+ . setRequired ( true )
417+ . setPlaceholder ( 'Create Ticket' ) ,
418+ ) ,
419+ ) ;
554420
555- await selectInteraction . showModal ( fallbackModal ) ;
556- }
421+ await selectInteraction . showModal ( modal ) ;
557422
558423 const submitted = await selectInteraction
559424 . awaitModalSubmit ( {
560- filter : ( i ) =>
561- i . customId === 'ticket_cfg_panel_buttons ' && i . user . id === selectInteraction . user . id ,
425+ filter : i =>
426+ i . customId === 'ticket_cfg_btn_label ' && i . user . id === selectInteraction . user . id ,
562427 time : 120_000 ,
563428 } )
564429 . catch ( ( ) => null ) ;
565430
566431 if ( ! submitted ) return ;
567432
568- const parsed = parsePanelButtonCombinedFields ( submitted . fields ) ;
569- if ( ! parsed . ok ) {
570- await replyUserError ( submitted , { type : ErrorTypes . VALIDATION , message : parsed . error } ) ;
571- return ;
572- }
573-
574- guildConfig . ticketPanelButtons = parsed . buttons ;
575- normalizeTicketPanelConfig ( guildConfig ) ;
433+ const newLabel = submitted . fields . getTextInputValue ( 'btn_label_input' ) . trim ( ) ;
434+ guildConfig . ticketButtonLabel = newLabel ;
576435 await client . db . set ( getGuildConfigKey ( guildId ) , guildConfig ) ;
577436
578437 const panelUpdated = await updateLivePanel ( client , rootInteraction . guild , guildConfig , guildId ) ;
579438
580439 await submitted . reply ( {
581440 embeds : [
582441 successEmbed (
583- '✅ Panel Buttons Updated' ,
584- `The panel now has ** ${ parsed . buttons . length } ** button ${ parsed . buttons . length !== 1 ? 's' : '' } .${
442+ '✅ Button Label Updated' ,
443+ `Button label changed to \` ${ newLabel } \` .${
585444 panelUpdated
586- ? '\n\ nThe live ticket panel has also been refreshed .'
587- : '\n\n > **Note:** The live panel could not be located. Use **Repost Panel** on the dashboard to restore it.'
588- } \n\nAll buttons open the same ticket creation form. `,
445+ ? '\nThe live ticket panel button has also been updated .'
446+ : '\n> **Note:** The live panel could not be located. Use **Repost Panel** on the dashboard to restore it.'
447+ } `,
589448 ) ,
590449 ] ,
591450 flags : MessageFlags . Ephemeral ,
@@ -1094,8 +953,6 @@ async function handleDeleteSystem(btnInteraction, rootInteraction, guildConfig,
1094953 'ticketCategoryId' ,
1095954 'ticketClosedCategoryId' ,
1096955 'ticketPanelMessage' ,
1097- 'ticketPanelImage' ,
1098- 'ticketPanelButtons' ,
1099956 'ticketButtonLabel' ,
1100957 'maxTicketsPerUser' ,
1101958 'dmOnClose' ,
@@ -1113,9 +970,7 @@ async function handleDeleteSystem(btnInteraction, rootInteraction, guildConfig,
1113970 const messages = await panelChannel . messages . fetch ( { limit : 50 } ) . catch ( ( ) => null ) ;
1114971 if ( messages ) {
1115972 const found = messages . find (
1116- ( m ) =>
1117- m . author . id === client . user . id &&
1118- messageHasButtonCustomIdPrefix ( m , 'create_ticket' ) ,
973+ m => m . author . id === client . user . id && messageHasButtonCustomId ( m , 'create_ticket' ) ,
1119974 ) ;
1120975 if ( found ) await found . delete ( ) . catch ( ( ) => { } ) ;
1121976 }
0 commit comments