From bee8e120b49bf4083de3d82a30f5c92cec4dc554 Mon Sep 17 00:00:00 2001
From: himaniraghav3
Date: Thu, 30 Apr 2026 17:07:14 +0530
Subject: [PATCH] PM-4992 Update talent search view
---
.../TalentSearchPage.module.scss | 34 +--
.../TalentSearchPage/TalentSearchPage.tsx | 231 +++++-------------
2 files changed, 81 insertions(+), 184 deletions(-)
diff --git a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss b/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss
index 5a6dd85e6..22116c969 100644
--- a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss
+++ b/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss
@@ -3,17 +3,20 @@
.container {
display: flex;
flex-direction: column;
- gap: $sp-4;
}
:global([class*='ContentLayout-module_content-outer']) {
- margin: 24px auto 0 !important;
+ margin: 0 auto 0 !important;
}
:global([class*='ContentLayout-module_content__']) {
padding-bottom: 0 !important;
}
+:global([class*='BreadCrumb-module_breadcrumb']) {
+ display: none;
+}
+
.pageArea {
position: relative;
@include substractPagePaddings;
@@ -32,9 +35,9 @@
.pageBody {
display: grid;
grid-template-columns: 443px 1fr;
- gap: 40px;
+ gap: 24px;
margin-top: -232px;
- padding: $sp-4 $sp-12 $sp-14 $sp-12;
+ padding: $sp-2 $sp-8 $sp-10 $sp-8;
position: relative;
z-index: 1;
font-family: $font-roboto;
@@ -62,17 +65,17 @@
background: $tc-white;
border: 0;
border-radius: 16px;
- padding: 32px;
+ padding: 20px;
display: flex;
flex-direction: column;
- gap: $sp-3;
+ gap: $sp-2;
}
.sidebar .panel + .panel {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top: 0;
- padding-top: 20px;
+ padding-top: 14px;
}
.panelTitle {
@@ -107,9 +110,9 @@
}
.jobDescriptionField {
- margin-top: 0.5rem;
+ margin-top: 0;
:global(textarea) {
- min-height: 442px;
+ min-height: 170px;
resize: vertical;
color: $black-100;
font-size: 14px;
@@ -119,7 +122,7 @@
.aiActions {
display: flex;
- gap: $sp-2;
+ gap: $sp-1;
:global(button) {
font-size: 14px;
@@ -134,6 +137,7 @@
}
.filterBlock {
+ margin-bottom: 2px;
:global([class*='__value-container']) {
min-height: 18px;
}
@@ -175,8 +179,8 @@
align-items: center;
gap: $sp-2;
color: $black-100;
- font-size: 16px;
- line-height: 24px;
+ font-size: 15px;
+ line-height: 21px;
font-weight: 400;
position: relative;
cursor: pointer;
@@ -242,8 +246,10 @@
}
.clearFiltersWrap {
- margin-top: 10px;
+ margin-top: 6px;
align-self: flex-start;
+ display: flex;
+ gap: $sp-2;
:global(button) {
font-size: 14px;
@@ -280,7 +286,7 @@
align-items: center;
justify-content: space-between;
gap: $sp-3;
- padding-top: 8px;
+ padding-top: 0;
@include ltemd {
flex-direction: column;
diff --git a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx b/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx
index d8f052b7b..b3b4fee54 100644
--- a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx
+++ b/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx
@@ -1,6 +1,6 @@
/* eslint-disable complexity */
/* eslint-disable react/jsx-no-bind */
-import { ChangeEvent, FC, FocusEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { ChangeEvent, FC, FocusEvent, useCallback, useMemo, useRef, useState } from 'react'
import classNames from 'classnames'
import { CountryLookup, useCountryLookup } from '~/libs/core'
@@ -9,8 +9,6 @@ import {
IconOutline,
InputMultiselect,
InputMultiselectOption,
- InputSelect,
- InputSelectOption,
InputTextarea,
Tooltip,
} from '~/libs/ui'
@@ -24,37 +22,30 @@ import {
searchMembers,
SearchTalent,
} from '../../../lib'
-import personSearchImage from '../../../lib/assets/person-search.png'
import styles from './TalentSearchPage.module.scss'
-type TalentSearchSortOption = 'alphabetical' | 'matching-index'
-
export const TalentSearchPage: FC = () => {
- const skipNextAutoSearchRef = useRef(false)
const searchGenerationRef = useRef(0)
- const toggleDebounceTimerRef = useRef | undefined>(undefined)
- const pendingToggleAutoSearchRef = useRef(false)
- const hasMountedRef = useRef(false)
const [lastSearchedDescription, setLastSearchedDescription] = useState('')
const countryLookup: CountryLookup[] | undefined = useCountryLookup()
const [jobDescription, setJobDescription] = useState('')
const [isExtractingSkills, setIsExtractingSkills] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
- const [hasSearched, setHasSearched] = useState(true)
+ const [hasSearched, setHasSearched] = useState(false)
const [skillOptionsLoading, setSkillOptionsLoading] = useState(false)
const [selectedSkills, setSelectedSkills] = useState([])
- const [sortBy, setSortBy] = useState('alphabetical')
const [selectedCountries, setSelectedCountries] = useState([])
- const [onlyProfileComplete, setOnlyProfileComplete] = useState(true)
+ const [onlyProfileComplete, setOnlyProfileComplete] = useState(false)
const [onlyOpenToWork, setOnlyOpenToWork] = useState(true)
const [onlyActive, setOnlyActive] = useState(true)
- const [isSearchingMembers, setIsSearchingMembers] = useState(true)
+ const [isSearchingMembers, setIsSearchingMembers] = useState(false)
const [isLoadingMore, setIsLoadingMore] = useState(false)
const [results, setResults] = useState([])
const [totalResults, setTotalResults] = useState(0)
const [currentPage, setCurrentPage] = useState(1)
+ const [lastAppliedSearchSignature, setLastAppliedSearchSignature] = useState('')
const countryNameByCode = useMemo((): Map => new Map(
(countryLookup || [])
.filter(country => country.countryCode && country.country)
@@ -81,17 +72,22 @@ export const TalentSearchPage: FC = () => {
)
const hasSkillSearch = selectedSkills.length > 0
- const hasActiveFilters = hasSkillSearch
- || selectedCountries.length > 0
- || onlyOpenToWork
- || onlyActive
- const shouldShowIntroState = !hasSearched && !hasActiveFilters
- const activeSort: TalentSearchSortOption = hasSkillSearch ? 'matching-index' : sortBy
- const sortOptions = useMemo(
- (): InputSelectOption[] => (hasSkillSearch
- ? [{ label: 'Matching Index', value: 'matching-index' }]
- : [{ label: 'Alphabetical', value: 'alphabetical' }]),
- [hasSkillSearch],
+ const shouldShowIntroState = !hasSearched
+ const currentSearchSignature = useMemo(
+ (): string => JSON.stringify({
+ countries: selectedCountryCodesList
+ .slice()
+ .sort(),
+ openToWork: onlyOpenToWork,
+ profileComplete: onlyProfileComplete,
+ recentlyActive: onlyActive,
+ skills: selectedSkills
+ .map(skill => String(skill.value || '')
+ .trim())
+ .filter(Boolean)
+ .sort(),
+ }),
+ [onlyActive, onlyOpenToWork, onlyProfileComplete, selectedCountryCodesList, selectedSkills],
)
// Order comes from reports-api (sortBy/sortOrder on each request) so pagination stays globally consistent.
@@ -247,26 +243,14 @@ export const TalentSearchPage: FC = () => {
}, [onlyActive, onlyOpenToWork, onlyProfileComplete, selectedCountryCodesList])
const clearAllFilters = useCallback((): void => {
- searchGenerationRef.current += 1
setSelectedCountries([])
- setOnlyProfileComplete(true)
+ setOnlyProfileComplete(false)
setOnlyOpenToWork(true)
setOnlyActive(true)
- setSortBy('alphabetical')
setSelectedSkills([])
- setHasSearched(true)
setErrorMessage('')
- skipNextAutoSearchRef.current = true
setLastSearchedDescription('')
- runMemberSearch([], {
- countries: [],
- generation: searchGenerationRef.current,
- openToWork: true,
- page: 1,
- profileComplete: true,
- recentlyActive: true,
- })
- }, [runMemberSearch])
+ }, [])
const handleAiSearch = useCallback(async (): Promise => {
const normalizedDescription = jobDescription.trim()
@@ -308,121 +292,47 @@ export const TalentSearchPage: FC = () => {
setSelectedSkills(extractedOptions)
if (extractedOptions.length === 0) {
- setResults([])
- setTotalResults(0)
- setHasSearched(true)
setErrorMessage('No skills were extracted from the job description.')
- skipNextAutoSearchRef.current = true
return
}
- setHasSearched(true)
- skipNextAutoSearchRef.current = true
- const searchSucceeded = await runMemberSearch(extractedOptions, { generation, page: 1 })
- if (searchGenerationRef.current !== generation) return
-
- if (searchSucceeded) {
- setLastSearchedDescription(normalizedDescription)
- }
+ setLastSearchedDescription(normalizedDescription)
} catch {
- skipNextAutoSearchRef.current = true
if (searchGenerationRef.current !== generation) return
setErrorMessage('Failed to extract skills. Please try again.')
- setHasSearched(true)
} finally {
setIsExtractingSkills(false)
}
- }, [isExtractingSkills, jobDescription, runMemberSearch])
-
- useEffect(() => {
- if ((shouldShowIntroState) || isExtractingSkills) {
- if (toggleDebounceTimerRef.current) {
- clearTimeout(toggleDebounceTimerRef.current)
- toggleDebounceTimerRef.current = undefined
- }
-
- pendingToggleAutoSearchRef.current = false
-
- return
- }
-
- if (!hasMountedRef.current) {
- hasMountedRef.current = true
-
- if (skipNextAutoSearchRef.current) {
- skipNextAutoSearchRef.current = false
- pendingToggleAutoSearchRef.current = false
- if (toggleDebounceTimerRef.current) {
- clearTimeout(toggleDebounceTimerRef.current)
- toggleDebounceTimerRef.current = undefined
- }
-
- return
- }
-
- runMemberSearch(selectedSkills, { generation: searchGenerationRef.current, page: 1 })
- return
- }
-
- if (skipNextAutoSearchRef.current) {
- skipNextAutoSearchRef.current = false
- if (toggleDebounceTimerRef.current) {
- clearTimeout(toggleDebounceTimerRef.current)
- toggleDebounceTimerRef.current = undefined
- }
-
- pendingToggleAutoSearchRef.current = false
+ }, [isExtractingSkills, jobDescription])
+ const handleSearch = useCallback(async (): Promise => {
+ if (isSearchingMembers || selectedSkills.length === 0) {
return
}
- const runSearch = (): void => {
- runMemberSearch(selectedSkills, {
- generation: searchGenerationRef.current,
- page: 1,
- })
- }
-
- const shouldDebounce = pendingToggleAutoSearchRef.current || Boolean(toggleDebounceTimerRef.current)
-
- if (shouldDebounce) {
- pendingToggleAutoSearchRef.current = false
- if (toggleDebounceTimerRef.current) {
- clearTimeout(toggleDebounceTimerRef.current)
- toggleDebounceTimerRef.current = undefined
- }
-
- toggleDebounceTimerRef.current = setTimeout(() => {
- toggleDebounceTimerRef.current = undefined
- runSearch()
- }, 800)
- return
+ setHasSearched(true)
+ const searchSucceeded = await runMemberSearch(selectedSkills, {
+ countries: selectedCountryCodesList,
+ openToWork: onlyOpenToWork,
+ page: 1,
+ profileComplete: onlyProfileComplete,
+ recentlyActive: onlyActive,
+ })
+ if (searchSucceeded) {
+ setLastAppliedSearchSignature(currentSearchSignature)
}
-
- // No debounce requested and no pending timer: execute immediately.
- pendingToggleAutoSearchRef.current = false
- runSearch()
}, [
- hasSearched,
- hasActiveFilters,
- isExtractingSkills,
+ currentSearchSignature,
+ isSearchingMembers,
onlyActive,
onlyOpenToWork,
+ onlyProfileComplete,
runMemberSearch,
- selectedCountries,
+ selectedCountryCodesList,
selectedSkills,
- shouldShowIntroState,
])
- // Cleanup any pending debounced request on unmount.
- useEffect(() => (): void => {
- if (toggleDebounceTimerRef.current) {
- clearTimeout(toggleDebounceTimerRef.current)
- toggleDebounceTimerRef.current = undefined
- }
- }, [])
-
const handleLoadMore = useCallback((): void => {
if (isLoadingMore || isSearchingMembers || !hasMoreResults) {
return
@@ -433,7 +343,7 @@ export const TalentSearchPage: FC = () => {
page: currentPage + 1,
})
}, [currentPage, hasMoreResults, isLoadingMore, isSearchingMembers, runMemberSearch, selectedSkills])
- const isSearchButtonDisabled = useMemo(
+ const isAiExtractButtonDisabled = useMemo(
() => isExtractingSkills
|| !jobDescription.trim()
|| jobDescription.trim() === lastSearchedDescription,
@@ -441,7 +351,7 @@ export const TalentSearchPage: FC = () => {
)
return (
@@ -450,20 +360,12 @@ export const TalentSearchPage: FC = () => {
{errorMessage && (
@@ -495,7 +397,6 @@ export const TalentSearchPage: FC = () => {
- Filter
{
onChange={(event: ChangeEvent) => {
const value = (event.target.value || []) as InputMultiselectOption[]
setSelectedSkills(value)
- setHasSearched(true)
if (value.length === 0) {
setLastSearchedDescription('')
}
@@ -535,7 +435,6 @@ export const TalentSearchPage: FC = () => {
checked={onlyOpenToWork}
className={styles.checkboxInput}
onChange={(event: ChangeEvent) => {
- pendingToggleAutoSearchRef.current = true
setOnlyOpenToWork(event.target.checked)
}}
/>
@@ -548,7 +447,6 @@ export const TalentSearchPage: FC = () => {
checked={onlyActive}
className={styles.checkboxInput}
onChange={(event: ChangeEvent) => {
- pendingToggleAutoSearchRef.current = true
setOnlyActive(event.target.checked)
}}
/>
@@ -578,7 +476,6 @@ export const TalentSearchPage: FC = () => {
checked={onlyProfileComplete}
className={styles.checkboxInput}
onChange={(event: ChangeEvent) => {
- pendingToggleAutoSearchRef.current = true
setOnlyProfileComplete(event.target.checked)
}}
/>
@@ -589,6 +486,17 @@ export const TalentSearchPage: FC = () => {
+
@@ -601,12 +509,10 @@ export const TalentSearchPage: FC = () => {
>
{shouldShowIntroState && (
-

-
Find the right talent
+
+ Paste a job description to AI-extract skills, or enter skills manually
+ to find talents
+
)}
@@ -621,21 +527,6 @@ export const TalentSearchPage: FC = () => {
that match your search.
-
- Sort by
- ) => {
- const nextSort = event.target.value || 'alphabetical'
- setSortBy(
- nextSort as TalentSearchSortOption,
- )
- }}
- />
-
)}
{isSearchingMembers && (