From 23241448133e5015411eb6bbc6f41f2b8057b493 Mon Sep 17 00:00:00 2001 From: Danny Simms <103758200+GreyNewfie@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:23:33 -0330 Subject: [PATCH 1/4] feat(slack): Enhance Slack channel selection with pagination support and improved error handling - Added state management for hasMoreChannels and hasMoreUsers to handle pagination. - Updated Autocomplete component to display messages when not all channels or users are loaded. - Improved error handling for channel selection, allowing manual entry with proper formatting. - Enhanced value handling in Autocomplete to support both selected and manually entered values. --- lib/components/Slack/SlackConnect.tsx | 118 ++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 16 deletions(-) diff --git a/lib/components/Slack/SlackConnect.tsx b/lib/components/Slack/SlackConnect.tsx index e969ff7..4c4b5c6 100644 --- a/lib/components/Slack/SlackConnect.tsx +++ b/lib/components/Slack/SlackConnect.tsx @@ -7,7 +7,8 @@ import { Alert, Stack, Autocomplete, - TextField + TextField, + Paper } from '@mui/material'; import { NotificationAPIContext } from '../Provider/context'; import { User } from '@notificationapi/core/dist/interfaces'; @@ -57,6 +58,8 @@ export function SlackConnect({ const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [isEditing, setIsEditing] = useState(false); + const [hasMoreChannels, setHasMoreChannels] = useState(false); + const [hasMoreUsers, setHasMoreUsers] = useState(false); const fetchUserSlackStatus = useCallback(async () => { if (!client) return; @@ -110,6 +113,9 @@ export function SlackConnect({ })) ]; + setHasMoreChannels(response.hasMoreChannels || false); + setHasMoreUsers(response.hasMoreUsers || false); + setChannels(allOptions); return allOptions; } catch (err) { @@ -200,18 +206,29 @@ export function SlackConnect({ setLoading(true); setError(null); - // Find the selected channel info to get its name and type + let formattedChannel: string; + + // Check if selectedChannel is an ID (from dropdown selection) const channelInfo = channels.find((c) => c.id === selectedChannel); - if (!channelInfo) { - setError('Channel not found. Please try again.'); + + if (channelInfo) { + // User selected from dropdown - format as #channelname or @username + formattedChannel = `${channelInfo.type === 'channel' ? '#' : '@'}${channelInfo.name}`; + } else if ( + selectedChannel.startsWith('#') || + selectedChannel.startsWith('@') + ) { + // User typed a custom value with proper prefix (freeSolo mode) + formattedChannel = selectedChannel; + } else { + // Invalid format - show error + setError( + 'Please enter a channel as #channel-name or user as @username' + ); + setLoading(false); return; } - // Format the channel as #channelname or @username - const formattedChannel = `${channelInfo.type === 'channel' ? '#' : '@'}${ - channelInfo.name - }`; - // Set the selected channel with formatted name await client.slack.setChannel(formattedChannel); @@ -276,6 +293,10 @@ export function SlackConnect({ if (matchingChannel) { setSelectedChannel(matchingChannel.id); + } else { + // Channel not found in list (possibly due to pagination) + // Set the formatted channel string directly for freeSolo mode + setSelectedChannel(slackChannel); } } }; @@ -361,7 +382,35 @@ export function SlackConnect({ ( + + {children && (hasMoreChannels || hasMoreUsers) && ( + + {hasMoreChannels && hasMoreUsers + ? 'Not all channels and users could be loaded.' + : hasMoreChannels + ? 'Not all channels could be loaded.' + : 'Not all users could be loaded.'}{' '} + Type #channel-name or @username to enter manually. + + )} + {children} + + ) + }} options={channels.sort((a, b) => { if (a.type === b.type) { return a.name.localeCompare(b.name); @@ -369,20 +418,57 @@ export function SlackConnect({ return a.type === 'channel' ? -1 : 1; })} groupBy={(option) => - option.type === 'channel' ? 'Channels' : 'Users' + typeof option === 'string' + ? '' + : option.type === 'channel' + ? 'Channels' + : 'Users' } getOptionLabel={(option) => - `${option.type === 'channel' ? '#' : '@'}${option.name}` + typeof option === 'string' + ? option + : `${option.type === 'channel' ? '#' : '@'}${option.name}` } - sx={{ minWidth: 200, flexGrow: 1 }} // Allow it to grow but keep min width + sx={{ minWidth: 200, flexGrow: 1 }} size="small" - value={channels.find((c) => c.id === selectedChannel) || null} + value={ + channels.find((c) => c.id === selectedChannel) || + (selectedChannel && + (selectedChannel.startsWith('#') || + selectedChannel.startsWith('@')) + ? selectedChannel + : null) + } onChange={(_, newValue) => { - setSelectedChannel(newValue ? newValue.id : ''); + if (typeof newValue === 'string') { + setSelectedChannel(newValue); + } else if (newValue) { + setSelectedChannel(newValue.id); + } else { + setSelectedChannel(''); + } + }} + onInputChange={(_, newInputValue, reason) => { + if ( + (hasMoreChannels || hasMoreUsers) && + reason === 'input' && + (newInputValue.startsWith('#') || + newInputValue.startsWith('@')) + ) { + setSelectedChannel(newInputValue); + } + }} + isOptionEqualToValue={(option, value) => { + if (typeof option === 'string' || typeof value === 'string') { + return option === value; + } + return option.id === value.id; }} - isOptionEqualToValue={(option, value) => option.id === value.id} renderInput={(params) => ( - + )} />