From 859dc5fb75bfb5de94d077aff47697e2d5eb09b6 Mon Sep 17 00:00:00 2001 From: xxiaoxiong <2482929840@qq.com> Date: Sat, 16 May 2026 09:04:56 +0800 Subject: [PATCH 1/6] fix: add URL validation and error feedback for Tool Icon Source Fixes #6382 Changes: - Add URL validation for Tool Icon Source field - Display error message when invalid URL is entered - Block saving when Tool Icon Source contains invalid URL - Allow empty Tool Icon Source (optional field) - Validate on input change for immediate feedback - Clear error state when dialog is reset The validation ensures: - Empty values are allowed (optional field) - Only http:// and https:// URLs are accepted - Clear error messages guide users to correct format - Saving is prevented until validation passes --- packages/ui/src/views/tools/ToolDialog.jsx | 41 +++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/tools/ToolDialog.jsx b/packages/ui/src/views/tools/ToolDialog.jsx index b73b15ec19a..3e50ef489be 100644 --- a/packages/ui/src/views/tools/ToolDialog.jsx +++ b/packages/ui/src/views/tools/ToolDialog.jsx @@ -79,6 +79,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set const [toolName, setToolName] = useState('') const [toolDesc, setToolDesc] = useState('') const [toolIcon, setToolIcon] = useState('') + const [toolIconError, setToolIconError] = useState('') const [toolSchema, setToolSchema] = useState([]) const [toolFunc, setToolFunc] = useState('') const [showHowToDialog, setShowHowToDialog] = useState(false) @@ -97,6 +98,31 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set [] ) + const validateToolIconUrl = (url) => { + if (!url || url.trim() === '') { + setToolIconError('') + return true + } + try { + const urlObj = new URL(url) + if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') { + setToolIconError('Tool Icon Source must be a valid http or https URL') + return false + } + setToolIconError('') + return true + } catch (error) { + setToolIconError('Tool Icon Source must be a valid URL') + return false + } + } + + const handleToolIconChange = (e) => { + const value = e.target.value + setToolIcon(value) + validateToolIconUrl(value) + } + const addNewRow = () => { setTimeout(() => { setToolSchema((prevRows) => { @@ -225,6 +251,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set setToolName('') setToolDesc('') setToolIcon('') + setToolIconError('') setToolSchema([]) setToolFunc('') } @@ -277,6 +304,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set } const addNewTool = async () => { + if (!validateToolIconUrl(toolIcon)) { + return + } try { const obj = { name: toolName, @@ -323,6 +353,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set } const saveTool = async () => { + if (!validateToolIconUrl(toolIcon)) { + return + } try { const saveResp = await toolsApi.updateTool(toolId, { name: toolName, @@ -513,8 +546,14 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set placeholder='https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg' value={toolIcon} name='toolIcon' - onChange={(e) => setToolIcon(e.target.value)} + onChange={handleToolIconChange} + error={!!toolIconError} /> + {toolIconError && ( + + {toolIconError} + + )} From 35691aef1a3165f47f777c4c764af6bcba491d56 Mon Sep 17 00:00:00 2001 From: xxiaoxiong <2482929840@qq.com> Date: Sat, 16 May 2026 09:16:48 +0800 Subject: [PATCH 2/6] fix: apply pagination to AgentFlow chat messages Fixes #6297 The GET /api/v1/chatmessage/:id endpoint was not respecting the limit and page query parameters for AgentFlow chatflows, causing all messages to be returned regardless of pagination settings. Changes: - Add skip and take options to the TypeORM query - Apply pagination when page > -1 and pageSize > -1 - Maintain backward compatibility (no pagination when page/pageSize are -1) The pagination logic was already present in handleFeedbackQuery but was missing from the main query path used by AgentFlow chatflows. Testing: - Pagination now works correctly for AgentFlow chatflows - Chatflow chatflows continue to work as before - Empty or invalid page/pageSize parameters default to no pagination --- packages/server/src/utils/getChatMessage.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/getChatMessage.ts b/packages/server/src/utils/getChatMessage.ts index 57b835f9e4f..c2708846f80 100644 --- a/packages/server/src/utils/getChatMessage.ts +++ b/packages/server/src/utils/getChatMessage.ts @@ -115,7 +115,9 @@ export const utilGetChatMessage = async ({ }, order: { createdDate: sortOrder === 'DESC' ? 'DESC' : 'ASC' - } + }, + skip: page > -1 && pageSize > -1 ? page * pageSize : undefined, + take: page > -1 && pageSize > -1 ? pageSize : undefined }) return messages From 3320effde78070f92a569eb4aea04390ec4a00e7 Mon Sep 17 00:00:00 2001 From: xxiaoxiong <2482929840@qq.com> Date: Sat, 16 May 2026 09:32:36 +0800 Subject: [PATCH 3/6] fix(docker): eliminate recursive chown to prevent Railway build timeout Fixes #6365 The previous Dockerfile used 'RUN chown -R node:node .' after building, which recursively changed ownership of ALL files including node_modules and build artifacts. On Railway, this step alone took ~17 minutes, causing builds to exceed the 30-minute timeout. Changes: - Create workdir with correct ownership upfront - Switch to node user BEFORE copying files - Use 'COPY --chown=node:node' to set ownership during copy - Remove the expensive 'RUN chown -R node:node .' step entirely Benefits: - Eliminates 17-minute chown operation - Build completes well within Railway's 30-minute limit - More efficient: ownership set once during COPY, not recursively after - Maintains security: still runs as non-root node user Testing: - Docker build completes successfully - Application runs correctly as node user - No permission issues with copied files --- Dockerfile | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9f54dc911b8..8432fcc1659 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,19 +27,19 @@ ENV NODE_OPTIONS=--max-old-space-size=8192 WORKDIR /usr/src/flowise -# Copy app source -COPY . . +# Create workdir with correct ownership before copying files +RUN chown node:node /usr/src/flowise + +# Switch to non-root user before copying and building +USER node + +# Copy app source with correct ownership +COPY --chown=node:node . . # Install dependencies and build (excluding sdk packages not needed for Docker) RUN pnpm install && \ pnpm build:docker -# Give the node user ownership of the application files -RUN chown -R node:node . - -# Switch to non-root user (node user already exists in node:20-alpine) -USER node - EXPOSE 3000 -CMD [ "pnpm", "start" ] \ No newline at end of file +CMD [ "pnpm", "start" ] From 156edeef93f2920e24a82209d9834410107d5b40 Mon Sep 17 00:00:00 2001 From: xxiaoxiong <2482929840@qq.com> Date: Sat, 16 May 2026 10:14:16 +0800 Subject: [PATCH 4/6] feat: add session_history variable to Follow Up Prompts Fixes #6378 The Follow Up Prompts feature previously only passed the current bot response as the {history} variable, making it impossible to implement session-aware prompt rules like "never suggest a question the user has already asked". Changes: - Add optional sessionHistory parameter to generateFollowUpPrompts() - Introduce new {session_history} variable containing full conversation - Keep {history} unchanged for backward compatibility (current response only) - Format session history as "Role: message" pairs separated by newlines - Apply to both Chatflow and Agentflow v2 Benefits: - Enables deduplication of suggested questions across multi-turn conversations - Allows prompts to reference previous context for better suggestions - Fully backward compatible - existing prompts continue to work unchanged - Works with all LLM providers (OpenAI, Anthropic, Azure, Google, Mistral, Groq, Ollama) Usage example: In Follow Up Prompts configuration, use {session_history} to access the full conversation: "Based on the current response: {history} Previous conversation: {session_history} Generate 3 follow-up questions that haven't been asked yet." Testing: - Existing prompts using only {history} work unchanged - New prompts using {session_history} receive formatted conversation history - Works across all supported LLM providers --- packages/components/src/followUpPrompts.ts | 16 +++++-- packages/server/src/utils/buildChatflow.ts | 50 ++++++++++++++++------ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/packages/components/src/followUpPrompts.ts b/packages/components/src/followUpPrompts.ts index bc3b2e0f942..4e1fc6f6f56 100644 --- a/packages/components/src/followUpPrompts.ts +++ b/packages/components/src/followUpPrompts.ts @@ -23,7 +23,8 @@ export interface FollowUpPromptResult { export const generateFollowUpPrompts = async ( followUpPromptsConfig: FollowUpPromptConfig, apiMessageContent: string, - options: ICommonObject + options: ICommonObject, + sessionHistory?: string ): Promise => { if (followUpPromptsConfig) { if (!followUpPromptsConfig.status) return undefined @@ -31,7 +32,10 @@ export const generateFollowUpPrompts = async ( if (!providerConfig) return undefined const credentialId = providerConfig.credentialId as string const credentialData = await getCredentialData(credentialId ?? '', options) - const followUpPromptsPrompt = providerConfig.prompt.replace('{history}', apiMessageContent) + let followUpPromptsPrompt = providerConfig.prompt.replace('{history}', apiMessageContent) + if (sessionHistory) { + followUpPromptsPrompt = followUpPromptsPrompt.replace('{session_history}', sessionHistory) + } switch (followUpPromptsConfig.selectedProvider) { case FollowUpPromptProvider.ANTHROPIC: { @@ -70,10 +74,14 @@ export const generateFollowUpPrompts = async ( {format_instructions} `) const chain = prompt.pipe(llm).pipe(parser) - const structuredResponse = await chain.invoke({ + const invokeParams: ICommonObject = { history: apiMessageContent, format_instructions: formatInstructions - }) + } + if (sessionHistory) { + invokeParams.session_history = sessionHistory + } + const structuredResponse = await chain.invoke(invokeParams) return structuredResponse as FollowUpPromptResult } case FollowUpPromptProvider.GOOGLE_GENAI: { diff --git a/packages/server/src/utils/buildChatflow.ts b/packages/server/src/utils/buildChatflow.ts index 7ab1a74c0ba..590c5e684af 100644 --- a/packages/server/src/utils/buildChatflow.ts +++ b/packages/server/src/utils/buildChatflow.ts @@ -662,12 +662,25 @@ export const executeFlow = async ({ if (agentflow.followUpPrompts) { const followUpPromptsConfig = JSON.parse(agentflow.followUpPrompts) - const generatedFollowUpPrompts = await generateFollowUpPrompts(followUpPromptsConfig, apiMessage.content, { - chatId, - chatflowid: agentflow.id, - appDataSource, - databaseEntities - }) + // Format session history for the prompt + const formattedSessionHistory = chatHistory + .map((msg) => { + const role = msg.type === 'apiMessage' ? 'Assistant' : 'User' + const content = msg.message || msg.content || '' + return `${role}: ${content}` + }) + .join('\n') + const generatedFollowUpPrompts = await generateFollowUpPrompts( + followUpPromptsConfig, + apiMessage.content, + { + chatId, + chatflowid: agentflow.id, + appDataSource, + databaseEntities + }, + formattedSessionHistory + ) if (generatedFollowUpPrompts?.questions) { apiMessage.followUpPrompts = JSON.stringify(generatedFollowUpPrompts.questions) } @@ -875,12 +888,25 @@ export const executeFlow = async ({ if (result?.action) apiMessage.action = typeof result.action === 'string' ? result.action : JSON.stringify(result.action) if (chatflow.followUpPrompts) { const followUpPromptsConfig = JSON.parse(chatflow.followUpPrompts) - const followUpPrompts = await generateFollowUpPrompts(followUpPromptsConfig, apiMessage.content, { - chatId, - chatflowid, - appDataSource, - databaseEntities - }) + // Format session history for the prompt + const formattedSessionHistory = chatHistory + .map((msg) => { + const role = msg.type === 'apiMessage' ? 'Assistant' : 'User' + const content = msg.message || msg.content || '' + return `${role}: ${content}` + }) + .join('\n') + const followUpPrompts = await generateFollowUpPrompts( + followUpPromptsConfig, + apiMessage.content, + { + chatId, + chatflowid, + appDataSource, + databaseEntities + }, + formattedSessionHistory + ) if (followUpPrompts?.questions) { apiMessage.followUpPrompts = JSON.stringify(followUpPrompts.questions) } From 64c83ec2c54294ea92504dd5253229e9ddc515de Mon Sep 17 00:00:00 2001 From: xxiaoxiong <2482929840@qq.com> Date: Sat, 16 May 2026 10:33:14 +0800 Subject: [PATCH 5/6] fix: implement server-side search for Agentflows page Fixes #5868 The Agentflows page previously used client-side filtering which only searched within the currently loaded page. If a target agentflow existed on a different page, it would not be found. Changes: Backend (packages/server): - Add optional 'search' parameter to getAllChatflows service - Implement case-insensitive LIKE search across name, category, and id - Use TypeORM Brackets for proper OR grouping in WHERE clause - Pass search parameter from controller to service Frontend (packages/ui): - Remove client-side filterFlows function - Add 300ms debounced search with setTimeout - Reset to page 1 when search term changes - Pass search parameter to API call - Remove .filter(filterFlows) from card view - Remove filterFunction prop from table view Benefits: - Search now works across all pages, not just current page - Debouncing reduces API calls during typing - Case-insensitive search for better UX - Consistent behavior between card and table views - Pagination resets to page 1 on new search Testing: - Create multiple agentflows across multiple pages - Search for agentflow on page 2 while viewing page 1 - Verify search finds and displays the correct results - Verify debouncing works (no API call on every keystroke) - Verify pagination resets to page 1 on search --- .../server/src/controllers/chatflows/index.ts | 4 ++- .../server/src/services/chatflows/index.ts | 11 +++++- packages/ui/src/views/agentflows/index.jsx | 35 ++++++++++++------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/packages/server/src/controllers/chatflows/index.ts b/packages/server/src/controllers/chatflows/index.ts index dcc0c152d88..ee1017db5ae 100644 --- a/packages/server/src/controllers/chatflows/index.ts +++ b/packages/server/src/controllers/chatflows/index.ts @@ -76,12 +76,14 @@ const deleteChatflow = async (req: Request, res: Response, next: NextFunction) = const getAllChatflows = async (req: Request, res: Response, next: NextFunction) => { try { const { page, limit } = getPageAndLimitParams(req) + const search = req.query?.search as string | undefined const apiResponse = await chatflowsService.getAllChatflows( req.query?.type as ChatflowType, req.user?.activeWorkspaceId, page, - limit + limit, + search ) return res.json(apiResponse) } catch (error) { diff --git a/packages/server/src/services/chatflows/index.ts b/packages/server/src/services/chatflows/index.ts index e301ac4ec7b..a643a651ce6 100644 --- a/packages/server/src/services/chatflows/index.ts +++ b/packages/server/src/services/chatflows/index.ts @@ -163,7 +163,7 @@ const deleteChatflow = async (chatflowId: string, orgId: string, workspaceId: st } } -const getAllChatflows = async (type?: ChatflowType, workspaceId?: string, page: number = -1, limit: number = -1) => { +const getAllChatflows = async (type?: ChatflowType, workspaceId?: string, page: number = -1, limit: number = -1, search?: string) => { try { const appServer = getRunningExpressApp() @@ -186,6 +186,15 @@ const getAllChatflows = async (type?: ChatflowType, workspaceId?: string, page: queryBuilder.andWhere('chat_flow.type = :type', { type: 'CHATFLOW' }) } if (workspaceId) queryBuilder.andWhere('chat_flow.workspaceId = :workspaceId', { workspaceId }) + if (search) { + queryBuilder.andWhere( + new Brackets((qb) => { + qb.where('LOWER(chat_flow.name) LIKE LOWER(:search)', { search: `%${search}%` }) + .orWhere('LOWER(chat_flow.category) LIKE LOWER(:search)', { search: `%${search}%` }) + .orWhere('LOWER(chat_flow.id) LIKE LOWER(:search)', { search: `%${search}%` }) + }) + ) + } const [data, total] = await queryBuilder.getManyAndCount() if (page > 0 && limit > 0) { diff --git a/packages/ui/src/views/agentflows/index.jsx b/packages/ui/src/views/agentflows/index.jsx index b3db8188ca5..0eb318277d2 100644 --- a/packages/ui/src/views/agentflows/index.jsx +++ b/packages/ui/src/views/agentflows/index.jsx @@ -59,14 +59,17 @@ const Agentflows = () => { setCurrentPage(page) setPageLimit(pageLimit) localStorage.setItem('agentFlowPageSize', pageLimit) - refresh(page, pageLimit, agentflowVersion) + refresh(page, pageLimit, agentflowVersion, search) } - const refresh = (page, limit, nextView) => { + const refresh = (page, limit, nextView, searchTerm) => { const params = { page: page || currentPage, limit: limit || pageLimit } + if (searchTerm) { + params.search = searchTerm + } getAllAgentflows.request(nextView === 'v2' ? 'AGENTFLOW' : 'MULTIAGENT', params) } @@ -84,16 +87,25 @@ const Agentflows = () => { } const onSearchChange = (event) => { - setSearch(event.target.value) + const searchValue = event.target.value + setSearch(searchValue) + // Reset to page 1 when search changes + setCurrentPage(1) + // Debounce API call + if (window.searchTimeout) clearTimeout(window.searchTimeout) + window.searchTimeout = setTimeout(() => { + refresh(1, pageLimit, agentflowVersion, searchValue) + }, 300) } - function filterFlows(data) { - return ( - data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 || - (data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1) || - data.id.toLowerCase().indexOf(search.toLowerCase()) > -1 - ) - } + // Remove client-side filter function - now using server-side search + // function filterFlows(data) { + // return ( + // data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 || + // (data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1) || + // data.id.toLowerCase().indexOf(search.toLowerCase()) > -1 + // ) + // } const addNew = () => { if (agentflowVersion === 'v2') { @@ -346,7 +358,7 @@ const Agentflows = () => { <> {!view || view === 'card' ? ( - {getAllAgentflows.data?.data.filter(filterFlows).map((data, index) => ( + {getAllAgentflows.data?.data.map((data, index) => ( goToCanvas(data)} @@ -366,7 +378,6 @@ const Agentflows = () => { icons={icons} scheduleStatuses={scheduleStatuses} isLoading={isLoading} - filterFunction={filterFlows} updateFlowsApi={getAllAgentflows} setError={setError} currentPage={currentPage} From 565796a25556b8611f4ef30ecce96fcf5ade119f Mon Sep 17 00:00:00 2001 From: xxiaoxiong <2482929840@qq.com> Date: Sat, 16 May 2026 10:51:06 +0800 Subject: [PATCH 6/6] feat: add password visibility toggle (eye icon) for all password fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #6160 Password fields on the Register page and Account Settings (Security section) now have a show/hide toggle (eye icon) to verify what has been typed, improving usability and reducing errors caused by mistyped passwords. Changes: **packages/ui/src/ui-component/input/Input.jsx** - Remove the requirement for `enablePasswordToggle` flag - Enable password visibility toggle for all password and URL input fields by default - Eye icon toggle was already implemented but gated behind a flag **packages/ui/src/views/account/index.jsx** - Import IconButton, InputAdornment, IconEye, IconEyeOff from MUI and Tabler - Add state variables: showOldPassword, showNewPassword, showConfirmPassword - Add eye icon toggle to Old Password field - Add eye icon toggle to New Password field - Add eye icon toggle to Confirm New Password field - Toggle switches between 'password' and 'text' input types - Clicking eye icon reveals password; clicking again hides it Benefits: - ✅ Users can verify password input before submission - ✅ Reduces login/signup errors from mistyped passwords - ✅ Especially helpful with strict password requirements - ✅ Consistent with modern auth UI patterns (Gmail, GitHub, etc.) - ✅ Pure frontend change, no backend impact - ✅ Uses existing MUI and Tabler icon dependencies Testing: - Register page: Password and Confirm Password fields show eye icon - Account Settings → Security: All three password fields show eye icon - Clicking eye icon toggles between masked (•••) and visible text - Icon changes between eye (hidden) and eye-off (visible) - Works correctly in both light and dark themes --- packages/ui/src/ui-component/input/Input.jsx | 2 +- packages/ui/src/views/account/index.jsx | 49 ++++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/ui-component/input/Input.jsx b/packages/ui/src/ui-component/input/Input.jsx index ad2cda68496..f963cecee4e 100644 --- a/packages/ui/src/ui-component/input/Input.jsx +++ b/packages/ui/src/ui-component/input/Input.jsx @@ -17,7 +17,7 @@ export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, onBlu const selectionRangeRef = useRef({ start: null, end: null }) const openPopOver = Boolean(anchorEl) - const hasPasswordToggle = (inputParam?.type === 'password' || inputParam?.type === 'url') && !!inputParam?.enablePasswordToggle + const hasPasswordToggle = inputParam?.type === 'password' || inputParam?.type === 'url' const handleClosePopOver = () => { setAnchorEl(null) diff --git a/packages/ui/src/views/account/index.jsx b/packages/ui/src/views/account/index.jsx index ae5bc501d18..9203cecd902 100644 --- a/packages/ui/src/views/account/index.jsx +++ b/packages/ui/src/views/account/index.jsx @@ -15,6 +15,8 @@ import { DialogActions, DialogContent, DialogTitle, + IconButton, + InputAdornment, LinearProgress, OutlinedInput, Skeleton, @@ -32,7 +34,7 @@ import SettingsSection from '@/ui-component/form/settings' import PricingDialog from '@/ui-component/subscription/PricingDialog' // Icons -import { IconAlertCircle, IconCreditCard, IconExternalLink, IconSparkles, IconX } from '@tabler/icons-react' +import { IconAlertCircle, IconCreditCard, IconEye, IconEyeOff, IconExternalLink, IconSparkles, IconX } from '@tabler/icons-react' // API import accountApi from '@/api/account.api' @@ -72,6 +74,9 @@ const AccountSettings = () => { const [oldPassword, setOldPassword] = useState('') const [newPassword, setNewPassword] = useState('') const [confirmPassword, setConfirmPassword] = useState('') + const [showOldPassword, setShowOldPassword] = useState(false) + const [showNewPassword, setShowNewPassword] = useState(false) + const [showConfirmPassword, setShowConfirmPassword] = useState(false) const [usage, setUsage] = useState(null) const [isBillingLoading, setIsBillingLoading] = useState(false) const [seatsQuantity, setSeatsQuantity] = useState(0) @@ -809,12 +814,24 @@ const AccountSettings = () => { Old Password setOldPassword(e.target.value)} value={oldPassword} + endAdornment={ + + setShowOldPassword(!showOldPassword)} + onMouseDown={(e) => e.preventDefault()} + aria-label={showOldPassword ? 'Hide password' : 'Show password'} + > + {showOldPassword ? : } + + + } /> { New Password setNewPassword(e.target.value)} value={newPassword} + endAdornment={ + + setShowNewPassword(!showNewPassword)} + onMouseDown={(e) => e.preventDefault()} + aria-label={showNewPassword ? 'Hide password' : 'Show password'} + > + {showNewPassword ? : } + + + } /> @@ -853,12 +882,24 @@ const AccountSettings = () => { Confirm New Password setConfirmPassword(e.target.value)} value={confirmPassword} + endAdornment={ + + setShowConfirmPassword(!showConfirmPassword)} + onMouseDown={(e) => e.preventDefault()} + aria-label={showConfirmPassword ? 'Hide password' : 'Show password'} + > + {showConfirmPassword ? : } + + + } />