Skip to content
Merged

Pm 4882 #1753

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 @@ -30,6 +30,9 @@ import styles from './TalentSearchPage.module.scss'

export const TalentSearchPage: FC = () => {
const skipNextAutoSearchRef = useRef<boolean>(false)
const searchGenerationRef = useRef<number>(0) // ← add this

const [lastSearchedDescription, setLastSearchedDescription] = useState<string>('')
const countryLookup: CountryLookup[] | undefined = useCountryLookup()
const [jobDescription, setJobDescription] = useState<string>('')
const [isExtractingSkills, setIsExtractingSkills] = useState<boolean>(false)
Expand All @@ -45,11 +48,7 @@ export const TalentSearchPage: FC = () => {
const [results, setResults] = useState<SearchTalent[]>([])
const [totalResults, setTotalResults] = useState<number>(0)
const [currentPage, setCurrentPage] = useState<number>(1)

// const breadCrumb = useMemo(
// () => [{ index: 1, label: 'Talent Search' }],
// [],
// )
const [isLoading, setIsLoading] = useState<boolean>(false)
const countryOptions = useMemo(
(): InputSelectOption[] => [
{ label: 'All Countries', value: 'all' },
Expand Down Expand Up @@ -84,9 +83,7 @@ export const TalentSearchPage: FC = () => {

return true
}), [countryOptions, onlyActive, results, selectedCountry])
const foundMembersCount = selectedCountry === 'all'
? (totalResults || filteredResults.length)
: filteredResults.length

const hasMoreResults = results.length < totalResults

const loadSkillOptions = useCallback(async (query: string): Promise<InputMultiselectOption[]> => {
Expand All @@ -104,16 +101,17 @@ export const TalentSearchPage: FC = () => {
skillsToSearch: InputMultiselectOption[],
overrides?: {
append?: boolean
generation?: number
openToWork?: boolean
page?: number
recentlyActive?: boolean
},
): Promise<void> => {
): Promise<boolean> => {
const append = overrides?.append === true
const generation = overrides?.generation
const openToWork = overrides?.openToWork ?? onlyOpenToWork
const page = overrides?.page ?? 1
const recentlyActive = overrides?.recentlyActive ?? onlyActive

const payload: MemberSearchPayload = {
limit: MEMBER_SEARCH_LIMIT,
openToWork,
Expand All @@ -130,19 +128,22 @@ export const TalentSearchPage: FC = () => {
skillSearchType: 'OR',
verifiedProfile: true,
}

if (append) {
setIsLoadingMore(true)
} else {
setIsSearchingMembers(true)
setIsLoading(true)
}

setErrorMessage('')

try {
const response = await searchMembers(payload)
const fetchedData = Array.isArray(response?.data) ? response.data : []
// If generation was provided and has changed, discard stale results
if (generation !== undefined && searchGenerationRef.current !== generation) {
return false
}

const fetchedData = Array.isArray(response?.data) ? response.data : []
setResults(prevResults => {
if (!append) {
return fetchedData
Expand All @@ -156,29 +157,33 @@ export const TalentSearchPage: FC = () => {
merged.push(item)
}
})

return merged
})
setTotalResults(Number(response?.total || 0))
setCurrentPage(Number(response?.page || page))
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
return true
} catch {
if (!append) {
setResults([])
setTotalResults(0)
setCurrentPage(1)
setLastSearchedDescription('')
}

setErrorMessage('Failed to search matching members. Please try again.')
return false
} finally {
if (append) {
setIsLoadingMore(false)
} else {
setIsSearchingMembers(false)
setIsLoading(false)
}
}
}, [onlyActive, onlyOpenToWork])

const clearAllFilters = useCallback((): void => {
searchGenerationRef.current += 1
setSelectedCountry('all')
setOnlyOpenToWork(true)
setOnlyActive(true)
Expand All @@ -188,6 +193,7 @@ export const TalentSearchPage: FC = () => {
setTotalResults(0)
setCurrentPage(1)
setErrorMessage('')
setLastSearchedDescription('')
}, [])

const handleAiSearch = useCallback(async (): Promise<void> => {
Expand All @@ -196,11 +202,15 @@ export const TalentSearchPage: FC = () => {
return
}

const generation = searchGenerationRef.current

setErrorMessage('')
setIsExtractingSkills(true)

try {
const extractedSkillsResult = await extractSkillsFromText(normalizedDescription)
if (searchGenerationRef.current !== generation) return

const extractedSkills = Array.isArray(extractedSkillsResult?.matches)
? extractedSkillsResult.matches
: []
Expand Down Expand Up @@ -235,14 +245,20 @@ export const TalentSearchPage: FC = () => {

setHasSearched(true)
skipNextAutoSearchRef.current = true
await runMemberSearch(extractedOptions, { page: 1 })
const searchSucceeded = await runMemberSearch(extractedOptions, { generation, page: 1 })
if (searchGenerationRef.current !== generation) return

if (searchSucceeded) {
setLastSearchedDescription(normalizedDescription)
}
} catch {
// Prevent stale auto-search when extraction fails and loading flips to false.
skipNextAutoSearchRef.current = true
if (searchGenerationRef.current !== generation) return
setErrorMessage('Failed to extract skills. Please try again.')
setHasSearched(true)
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
} finally {
setIsExtractingSkills(false)

}
}, [isExtractingSkills, jobDescription, runMemberSearch])

Expand All @@ -256,7 +272,7 @@ export const TalentSearchPage: FC = () => {
return
}

runMemberSearch(selectedSkills)
runMemberSearch(selectedSkills, { generation: searchGenerationRef.current })
}, [
hasSearched,
isExtractingSkills,
Expand All @@ -276,7 +292,12 @@ export const TalentSearchPage: FC = () => {
page: currentPage + 1,
})
}, [currentPage, hasMoreResults, isLoadingMore, isSearchingMembers, runMemberSearch, selectedSkills])

const isSearchButtonDisabled = useMemo(
() => isExtractingSkills
|| !jobDescription.trim()
|| jobDescription.trim() === lastSearchedDescription,
[isExtractingSkills, jobDescription, lastSearchedDescription],
)
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
return (
<PageWrapper
pageTitle='Talent Search'
Expand Down Expand Up @@ -311,15 +332,17 @@ export const TalentSearchPage: FC = () => {
secondary
disabled={isExtractingSkills}
onClick={() => {
searchGenerationRef.current += 1
setJobDescription('')
setErrorMessage('')
setLastSearchedDescription('')
}}
>
Clear
</Button>
<Button
primary
disabled={isExtractingSkills || !jobDescription.trim()}
disabled={isSearchButtonDisabled}
onClick={handleAiSearch}
>
{isExtractingSkills ? 'Analyzing...' : 'Search'}
Expand All @@ -345,6 +368,9 @@ export const TalentSearchPage: FC = () => {
const value = (event.target.value || []) as InputMultiselectOption[]
setSelectedSkills(value)
setHasSearched(value.length > 0)
if (value.length === 0) {
setLastSearchedDescription('')
}
}}
/>
</div>
Expand Down Expand Up @@ -434,58 +460,40 @@ export const TalentSearchPage: FC = () => {

{hasSearched && (
<div className={styles.resultsContent}>
<div className={styles.resultsTop}>
<p className={styles.foundText}>
We have found&nbsp;
<span className={styles.foundTextCount}>
{`${foundMembersCount} members`}
</span>
&nbsp;that match your search.
</p>
<div className={styles.sortControl}>
<span className={styles.sortLabel}>Sort by</span>
<InputSelect
classNameWrapper={styles.matchingIndexSelect}
name='sortBy'
options={[
{ label: 'Matching Index', value: 'matching-index' },
]}
value='matching-index'
onChange={() => undefined}
/>
</div>
</div>
{isSearchingMembers && (
{isLoading ? (
<div className={styles.emptyState}>
<h4>Searching talent...</h4>
</div>
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
)}
{!isSearchingMembers && filteredResults.length === 0 && (
<div className={styles.emptyState}>
<h4>No matching talent found</h4>
<p>Try changing filters or using a different job description.</p>
</div>
)}
{!isSearchingMembers && filteredResults.length > 0 && (
) : (
<>
<div className={styles.cardsGrid}>
{filteredResults.map(talent => (
<TalentResultCard
key={talent.id}
talent={talent}
/>
))}
</div>
{hasMoreResults && (
<div className={styles.loadMoreWrap}>
<Button
secondary
disabled={isLoadingMore}
onClick={handleLoadMore}
>
{isLoadingMore ? 'Loading...' : 'Load More Members'}
</Button>

{filteredResults.length === 0 ? (
<div className={styles.emptyState}>
<h4>No matching talent found</h4>
<p>Try changing filters or using a different job description.</p>
</div>
) : (
<>
<div className={styles.cardsGrid}>
{filteredResults.map(talent => (
<TalentResultCard
key={talent.id}
talent={talent}
/>
))}
</div>
{hasMoreResults && (
<div className={styles.loadMoreWrap}>
<Button
secondary
disabled={isLoadingMore}
onClick={handleLoadMore}
>
{isLoadingMore ? 'Loading...' : 'Load More Members'}
</Button>
</div>
)}
</>
)}
</>
)}
Expand Down
Loading