Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,7 @@ export function SlackChannelSelector({
<div className='flex max-w-[calc(100%-20px)] items-center gap-2 overflow-hidden'>
<SlackIcon className='h-4 w-4 text-[#611f69]' />
{cachedChannelName ? (
<>
<Hash className='h-1.5 w-1.5' />
<span className='truncate font-normal'>{cachedChannelName}</span>
</>
<span className='truncate font-normal'>{cachedChannelName}</span>
) : (
<span className='truncate text-muted-foreground'>{label}</span>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,21 +210,23 @@ export function ConfluenceFileSelector({
}

const data = await response.json()
if (data.file) {
setSelectedFile(data.file)
onFileInfoChange?.(data.file)
} else {
const fileInfo: ConfluenceFileInfo = {
id: data.id || pageId,
name: data.title || `Page ${pageId}`,
mimeType: 'confluence/page',
webViewLink: undefined,
modifiedTime: undefined,
spaceId: undefined,
url: undefined,
}
setSelectedFile(fileInfo)
onFileInfoChange?.(fileInfo)
const fileInfo: ConfluenceFileInfo = {
id: data.id || pageId,
name: data.title || `Page ${pageId}`,
mimeType: 'confluence/page',
webViewLink: `https://${domain}/wiki/pages/${data.id}`,
modifiedTime: data.version?.when,
spaceId: data.spaceId,
url: `https://${domain}/wiki/pages/${data.id}`,
}
setSelectedFile(fileInfo)
onFileInfoChange?.(fileInfo)

// Cache the page name in display names store
if (selectedCredentialId) {
useDisplayNamesStore
.getState()
.setDisplayNames('files', selectedCredentialId, { [fileInfo.id]: fileInfo.name })
}
} catch (error) {
logger.error('Error fetching page info:', error)
Expand Down Expand Up @@ -394,6 +396,13 @@ export function ConfluenceFileSelector({
}
}, [value, onFileInfoChange])

// Fetch page info on mount if we have a value but no selectedFile state
useEffect(() => {
if (value && selectedCredentialId && domain && !selectedFile) {
fetchPageInfo(value)
}
}, [value, selectedCredentialId, domain, selectedFile, fetchPageInfo])

// Handle file selection
const handleSelectFile = (file: ConfluenceFileInfo) => {
setSelectedFileId(file.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,13 @@ export function JiraIssueSelector({
}
}, [value, onIssueInfoChange])

// Fetch issue info on mount if we have a value but no selectedIssue state
useEffect(() => {
if (value && selectedCredentialId && domain && projectId && !selectedIssue) {
fetchIssueInfo(value)
}
}, [value, selectedCredentialId, domain, projectId, selectedIssue, fetchIssueInfo])

// Handle issue selection
const handleSelectIssue = (issue: JiraIssueInfo) => {
setSelectedIssueId(issue.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export function TeamsMessageSelector({
const initialFetchRef = useRef(false)
const [error, setError] = useState<string | null>(null)
const [selectionStage, setSelectionStage] = useState<'team' | 'channel' | 'chat'>(selectionType)
const lastRestoredValueRef = useRef<string | null>(null)

// Get cached display name
const cachedMessageName = useDisplayNamesStore(
Expand Down Expand Up @@ -240,6 +241,18 @@ export function TeamsMessageSelector({

setChannels(channelsData)

// Cache channel names in display names store
if (selectedCredentialId && channelsData.length > 0) {
const channelMap = channelsData.reduce(
(acc: Record<string, string>, channel: TeamsMessageInfo) => {
acc[channel.channelId!] = channel.displayName
return acc
},
{}
)
useDisplayNamesStore.getState().setDisplayNames('files', selectedCredentialId, channelMap)
}

// If we have a selected channel ID, find it in the list
if (selectedChannelId) {
const channel = channelsData.find(
Expand Down Expand Up @@ -304,6 +317,14 @@ export function TeamsMessageSelector({

setChats(chatsData)

if (selectedCredentialId && chatsData.length > 0) {
const chatMap = chatsData.reduce((acc: Record<string, string>, chat: TeamsMessageInfo) => {
acc[chat.id] = chat.displayName
return acc
}, {})
useDisplayNamesStore.getState().setDisplayNames('files', selectedCredentialId, chatMap)
}

// If we have a selected chat ID, find it in the list
if (selectedChatId) {
const chat = chatsData.find((c: TeamsMessageInfo) => c.chatId === selectedChatId)
Expand Down Expand Up @@ -547,6 +568,19 @@ export function TeamsMessageSelector({

if (response.ok) {
const data = await response.json()

// Cache all chat names
if (data.chats && selectedCredentialId) {
const chatMap = data.chats.reduce(
(acc: Record<string, string>, c: { id: string; displayName: string }) => {
acc[c.id] = c.displayName
return acc
},
{}
)
useDisplayNamesStore.getState().setDisplayNames('files', selectedCredentialId, chatMap)
}

const chat = data.chats.find((c: { id: string; displayName: string }) => c.id === chatId)
if (chat) {
const chatInfo: TeamsMessageInfo = {
Expand Down Expand Up @@ -691,14 +725,20 @@ export function TeamsMessageSelector({
// Restore selection whenever the canonical value changes
useEffect(() => {
if (value && selectedCredentialId) {
if (selectionType === 'team') {
restoreTeamSelection(value)
} else if (selectionType === 'chat') {
restoreChatSelection(value)
} else if (selectionType === 'channel') {
restoreChannelSelection(value)
// Only restore if we haven't already restored this value
if (lastRestoredValueRef.current !== value) {
lastRestoredValueRef.current = value

if (selectionType === 'team') {
restoreTeamSelection(value)
} else if (selectionType === 'chat') {
restoreChatSelection(value)
} else if (selectionType === 'channel') {
restoreChannelSelection(value)
}
}
} else {
lastRestoredValueRef.current = null
setSelectedMessage(null)
}
}, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,14 @@ export function WealthboxFileSelector({
if (data.item) {
setSelectedItem(data.item)
onFileInfoChange?.(data.item)

// Cache the item name in display names store
if (selectedCredentialId) {
useDisplayNamesStore
.getState()
.setDisplayNames('files', selectedCredentialId, { [data.item.id]: data.item.name })
}

return data.item
}
} else {
Expand Down Expand Up @@ -233,7 +241,20 @@ export function WealthboxFileSelector({
}
}, [selectedCredentialId, open, fetchAvailableItems])

// Fetch the selected item metadata only once when needed
// Fetch item info on mount if we have a value but no selectedItem state
useEffect(() => {
if (value && selectedCredentialId && !selectedItem) {
fetchItemById(value)
}
}, [value, selectedCredentialId, selectedItem, fetchItemById])

// Clear selectedItem when value is cleared
useEffect(() => {
if (!value) {
setSelectedItem(null)
onFileInfoChange?.(null)
}
}, [value, onFileInfoChange])

// Handle search input changes with debouncing
const handleSearchChange = useCallback(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Check, ChevronDown, RefreshCw } from 'lucide-react'
import { Check, ChevronDown, ExternalLink, RefreshCw, X } from 'lucide-react'
import { JiraIcon } from '@/components/icons'
import { Button } from '@/components/ui/button'
import {
Expand Down Expand Up @@ -74,6 +74,7 @@ export function JiraProjectSelector({
const [projects, setProjects] = useState<JiraProjectInfo[]>([])
const [selectedCredentialId, setSelectedCredentialId] = useState<string>(credentialId || '')
const [selectedProjectId, setSelectedProjectId] = useState(value)
const [selectedProject, setSelectedProject] = useState<JiraProjectInfo | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [showOAuthModal, setShowOAuthModal] = useState(false)
const initialFetchRef = useRef(false)
Expand Down Expand Up @@ -210,8 +211,10 @@ export function JiraProjectSelector({
}

if (projectInfo) {
setSelectedProject(projectInfo)
onProjectInfoChange?.(projectInfo)
} else {
setSelectedProject(null)
onProjectInfoChange?.(null)
}
} catch (error) {
Expand Down Expand Up @@ -322,6 +325,7 @@ export function JiraProjectSelector({
(project: JiraProjectInfo) => project.id === selectedProjectId
)
if (projectInfo) {
setSelectedProject(projectInfo)
onProjectInfoChange?.(projectInfo)
} else if (!searchQuery && selectedProjectId) {
// If we can't find the project in the list, try to fetch it directly
Expand Down Expand Up @@ -370,10 +374,18 @@ export function JiraProjectSelector({
// Clear callback when value is cleared
useEffect(() => {
if (!value) {
setSelectedProject(null)
onProjectInfoChange?.(null)
}
}, [value, onProjectInfoChange])

// Fetch project info on mount if we have a value but no selectedProject state
useEffect(() => {
if (value && selectedCredentialId && domain && !selectedProject) {
fetchProjectInfo(value)
}
}, [value, selectedCredentialId, domain, selectedProject, fetchProjectInfo])

// Handle open change
const handleOpenChange = (isOpen: boolean) => {
setOpen(isOpen)
Expand All @@ -386,6 +398,7 @@ export function JiraProjectSelector({
// Handle project selection
const handleSelectProject = (project: JiraProjectInfo) => {
setSelectedProjectId(project.id)
setSelectedProject(project)
onChange(project.id, project)
onProjectInfoChange?.(project)
setOpen(false)
Expand All @@ -401,6 +414,7 @@ export function JiraProjectSelector({
// Clear selection
const handleClearSelection = () => {
setSelectedProjectId('')
setSelectedProject(null)
setError(null)
onChange('', undefined)
onProjectInfoChange?.(null)
Expand Down Expand Up @@ -558,6 +572,55 @@ export function JiraProjectSelector({
</PopoverContent>
)}
</Popover>

{/* Project preview */}
{showPreview && selectedProject && (
<div className='relative mt-2 rounded-md border border-muted bg-muted/10 p-2'>
<div className='absolute top-2 right-2'>
<Button
variant='ghost'
size='icon'
className='h-5 w-5 hover:bg-muted'
onClick={handleClearSelection}
>
<X className='h-3 w-3' />
</Button>
</div>
<div className='flex items-center gap-3 pr-4'>
<div className='flex h-6 w-6 flex-shrink-0 items-center justify-center rounded bg-muted/20'>
{selectedProject.avatarUrl ? (
<img
src={selectedProject.avatarUrl}
alt={selectedProject.name}
className='h-6 w-6 rounded'
/>
) : (
<JiraIcon className='h-4 w-4' />
)}
</div>
<div className='min-w-0 flex-1 overflow-hidden'>
<div className='flex items-center gap-2'>
<h4 className='truncate font-medium text-xs'>{selectedProject.name}</h4>
<span className='whitespace-nowrap text-muted-foreground text-xs'>
{selectedProject.key}
</span>
</div>
{selectedProject.url ? (
<a
href={selectedProject.url}
target='_blank'
rel='noopener noreferrer'
className='flex items-center gap-1 text-foreground text-xs hover:underline'
onClick={(e) => e.stopPropagation()}
>
<span>Open in Jira</span>
<ExternalLink className='h-3 w-3' />
</a>
) : null}
</div>
</div>
</div>
)}
</div>

{showOAuthModal && (
Expand Down
Loading