From 3a66ecd32df50e79fdcf15b6341c634558a80e3b Mon Sep 17 00:00:00 2001 From: priyanshu Date: Fri, 2 May 2025 14:24:14 +0530 Subject: [PATCH 1/3] Add error block IDs to CreateModalEnum --- src/enum/modals/createModal.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/enum/modals/createModal.ts b/src/enum/modals/createModal.ts index cdfb54a..48f04b6 100644 --- a/src/enum/modals/createModal.ts +++ b/src/enum/modals/createModal.ts @@ -8,4 +8,6 @@ export enum CreateModalEnum { CLOSE_BLOCK_ID = 'create-new-reply-block-id', SUBMIT_ACTION_ID = 'submit-create-action-id', SUBMIT_BLOCK_ID = 'submit-create-block-id', + NAME_ERROR_BLOCK_ID = 'name-error-block-id', + BODY_ERROR_BLOCK_ID = 'body-error-block-id', } From f2be1ff600e95275d8f631f180c78292de389b55 Mon Sep 17 00:00:00 2001 From: priyanshu Date: Fri, 2 May 2025 14:24:30 +0530 Subject: [PATCH 2/3] Enhance CreateReplyModal to include error context blocks for name and body fields --- src/modal/createModal.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/modal/createModal.ts b/src/modal/createModal.ts index 7815e98..972519a 100644 --- a/src/modal/createModal.ts +++ b/src/modal/createModal.ts @@ -4,7 +4,7 @@ import { IRead, IUIKitSurfaceViewParam, } from '@rocket.chat/apps-engine/definition/accessors'; -import { TextObjectType, InputBlock } from '@rocket.chat/ui-kit'; +import { TextObjectType, InputBlock, ContextBlock } from '@rocket.chat/ui-kit'; import { QuickRepliesApp } from '../../QuickRepliesApp'; import { IUser } from '@rocket.chat/apps-engine/definition/users'; @@ -25,10 +25,14 @@ export async function CreateReplyModal( modify: IModify, room: IRoom, language: Language, + errors?: { + nameError?: boolean; + bodyError?: boolean; + } ): Promise { const { elementBuilder, blockBuilder } = app.getUtils(); - const blocks: InputBlock[] = []; + const blocks: Array = []; const labelReplyName = t('Reply_Name_Label', language); const placeholderReplyName = t('Reply_Name_Placeholder', language); @@ -46,6 +50,17 @@ export async function CreateReplyModal( }, ); + blocks.push(inputReplyName); + + // Add name error context block if needed + if (errors?.nameError) { + const nameErrorContext = blockBuilder.createContextBlock({ + blockId: CreateModalEnum.NAME_ERROR_BLOCK_ID, + contextElements: ['**❗ Name field is required**'] + }); + blocks.push(nameErrorContext); + } + const labelReplyBody = t('Reply_Body_Label', language); const placeholderReplyBody = t('Reply_Body_Placeholder', language); @@ -63,7 +78,16 @@ export async function CreateReplyModal( }, ); - blocks.push(inputReplyName, inputReplyBody); + blocks.push(inputReplyBody); + + // Add body error context block if needed + if (errors?.bodyError) { + const bodyErrorContext = blockBuilder.createContextBlock({ + blockId: CreateModalEnum.BODY_ERROR_BLOCK_ID, + contextElements: ['**❗ Body field is required**'] + }); + blocks.push(bodyErrorContext); + } const submit = elementBuilder.addButton( { text: t('Create_Button', language), style: ButtonStyle.PRIMARY }, From 56c0f51bc79f3abb2e482552164619f3e0b724ae Mon Sep 17 00:00:00 2001 From: priyanshu Date: Fri, 2 May 2025 14:26:10 +0530 Subject: [PATCH 3/3] Update ExecuteViewSubmitHandler to update error handling for required fields in reply submission. --- src/handlers/ExecuteViewSubmitHandler.ts | 213 ++++++++++++++--------- 1 file changed, 127 insertions(+), 86 deletions(-) diff --git a/src/handlers/ExecuteViewSubmitHandler.ts b/src/handlers/ExecuteViewSubmitHandler.ts index 53334e5..336be3d 100644 --- a/src/handlers/ExecuteViewSubmitHandler.ts +++ b/src/handlers/ExecuteViewSubmitHandler.ts @@ -34,6 +34,7 @@ import { AIusagePreference } from '../definition/helper/userPreference'; import { listReplyContextualBar } from '../modal/listContextualBar'; import { Receiverstorage } from '../storage/ReceiverStorage'; import { Replacements } from '../definition/helper/message'; +import { CreateReplyModal } from '../modal/createModal'; export class ExecuteViewSubmitHandler { private context: UIKitViewSubmitInteractionContext; @@ -130,12 +131,33 @@ export class ExecuteViewSubmitHandler { const name = nameStateValue ? nameStateValue.trim() : ''; const body = bodyStateValue ? bodyStateValue.trim() : ''; - if (!name || !body) { - const errorMessage = `${t('Error_Fill_Required_Fields', language)}`; - await sendNotification(this.read, this.modify, user, room, { - message: errorMessage, - }); - return this.context.getInteractionResponder().errorResponse(); + + // Check for empty fields + const errors = { + nameError: !name, + bodyError: !body + }; + + // If any errors exist, recreate the modal with error messages + if (errors.nameError || errors.bodyError) { + const modal = await CreateReplyModal( + this.app, + user, + this.read, + this.persistence, + this.modify, + room, + language, + errors + ); + + if (modal instanceof Error) { + this.app.getLogger().error(modal.message); + return this.context.getInteractionResponder().errorResponse(); + } + + // Update the view with errors instead of creating a new one + return this.context.getInteractionResponder().updateModalViewResponse(modal); } const replyStorage = new ReplyStorage( @@ -171,91 +193,14 @@ export class ExecuteViewSubmitHandler { } } - private async handleSetUserPreference( - room: IRoom, - user: IUser, - view: IUIKitSurface, - ): Promise { - const languageInput = view.state?.[ - UserPreferenceModalEnum.LANGUAGE_INPUT_DROPDOWN_BLOCK_ID - ]?.[ - UserPreferenceModalEnum.LANGUAGE_INPUT_DROPDOWN_ACTION_ID - ] as Language; - - const AIpreferenceInput = view.state?.[ - UserPreferenceModalEnum.AI_PREFERENCE_DROPDOWN_BLOCK_ID - ]?.[ - UserPreferenceModalEnum.AI_PREFERENCE_DROPDOWN_ACTION_ID - ] as AIusagePreference; - - const AIoptionInput = - view.state?.[UserPreferenceModalEnum.AI_OPTION_DROPDOWN_BLOCK_ID]?.[ - UserPreferenceModalEnum.AI_OPTION_DROPDOWN_ACTION_ID - ]; - - const OpenAIAPIKeyInput = - view.state?.[UserPreferenceModalEnum.OPEN_AI_API_KEY_BLOCK_ID]?.[ - UserPreferenceModalEnum.OPEN_AI_API_KEY_ACTION_ID - ]; - const OpenAImodelInput = - view.state?.[UserPreferenceModalEnum.OPEN_AI_MODEL_BLOCK_ID]?.[ - UserPreferenceModalEnum.OPEN_AI_MODEL_ACTION_ID - ]; - const GeminiAPIKeyInput = - view.state?.[UserPreferenceModalEnum.GEMINI_API_KEY_BLOCK_ID]?.[ - UserPreferenceModalEnum.GEMINI_API_KEY_ACTION_ID - ]; - const SelfHostedURLInput = - view.state?.[UserPreferenceModalEnum.SELF_HOSTED_URL_BLOCK_ID]?.[ - UserPreferenceModalEnum.SELF_HOSTED_URL_ACTION_ID - ]; - - const PromptConfigurationInput = - view.state?.[ - UserPreferenceModalEnum.PROMPT_CONFIG_INPUT_BLOCK_ID - ]?.[UserPreferenceModalEnum.PROMPT_CONFIG_INPUT_ACTION_ID]; - - const userPreference = new UserPreferenceStorage( - this.persistence, - this.read.getPersistenceReader(), - user.id, - ); - - await userPreference.storeUserPreference({ - userId: user.id, - language: languageInput, - AIusagePreference: AIpreferenceInput, - AIconfiguration: { - AIPrompt: PromptConfigurationInput, - AIProvider: AIoptionInput, - openAI: { - apiKey: OpenAIAPIKeyInput, - model: OpenAImodelInput, - }, - gemini: { - apiKey: GeminiAPIKeyInput, - }, - selfHosted: { - url: SelfHostedURLInput, - }, - }, - }); - - await sendNotification(this.read, this.modify, user, room, { - message: t('Config_Updated_Successfully', languageInput), - }); - - return this.context.getInteractionResponder().successResponse(); - } - private async handleSend( - room: IRoom, + room: IRoom, user: IUser, view: IUIKitSurface, replyId: string, ): Promise { - try { - const replyStorage = new ReplyStorage( + try { + const replyStorage = new ReplyStorage( this.persistence, this.read.getPersistenceReader(), ); @@ -312,6 +257,83 @@ export class ExecuteViewSubmitHandler { } } + private async handleSetUserPreference( + room: IRoom, + user: IUser, + view: IUIKitSurface, + ): Promise { + const languageInput = view.state?.[ + UserPreferenceModalEnum.LANGUAGE_INPUT_DROPDOWN_BLOCK_ID + ]?.[ + UserPreferenceModalEnum.LANGUAGE_INPUT_DROPDOWN_ACTION_ID + ] as Language; + + const AIpreferenceInput = view.state?.[ + UserPreferenceModalEnum.AI_PREFERENCE_DROPDOWN_BLOCK_ID + ]?.[ + UserPreferenceModalEnum.AI_PREFERENCE_DROPDOWN_ACTION_ID + ] as AIusagePreference; + + const AIoptionInput = + view.state?.[UserPreferenceModalEnum.AI_OPTION_DROPDOWN_BLOCK_ID]?.[ + UserPreferenceModalEnum.AI_OPTION_DROPDOWN_ACTION_ID + ]; + + const OpenAIAPIKeyInput = + view.state?.[UserPreferenceModalEnum.OPEN_AI_API_KEY_BLOCK_ID]?.[ + UserPreferenceModalEnum.OPEN_AI_API_KEY_ACTION_ID + ]; + const OpenAImodelInput = + view.state?.[UserPreferenceModalEnum.OPEN_AI_MODEL_BLOCK_ID]?.[ + UserPreferenceModalEnum.OPEN_AI_MODEL_ACTION_ID + ]; + const GeminiAPIKeyInput = + view.state?.[UserPreferenceModalEnum.GEMINI_API_KEY_BLOCK_ID]?.[ + UserPreferenceModalEnum.GEMINI_API_KEY_ACTION_ID + ]; + const SelfHostedURLInput = + view.state?.[UserPreferenceModalEnum.SELF_HOSTED_URL_BLOCK_ID]?.[ + UserPreferenceModalEnum.SELF_HOSTED_URL_ACTION_ID + ]; + + const PromptConfigurationInput = + view.state?.[ + UserPreferenceModalEnum.PROMPT_CONFIG_INPUT_BLOCK_ID + ]?.[UserPreferenceModalEnum.PROMPT_CONFIG_INPUT_ACTION_ID]; + + const userPreference = new UserPreferenceStorage( + this.persistence, + this.read.getPersistenceReader(), + user.id, + ); + + await userPreference.storeUserPreference({ + userId: user.id, + language: languageInput, + AIusagePreference: AIpreferenceInput, + AIconfiguration: { + AIPrompt: PromptConfigurationInput, + AIProvider: AIoptionInput, + openAI: { + apiKey: OpenAIAPIKeyInput, + model: OpenAImodelInput, + }, + gemini: { + apiKey: GeminiAPIKeyInput, + }, + selfHosted: { + url: SelfHostedURLInput, + }, + }, + }); + + await sendNotification(this.read, this.modify, user, room, { + message: t('Config_Updated_Successfully', languageInput), + }); + + return this.context.getInteractionResponder().successResponse(); + } + private async handleDelete( room: IRoom, user: IUser, @@ -396,6 +418,25 @@ export class ExecuteViewSubmitHandler { ? storedReply.body.trim() : ''; + // Check for empty fields and show red boundaries + const errors: Record = {}; + + if (!name) { + errors[EditModalEnum.REPLY_NAME_BLOCK_ID] = 'This field is required'; + } + + if (!body) { + errors[EditModalEnum.REPLY_BODY_BLOCK_ID] = 'This field is required'; + } + + if (Object.keys(errors).length > 0) { + // Return error response with error fields to highlight them with red boundaries + return this.context.getInteractionResponder().viewErrorResponse({ + viewId: view.id, + errors, + }); + } + const result = await replyStorage.updateReplyById( user, replyId,