From 023c8c01e920643e40938bb42af4afd3fdcc9348 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Thu, 11 Jun 2026 17:07:50 +0530 Subject: [PATCH 1/5] Removed talent-search app from platform-ui --- craco.config.js | 1 - src/apps/customer-portal/src/config/routes.config.ts | 2 -- .../customer-portal/src/customer-portal.routes.tsx | 11 ----------- .../getting-started/GettingStartedGuide.md | 5 ----- src/apps/onboarding/src/models/MemberInfo.ts | 5 ++++- .../onboarding/src/pages/account-details/index.tsx | 4 ++-- src/apps/onboarding/src/pages/skills/index.tsx | 4 ++-- src/apps/platform/src/platform.routes.tsx | 2 -- .../profiles/src/member-profile/MemberProfilePage.tsx | 4 ++-- .../src/profiles-landing-page/ProfilesLandingPage.tsx | 7 ++----- src/config/constants.ts | 2 -- tsconfig.paths.json | 3 --- 12 files changed, 12 insertions(+), 38 deletions(-) diff --git a/craco.config.js b/craco.config.js index bdc7fa39f..1ff13b064 100644 --- a/craco.config.js +++ b/craco.config.js @@ -41,7 +41,6 @@ module.exports = { '@learn': resolve('src/apps/learn/src'), '@devCenter': resolve('src/apps/dev-center/src'), '@gamificationAdmin': resolve('src/apps/gamification-admin/src'), - '@talentSearch': resolve('src/apps/talent-search/src'), '@profiles': resolve('src/apps/profiles/src'), '@wallet': resolve('src/apps/wallet/src'), '@walletAdmin': resolve('src/apps/wallet-admin/src'), diff --git a/src/apps/customer-portal/src/config/routes.config.ts b/src/apps/customer-portal/src/config/routes.config.ts index 316357915..a33c25ad3 100644 --- a/src/apps/customer-portal/src/config/routes.config.ts +++ b/src/apps/customer-portal/src/config/routes.config.ts @@ -7,5 +7,3 @@ export const rootRoute: string = EnvironmentConfig.SUBDOMAIN === AppSubdomain.customer ? '' : `/${AppSubdomain.customer}` - -export const talentSearchRouteId = 'talent-search' diff --git a/src/apps/customer-portal/src/customer-portal.routes.tsx b/src/apps/customer-portal/src/customer-portal.routes.tsx index 2ee282745..f4f44f577 100644 --- a/src/apps/customer-portal/src/customer-portal.routes.tsx +++ b/src/apps/customer-portal/src/customer-portal.routes.tsx @@ -6,15 +6,12 @@ import { lazyLoad, LazyLoadedComponent, PlatformRoute, - Rewrite, UserRole, } from '~/libs/core' import { rootRoute, - talentSearchRouteId, } from './config/routes.config' -import { customerPortalTalentSearchRoutes } from './pages/talent-search/talent-search.routes' const CustomerPortalApp: LazyLoadedComponent = lazyLoad(() => import('./CustomerPortalApp')) @@ -24,14 +21,6 @@ export const customerPortalRoutes: ReadonlyArray = [ // Customer portal App Root { authRequired: true, - children: [ - { - authRequired: true, - element: , - route: '', - }, - ...customerPortalTalentSearchRoutes, - ], domain: AppSubdomain.customer, element: , id: toolTitle, diff --git a/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.md b/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.md index 291640e86..bf9e09688 100644 --- a/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.md +++ b/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.md @@ -159,11 +159,6 @@ Application that allows users to manage their own profile data, and allows visit Located `src/apps/profiles`. -#### Talent Search App -This is an internal app for finding members based on skills and other search facets. - -Located `src/apps/talent-search`. - #### Skills Manager Admin app that allows one to manage the standardized skills. diff --git a/src/apps/onboarding/src/models/MemberInfo.ts b/src/apps/onboarding/src/models/MemberInfo.ts index 23de4ea8a..236abe13b 100644 --- a/src/apps/onboarding/src/models/MemberInfo.ts +++ b/src/apps/onboarding/src/models/MemberInfo.ts @@ -1,8 +1,11 @@ -import { MemberMaxRating } from '~/apps/talent-search/src/lib/models' import { MemberStats, UserSkill } from '~/libs/core' import MemberAddress from './MemberAddress' +export type MemberMaxRating = { + rating?: number +} + export default interface MemberInfo { userId: number handle: string diff --git a/src/apps/onboarding/src/pages/account-details/index.tsx b/src/apps/onboarding/src/pages/account-details/index.tsx index 36e17f2bb..438c6c734 100644 --- a/src/apps/onboarding/src/pages/account-details/index.tsx +++ b/src/apps/onboarding/src/pages/account-details/index.tsx @@ -7,7 +7,7 @@ import classNames from 'classnames' import { Button, IconOutline, InputSelect, PageDivider } from '~/libs/ui' import { getCountryLookup } from '~/libs/core/lib/profile/profile-functions/profile-store/profile-xhr.store' import { EnvironmentConfig } from '~/config' -import { Member } from '~/apps/talent-search/src/lib/models' +import { UserProfile } from '~/libs/core' import { ProgressBar } from '../../components/progress-bar' import { validatePhonenumber } from '../../utils/validation' @@ -28,7 +28,7 @@ const blankConnectInfo: ConnectInfo = emptyConnectInfo() const PageAccountDetailsContent: FC<{ reduxAddress: MemberAddress | undefined reduxConnectInfo: ConnectInfo | undefined - reduxMemberInfo: Member | undefined + reduxMemberInfo: UserProfile | undefined updateMemberConnectInfos: (infos: ConnectInfo[]) => void createMemberConnectInfos: (infos: ConnectInfo[]) => void updateMemberHomeAddresss: (infos: MemberAddress[]) => void diff --git a/src/apps/onboarding/src/pages/skills/index.tsx b/src/apps/onboarding/src/pages/skills/index.tsx index 9ac5a1606..81c127ddd 100644 --- a/src/apps/onboarding/src/pages/skills/index.tsx +++ b/src/apps/onboarding/src/pages/skills/index.tsx @@ -4,7 +4,7 @@ import { connect } from 'react-redux' import classNames from 'classnames' import { Button, PageDivider } from '~/libs/ui' -import { Member } from '~/apps/talent-search/src/lib/models' +import { UserProfile } from '~/libs/core' import { MemberSkillEditor, useMemberSkillEditor } from '~/libs/shared' import { ProgressBar } from '../../components/progress-bar' @@ -12,7 +12,7 @@ import { ProgressBar } from '../../components/progress-bar' import styles from './styles.module.scss' export const PageSkillsContent: FC<{ - reduxMemberInfo: Member | undefined + reduxMemberInfo: UserProfile | undefined }> = props => { const navigate: any = useNavigate() const [loading, setLoading] = useState(false) diff --git a/src/apps/platform/src/platform.routes.tsx b/src/apps/platform/src/platform.routes.tsx index 0bc933be7..14b912d9d 100644 --- a/src/apps/platform/src/platform.routes.tsx +++ b/src/apps/platform/src/platform.routes.tsx @@ -3,7 +3,6 @@ import { lazyLoad, LazyLoadedComponent, PlatformRoute } from '~/libs/core' import { learnRoutes } from '~/apps/learn' import { devCenterRoutes } from '~/apps/dev-center' import { profilesRoutes } from '~/apps/profiles' -import { talentSearchRoutes } from '~/apps/talent-search' import { accountsRoutes } from '~/apps/accounts' import { onboardingRoutes } from '~/apps/onboarding' import { walletRoutes } from '~/apps/wallet' @@ -38,7 +37,6 @@ export const platformRoutes: Array = [ ...devCenterRoutes, ...copilotsRoutes, ...learnRoutes, - ...talentSearchRoutes, ...profilesRoutes, ...walletRoutes, ...walletAdminRoutes, diff --git a/src/apps/profiles/src/member-profile/MemberProfilePage.tsx b/src/apps/profiles/src/member-profile/MemberProfilePage.tsx index b8054c8ac..d5a85cf72 100644 --- a/src/apps/profiles/src/member-profile/MemberProfilePage.tsx +++ b/src/apps/profiles/src/member-profile/MemberProfilePage.tsx @@ -3,9 +3,9 @@ import { Params, useNavigate, useParams } from 'react-router-dom' import { AxiosError } from 'axios' import { profileContext, ProfileContextData, profileGetPublicAsync, UserProfile } from '~/libs/core' -import { TALENT_SEARCH_PATHS } from '~/apps/talent-search' import { LoadingSpinner } from '~/libs/ui' +import { rootRoute } from '../profiles.routes' import { notifyUniNavi } from '../lib' import { ProfilePageLayout } from './page-layout' @@ -38,7 +38,7 @@ const MemberProfilePage: FC<{}> = () => { }) .catch((e: AxiosError) => { if (e.code === AxiosError.ERR_BAD_REQUEST && e.response?.status === 404) { - window.location.href = `${TALENT_SEARCH_PATHS.absoluteUrl}?memberNotFound` + navigate(rootRoute) } }) } diff --git a/src/apps/profiles/src/profiles-landing-page/ProfilesLandingPage.tsx b/src/apps/profiles/src/profiles-landing-page/ProfilesLandingPage.tsx index 08cac5dba..0039ee3ec 100644 --- a/src/apps/profiles/src/profiles-landing-page/ProfilesLandingPage.tsx +++ b/src/apps/profiles/src/profiles-landing-page/ProfilesLandingPage.tsx @@ -2,23 +2,20 @@ import { FC, useContext, useEffect } from 'react' import { NavigateFunction, useNavigate } from 'react-router-dom' import { profileContext, ProfileContextData } from '~/libs/core' -import { TALENT_SEARCH_PATHS } from '~/apps/talent-search' import { rootRoute } from '../profiles.routes' const ProfilesLandingPage: FC = () => { const navigate: NavigateFunction = useNavigate() - const { profile: authProfile, initialized }: ProfileContextData = useContext(profileContext) + const { profile: authProfile }: ProfileContextData = useContext(profileContext) // redirect to profile page if logged in useEffect(() => { if (authProfile) { navigate(`${rootRoute}/${authProfile.handle}`) - } else if (initialized) { - window.location.href = `${TALENT_SEARCH_PATHS.absoluteUrl}` } - }, [authProfile, navigate, initialized]) + }, [authProfile, navigate]) return ( // TODO: no profile specified - redirect to talent search or dedicated page diff --git a/src/config/constants.ts b/src/config/constants.ts index 4287114b3..e81b79395 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -6,7 +6,6 @@ export enum AppSubdomain { tcAcademy = 'academy', onboarding = 'onboarding', work = 'work', - talentSearch = 'talent-search', wallet = 'wallet', walletAdmin = 'wallet-admin', copilots = 'copilots', @@ -27,7 +26,6 @@ export enum ToolTitle { selfService = 'Self Service Challenges', onboarding = ' ', work = 'Work', - talentSearch = 'Expert Talent', wallet = 'Wallet', walletAdmin = 'Wallet Admin', copilots = 'Copilots', diff --git a/tsconfig.paths.json b/tsconfig.paths.json index 2e0fc3ec9..54e6a260e 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -21,9 +21,6 @@ "@platform/*": [ "./src/apps/platform/src/*" ], - "@talentSearch/*": [ - "./src/apps/talent-search/src/*" - ], "@profiles/*": [ "./src/apps/profiles/src/*" ], From 3a51b869052f4fc8662083235260bb84d718d580 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Thu, 11 Jun 2026 17:22:11 +0530 Subject: [PATCH 2/5] Removed talent-search app from platform-ui --- .../src/lib/components/NavTabs/NavTabs.tsx | 28 ++----------------- .../components/NavTabs/config/tabs-config.ts | 24 ++-------------- 2 files changed, 6 insertions(+), 46 deletions(-) diff --git a/src/apps/customer-portal/src/lib/components/NavTabs/NavTabs.tsx b/src/apps/customer-portal/src/lib/components/NavTabs/NavTabs.tsx index 5e87e8ea8..278c22d81 100644 --- a/src/apps/customer-portal/src/lib/components/NavTabs/NavTabs.tsx +++ b/src/apps/customer-portal/src/lib/components/NavTabs/NavTabs.tsx @@ -4,23 +4,16 @@ import { MouseEvent, SetStateAction, useCallback, - useContext, useEffect, - useMemo, useRef, useState, } from 'react' import { NavigateFunction, useLocation, useNavigate } from 'react-router-dom' -import { isEmpty } from 'lodash' import classNames from 'classnames' import { useClickOutside } from '~/libs/shared/lib/hooks' import { IconOutline } from '~/libs/ui' -import { CustomerPortalAppContext } from '../../contexts' -import { CustomerPortalAppContextModel } from '../../models' -import { PRIVILEGED_ROLES } from '../../../config/index.config' - import { getTabIdFromPathName, getTabsConfig } from './config' import styles from './NavTabs.module.scss' @@ -30,23 +23,8 @@ const NavTabs: FC = () => { const triggerRef = useRef(null) const { pathname }: { pathname: string } = useLocation() - const { loginUserInfo }: CustomerPortalAppContextModel = useContext(CustomerPortalAppContext) - const isAnonymous = isEmpty(loginUserInfo) - const userRoles = useMemo(() => loginUserInfo?.roles || [], [loginUserInfo?.roles]) - const isUnprivilegedUser = useMemo(() => { - if (!loginUserInfo) return true - - return !userRoles.some(role => PRIVILEGED_ROLES.includes(role)) - }, [loginUserInfo, userRoles]) - const tabs = useMemo( - () => getTabsConfig(userRoles, isAnonymous, isUnprivilegedUser), - [userRoles, isAnonymous, isUnprivilegedUser], - ) - - const activeTabPathName: string = useMemo( - () => getTabIdFromPathName(pathname, userRoles, isAnonymous, isUnprivilegedUser), - [pathname, userRoles, isAnonymous, isUnprivilegedUser], - ) + const tabs = getTabsConfig() + const activeTabPathName: string = getTabIdFromPathName(pathname) const [activeTab, setActiveTab]: [ string, Dispatch> @@ -54,7 +32,7 @@ const NavTabs: FC = () => { useEffect(() => { setActiveTab(activeTabPathName) - }, [activeTabPathName]) + }, [pathname]) const triggerTab = useCallback(() => { setIsOpen(!isOpen) diff --git a/src/apps/customer-portal/src/lib/components/NavTabs/config/tabs-config.ts b/src/apps/customer-portal/src/lib/components/NavTabs/config/tabs-config.ts index c76f23ed4..810e4db43 100644 --- a/src/apps/customer-portal/src/lib/components/NavTabs/config/tabs-config.ts +++ b/src/apps/customer-portal/src/lib/components/NavTabs/config/tabs-config.ts @@ -1,33 +1,15 @@ import _ from 'lodash' import { TabsNavItem } from '~/libs/ui' -import { - talentSearchRouteId, -} from '~/apps/customer-portal/src/config/routes.config' -export function getTabsConfig(userRoles: string[], isAnonymous: boolean, isUnprivilegedUser: boolean): TabsNavItem[] { - - const tabs: TabsNavItem[] = [ - ...(!isUnprivilegedUser ? [{ - id: talentSearchRouteId, - title: 'Talent Search', - }] : []), - ] - - return tabs +export function getTabsConfig(): TabsNavItem[] { + return [] } export function getTabIdFromPathName( pathname: string, - userRoles: string[], - isAnonymous: boolean, - isUnprivilegedUser: boolean, ): string { - const matchItem = _.find(getTabsConfig( - userRoles, - isAnonymous, - isUnprivilegedUser, - ), item => pathname.includes(`/${item.id}`)) + const matchItem = _.find(getTabsConfig(), item => pathname.includes(`/${item.id}`)) if (matchItem) { return matchItem.id From ab5906f1c7b6e36b9639c2512248af00d3c3ef34 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Thu, 11 Jun 2026 17:34:43 +0530 Subject: [PATCH 3/5] Clean up talent-search unused code and linting errors --- .github/workflows/code_reviewer.yml | 22 + .../lib/services/profileCompletion.service.ts | 142 +++++ .../ProfileCompletionPage.module.scss | 218 +++++++ .../ProfileCompletionPage.tsx | 409 +++++++++++++ .../ProfileCompletionPage/index.ts | 1 + .../src/pages/profile-completion/index.ts | 1 + .../profile-completion.routes.tsx | 26 + .../TalentSearchPage.module.scss | 409 ------------- .../TalentSearchPage/TalentSearchPage.tsx | 578 ------------------ .../talent-search/TalentSearchPage/index.ts | 1 - .../TalentResultCard.module.scss | 298 --------- .../TalentResultCard/TalentResultCard.tsx | 205 ------- .../components/TalentResultCard/index.ts | 1 - .../talent-search/talent-search.routes.tsx | 26 - src/apps/talent-search/README.md | 6 - src/apps/talent-search/index.ts | 1 - .../talent-search/src/TalentSearchApp.tsx | 23 - .../src/assets/background-m.webp | Bin 19330 -> 0 bytes .../talent-search/src/assets/background.jpg | Bin 28106 -> 0 bytes .../talent-search/src/assets/background.webp | Bin 19104 -> 0 bytes .../src/assets/verified-icon-white.svg | 3 - .../popular-skills/PopularSkills.module.scss | 33 - .../popular-skills/PopularSkills.tsx | 133 ---- .../src/components/popular-skills/index.ts | 1 - .../profile-match/ProfileMatch.module.scss | 52 -- .../components/profile-match/ProfileMatch.tsx | 28 - .../src/components/profile-match/index.ts | 1 - .../ProfileSkillsMatch.module.scss | 98 --- .../ProfileSkillsMatch.tsx | 79 --- .../components/profile-skills-match/index.ts | 1 - .../search-input/SearchInput.module.scss | 44 -- .../components/search-input/SearchInput.tsx | 95 --- .../src/components/search-input/index.ts | 1 - .../talent-card/TalentCard.module.scss | 171 ------ .../src/components/talent-card/TalentCard.tsx | 144 ----- .../src/components/talent-card/index.ts | 1 - .../talent-search/src/config/constants.ts | 2 - src/apps/talent-search/src/config/index.ts | 1 - src/apps/talent-search/src/index.ts | 1 - .../talent-search/src/lib/models/Member.ts | 30 - .../src/lib/models/MemberAddress.ts | 7 - .../src/lib/models/MemberDisplayName.ts | 5 - .../src/lib/models/MemberMaxRating.ts | 6 - .../src/lib/models/MemberStats.ts | 7 - .../talent-search/src/lib/models/index.ts | 3 - .../talent-search/src/lib/services/index.ts | 1 - .../services/use-fetch-talent-matches.spec.ts | 50 -- .../lib/services/use-fetch-talent-matches.ts | 113 ---- src/apps/talent-search/src/lib/utils/index.ts | 1 - .../src/lib/utils/search-query.tsx | 38 -- .../src/lib/utils/skills.utils.tsx | 13 - .../routes/search-page/SearchPage.module.scss | 89 --- .../src/routes/search-page/SearchPage.tsx | 91 --- .../src/routes/search-page/index.ts | 1 - .../SearchResultsPage.module.scss | 127 ---- .../search-results-page/SearchResultsPage.tsx | 158 ----- .../src/routes/search-results-page/index.ts | 1 - .../search-results-page/letsTalkUrl.spec.ts | 13 - .../routes/search-results-page/letsTalkUrl.ts | 1 - .../routes/talent-page/TalentPage.module.scss | 0 .../src/routes/talent-page/TalentPage.tsx | 35 -- .../src/routes/talent-page/index.ts | 1 - .../talent-search/src/styles/main.vendor.scss | 5 - .../src/talent-search.routes.tsx | 87 --- 64 files changed, 819 insertions(+), 3320 deletions(-) create mode 100644 .github/workflows/code_reviewer.yml create mode 100644 src/apps/customer-portal/src/lib/services/profileCompletion.service.ts create mode 100644 src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss create mode 100644 src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx create mode 100644 src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/index.ts create mode 100644 src/apps/customer-portal/src/pages/profile-completion/index.ts create mode 100644 src/apps/customer-portal/src/pages/profile-completion/profile-completion.routes.tsx delete mode 100644 src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss delete mode 100644 src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx delete mode 100644 src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/index.ts delete mode 100644 src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/TalentResultCard.module.scss delete mode 100644 src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/TalentResultCard.tsx delete mode 100644 src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/index.ts delete mode 100644 src/apps/customer-portal/src/pages/talent-search/talent-search.routes.tsx delete mode 100644 src/apps/talent-search/README.md delete mode 100644 src/apps/talent-search/index.ts delete mode 100644 src/apps/talent-search/src/TalentSearchApp.tsx delete mode 100644 src/apps/talent-search/src/assets/background-m.webp delete mode 100644 src/apps/talent-search/src/assets/background.jpg delete mode 100644 src/apps/talent-search/src/assets/background.webp delete mode 100644 src/apps/talent-search/src/assets/verified-icon-white.svg delete mode 100644 src/apps/talent-search/src/components/popular-skills/PopularSkills.module.scss delete mode 100644 src/apps/talent-search/src/components/popular-skills/PopularSkills.tsx delete mode 100644 src/apps/talent-search/src/components/popular-skills/index.ts delete mode 100644 src/apps/talent-search/src/components/profile-match/ProfileMatch.module.scss delete mode 100644 src/apps/talent-search/src/components/profile-match/ProfileMatch.tsx delete mode 100644 src/apps/talent-search/src/components/profile-match/index.ts delete mode 100644 src/apps/talent-search/src/components/profile-skills-match/ProfileSkillsMatch.module.scss delete mode 100644 src/apps/talent-search/src/components/profile-skills-match/ProfileSkillsMatch.tsx delete mode 100644 src/apps/talent-search/src/components/profile-skills-match/index.ts delete mode 100644 src/apps/talent-search/src/components/search-input/SearchInput.module.scss delete mode 100644 src/apps/talent-search/src/components/search-input/SearchInput.tsx delete mode 100644 src/apps/talent-search/src/components/search-input/index.ts delete mode 100644 src/apps/talent-search/src/components/talent-card/TalentCard.module.scss delete mode 100644 src/apps/talent-search/src/components/talent-card/TalentCard.tsx delete mode 100644 src/apps/talent-search/src/components/talent-card/index.ts delete mode 100644 src/apps/talent-search/src/config/constants.ts delete mode 100644 src/apps/talent-search/src/config/index.ts delete mode 100644 src/apps/talent-search/src/index.ts delete mode 100644 src/apps/talent-search/src/lib/models/Member.ts delete mode 100644 src/apps/talent-search/src/lib/models/MemberAddress.ts delete mode 100644 src/apps/talent-search/src/lib/models/MemberDisplayName.ts delete mode 100644 src/apps/talent-search/src/lib/models/MemberMaxRating.ts delete mode 100644 src/apps/talent-search/src/lib/models/MemberStats.ts delete mode 100644 src/apps/talent-search/src/lib/models/index.ts delete mode 100644 src/apps/talent-search/src/lib/services/index.ts delete mode 100644 src/apps/talent-search/src/lib/services/use-fetch-talent-matches.spec.ts delete mode 100644 src/apps/talent-search/src/lib/services/use-fetch-talent-matches.ts delete mode 100644 src/apps/talent-search/src/lib/utils/index.ts delete mode 100644 src/apps/talent-search/src/lib/utils/search-query.tsx delete mode 100644 src/apps/talent-search/src/lib/utils/skills.utils.tsx delete mode 100644 src/apps/talent-search/src/routes/search-page/SearchPage.module.scss delete mode 100644 src/apps/talent-search/src/routes/search-page/SearchPage.tsx delete mode 100644 src/apps/talent-search/src/routes/search-page/index.ts delete mode 100644 src/apps/talent-search/src/routes/search-results-page/SearchResultsPage.module.scss delete mode 100644 src/apps/talent-search/src/routes/search-results-page/SearchResultsPage.tsx delete mode 100644 src/apps/talent-search/src/routes/search-results-page/index.ts delete mode 100644 src/apps/talent-search/src/routes/search-results-page/letsTalkUrl.spec.ts delete mode 100644 src/apps/talent-search/src/routes/search-results-page/letsTalkUrl.ts delete mode 100644 src/apps/talent-search/src/routes/talent-page/TalentPage.module.scss delete mode 100644 src/apps/talent-search/src/routes/talent-page/TalentPage.tsx delete mode 100644 src/apps/talent-search/src/routes/talent-page/index.ts delete mode 100644 src/apps/talent-search/src/styles/main.vendor.scss delete mode 100644 src/apps/talent-search/src/talent-search.routes.tsx diff --git a/.github/workflows/code_reviewer.yml b/.github/workflows/code_reviewer.yml new file mode 100644 index 000000000..02f198a18 --- /dev/null +++ b/.github/workflows/code_reviewer.yml @@ -0,0 +1,22 @@ +name: AI PR Reviewer + +on: + pull_request: + types: + - opened + - synchronize +permissions: + pull-requests: write +jobs: + tc-ai-pr-review: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: TC AI PR Reviewer + uses: topcoder-platform/tc-ai-pr-reviewer@master + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # The GITHUB_TOKEN is there by default so you just need to keep it like it is and not necessarily need to add it as secret as it will throw an error. [More Details](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret) + LAB45_API_KEY: ${{ secrets.LAB45_API_KEY }} + exclude: '**/*.json, **/*.md, **/*.jpg, **/*.png, **/*.jpeg, **/*.bmp, **/*.webp' # Optional: exclude patterns separated by commas diff --git a/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts b/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts new file mode 100644 index 000000000..f0674b639 --- /dev/null +++ b/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts @@ -0,0 +1,142 @@ +import { EnvironmentConfig } from '~/config' +import { UserSkill, xhrGetAsync } from '~/libs/core' + +export type CompletedProfile = { + countryCode?: string + countryName?: string + city?: string + firstName?: string + handle: string + lastName?: string + photoURL?: string + skillCount?: number + userId?: number | string + isOpenToWork?: boolean | null + openToWork?: { + availability?: string + preferredRoles?: string[] + } | null +} + +export type CompletedProfilesResponse = { + data: CompletedProfile[] + page: number + perPage: number + total: number + totalPages: number +} + +export const DEFAULT_PAGE_SIZE = 50 + +function normalizeToList(raw: any): any[] { + if (Array.isArray(raw)) { + return raw + } + + if (Array.isArray(raw?.data)) { + return raw.data + } + + if (Array.isArray(raw?.result?.content)) { + return raw.result.content + } + + if (Array.isArray(raw?.result)) { + return raw.result + } + + return [] +} + +function normalizeCompletedProfilesResponse( + raw: any, + fallbackPage: number, + fallbackPerPage: number, +): CompletedProfilesResponse { + if (raw && Array.isArray(raw.data)) { + const total: number = Number(raw.total ?? raw.data.length) + const perPage: number = Number(raw.perPage ?? fallbackPerPage) + const page: number = Number(raw.page ?? fallbackPage) + const safePerPage = Number.isFinite(perPage) ? Math.max(perPage, 1) : fallbackPerPage + const safeTotal = Number.isFinite(total) ? Math.max(total, 0) : raw.data.length + + return { + data: raw.data, + page: Number.isFinite(page) ? Math.max(page, 1) : fallbackPage, + perPage: safePerPage, + total: safeTotal, + totalPages: Number.isFinite(raw.totalPages) + ? Math.max(Number(raw.totalPages), 1) + : Math.max(Math.ceil(safeTotal / safePerPage), 1), + } + } + + const rows = normalizeToList(raw) + const total = Number(raw?.total ?? rows.length) + const safeTotal = Number.isFinite(total) ? Math.max(total, 0) : rows.length + + return { + data: rows, + page: fallbackPage, + perPage: fallbackPerPage, + total: safeTotal, + totalPages: Math.max(Math.ceil(safeTotal / fallbackPerPage), 1), + } +} + +export type OpenToWorkFilter = 'all' | 'yes' | 'no' + +export async function fetchCompletedProfiles( + countryCode: string | undefined, + page: number, + perPage: number, + openToWorkFilter?: OpenToWorkFilter, + skillIds?: string[], +): Promise { + const queryParams = new URLSearchParams({ + page: String(page), + perPage: String(perPage), + }) + + if (countryCode) { + queryParams.set('countryCode', countryCode) + } + + if (openToWorkFilter === 'yes') { + queryParams.set('openToWork', 'true') + } + + if (openToWorkFilter === 'no') { + queryParams.set('openToWork', 'false') + } + + if (Array.isArray(skillIds) && skillIds.length > 0) { + skillIds.forEach(id => { + if (id) { + queryParams.append('skillId', String(id)) + } + }) + } + + const response = await xhrGetAsync( + `${EnvironmentConfig.REPORTS_API}/topcoder/completed-profiles?${queryParams.toString()}`, + ) + + return normalizeCompletedProfilesResponse(response, page, perPage) +} + +export async function fetchMemberSkillsData(userId: string | number | undefined): Promise { + if (!userId) { + return [] + } + + const baseUrl = `${EnvironmentConfig.API.V5}/standardized-skills` + const url = `${baseUrl}/user-skills/${userId}?disablePagination=true` + + try { + return await xhrGetAsync(url) + } catch { + // If skills API fails, return empty array to not block the page + return [] + } +} diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss new file mode 100644 index 000000000..ec7051428 --- /dev/null +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss @@ -0,0 +1,218 @@ +@import '@libs/ui/styles/includes'; + +.container { + display: flex; + flex-direction: column; + gap: $sp-4; +} + +.headerRow { + display: flex; + align-items: flex-end; + gap: $sp-4; + justify-content: space-between; + + @include ltemd { + flex-direction: column; + align-items: stretch; + } +} + +.filterWrapper { + display: flex; + gap: $sp-4; + + :global([class*='__value-container']) { + min-height: 18px; + } + + @include ltemd { + flex-direction: column; + align-items: stretch; + } + + .filterWrap { + min-width: 280px; + max-width: 360px; + + @include ltemd { + max-width: unset; + min-width: unset; + width: 100%; + } + } +} + +.counterCard { + border: 1px solid $black-20; + border-radius: $sp-2; + background: $tc-white; + padding: $sp-4; + min-width: 260px; + display: flex; + flex-direction: column; + gap: $sp-1; +} + +.counterLabel { + color: $black-60; + font-size: 12px; + line-height: 16px; + font-weight: 600; + text-transform: uppercase; +} + +.counterValue { + color: $black-100; + font-size: 32px; + line-height: 36px; + font-weight: 700; + font-family: 'Nunito Sans', sans-serif; +} + +.loadingWrap { + position: relative; + height: 90px; + + .spinner { + background: none; + } +} + +.errorMessage { + color: $red-100; + font-size: 14px; + line-height: 20px; + font-weight: 700; +} + +.emptyMessage { + color: $black-60; + font-size: 14px; + line-height: 20px; +} + +.tableWrap { + overflow: auto; + border: 1px solid $black-20; + border-radius: $sp-2; + + table { + width: 100%; + border-collapse: collapse; + min-width: 1120px; + } + + th, + td { + text-align: left; + padding: $sp-3 $sp-4; + border-bottom: 1px solid $black-20; + font-size: 14px; + line-height: 20px; + } + + th { + color: $black-100; + font-weight: 700; + background: $black-5; + } + + td { + color: $black-100; + vertical-align: middle; + } + + tr:last-child td { + border-bottom: 0; + } +} + +.memberCell { + display: flex; + align-items: center; + gap: $sp-2; +} + +.avatar { + width: 28px; + height: 28px; + border-radius: 50%; + object-fit: cover; + border: 1px solid $black-20; +} + +.paginationRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: $sp-3; + + @include ltemd { + flex-direction: column; + align-items: flex-start; + } +} + +.paginationInfo { + color: $black-60; + font-size: 14px; + line-height: 20px; +} + +.paginationButtons { + display: flex; + align-items: center; + gap: $sp-2; +} + +.skillsList { + display: flex; + flex-wrap: wrap; + gap: $sp-2; +} + +.skillTag { + display: inline-block; + background: $black-5; + border: 1px solid $black-20; + border-radius: $sp-1; + padding: $sp-1 $sp-2; + font-size: 12px; + line-height: 16px; + color: $black-80; + white-space: nowrap; +} + +.moreIndicator { + display: inline-block; + background: $black-5; + border: 1px solid $black-20; + border-radius: $sp-1; + padding: $sp-1 $sp-2; + font-size: 12px; + line-height: 16px; + color: $black-80; + font-weight: 700; + min-width: 24px; + text-align: center; + cursor: help; +} + +.link { + display: flex; + gap: $sp-1; + text-decoration: underline; + color: $link-blue; + cursor: pointer; +} + +.openToWorkYes { + color: $green-100; + font-weight: 600; +} + +.openToWorkNo { + color: $red-100; + font-weight: 600; +} diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx new file mode 100644 index 000000000..0434e1826 --- /dev/null +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx @@ -0,0 +1,409 @@ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable no-await-in-loop */ +/* eslint-disable complexity */ +import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react' +import useSWR, { SWRResponse } from 'swr' + +import { EnvironmentConfig } from '~/config' +import { CountryLookup, useCountryLookup, UserSkill, UserSkillDisplayModes } from '~/libs/core' +import { + Button, + InputMultiselect, + InputMultiselectOption, + InputSelect, + InputSelectOption, + LoadingSpinner, + Tooltip, +} from '~/libs/ui' +import { fetchSkillAutocompleteOptions } from '~/libs/shared' +import { getPreferredRoleLabelByValue } from '~/libs/shared/lib/utils/roles' + +import { PageWrapper } from '../../../lib' +import { + CompletedProfilesResponse, + DEFAULT_PAGE_SIZE, + fetchCompletedProfiles, + fetchMemberSkillsData, + type OpenToWorkFilter, +} from '../../../lib/services/profileCompletion.service' + +import styles from './ProfileCompletionPage.module.scss' + +const DISPLAY_SKILLS_COUNT = 5 + +export const ProfileCompletionPage: FC = () => { + const [selectedCountry, setSelectedCountry] = useState('all') + const [currentPage, setCurrentPage] = useState(1) + const [selectedOpenToWork, setSelectedOpenToWork] = useState('all') + const [selectedSkills, setSelectedSkills] = useState([]) + const [memberSkills, setMemberSkills] = useState>(new Map()) + const [skillOptionsLoading, setSkillOptionsLoading] = useState(false) + const countryLookup: CountryLookup[] | undefined = useCountryLookup() + + const countryCodeFilter = selectedCountry === 'all' ? undefined : selectedCountry + + const loadSkillOptions = useCallback(async (query: string): Promise => { + setSkillOptionsLoading(true) + try { + return await fetchSkillAutocompleteOptions(query) + } catch { + return [] + } finally { + setSkillOptionsLoading(false) + } + }, []) + + const { data, error, isValidating }: SWRResponse = useSWR( + // eslint-disable-next-line max-len + `customer-portal-completed-profiles:${countryCodeFilter || 'all'}:${selectedOpenToWork}:${currentPage}:${DEFAULT_PAGE_SIZE}:${selectedSkills.map(skill => skill.value) + .sort() + .join(',')}`, + () => fetchCompletedProfiles( + countryCodeFilter, + currentPage, + DEFAULT_PAGE_SIZE, + selectedOpenToWork, + selectedSkills.map(skill => skill.value), + ), + { + revalidateOnFocus: false, + }, + ) + + // Fetch member skills for all profiles on the current page + useEffect(() => { + if (!data?.data || data.data.length === 0) return + + const fetchAllMemberSkills = async (): Promise => { + const skillsMap = new Map() + + for (const profile of data.data) { + if (profile.userId && !memberSkills.has(profile.userId)) { + const skills = await fetchMemberSkillsData(profile.userId) + skillsMap.set(profile.userId, skills) + } + } + + if (skillsMap.size > 0) { + setMemberSkills(prevSkills => { + const newMap = new Map(prevSkills) + skillsMap.forEach((skills, userId) => { + newMap.set(userId, skills) + }) + return newMap + }) + } + } + + fetchAllMemberSkills() + }, [data?.data]) + + const countryMap = useMemo(() => { + const map = new Map() + const countries = countryLookup || [] + + countries.forEach((country: CountryLookup) => { + if (country.countryCode) { + map.set(country.countryCode, country.country) + } + }) + + return map + }, [countryLookup]) + + const countryOptions = useMemo(() => { + const staticOptions = (countryLookup || []) + .filter(country => !!country.countryCode) + .map(country => ({ + label: country.country, + value: country.countryCode, + })) + .sort((a, b) => String(a.label) + .localeCompare(String(b.label))) + + const seen = new Set(staticOptions.map(option => option.value)) + const dynamicOptions = (data?.data || []) + .filter(profile => !!profile.countryCode && !seen.has(String(profile.countryCode))) + .map(profile => ({ + label: ( + countryMap.get(String(profile.countryCode)) + || profile.countryName + || String(profile.countryCode) + ), + value: String(profile.countryCode), + })) + .sort((a, b) => String(a.label) + .localeCompare(String(b.label))) + + return [ + { + label: 'All Countries', + value: 'all', + }, + ...staticOptions, + ...dynamicOptions, + ] + }, [countryLookup, countryMap, data?.data]) + + const profiles = data?.data || [] + const totalProfiles = data?.total || 0 + const totalPages = data?.totalPages || 1 + + const displayedRows = useMemo(() => profiles + .map(profile => { + const userSkills = profile.userId ? (memberSkills.get(profile.userId) || []) : [] + + // Prioritize principal skills, then add additional skills + const principalSkills = [ + ...userSkills.filter(skill => skill.displayMode?.name === UserSkillDisplayModes.principal), + ] + + const displayedSkills = principalSkills.slice(0, DISPLAY_SKILLS_COUNT) + const remainingSkillsText = principalSkills.slice(DISPLAY_SKILLS_COUNT) + .map(skill => skill.name) + .filter(Boolean) + .join(', ') + const additionalSkillsCount = Math.max(0, principalSkills.length - DISPLAY_SKILLS_COUNT) + + const isOpenToWork = profile.isOpenToWork === true + const openToWorkLabel = isOpenToWork ? 'Yes' : 'No' + const openToWorkRolesText = profile.openToWork?.preferredRoles && profile.openToWork.preferredRoles.length + ? profile.openToWork.preferredRoles.map(getPreferredRoleLabelByValue) + .filter(Boolean) + .join(', ') + : 'No role preferences set' + + return { + ...profile, + additionalSkillsCount, + countryLabel: profile.countryCode + ? countryMap.get(profile.countryCode) || profile.countryName || profile.countryCode + : profile.countryName || '-', + displayedSkills, + fullName: [profile.firstName, profile.lastName].filter(Boolean) + .join(' ') + .trim(), + isOpenToWork, + locationLabel: [profile.city, profile.countryCode + ? countryMap.get(profile.countryCode) || profile.countryName || profile.countryCode + : profile.countryName] + .filter(Boolean) + .join(', '), + openToWorkLabel, + openToWorkRolesText, + remainingSkillsText, + } + }) + .sort((a, b) => a.handle.localeCompare(b.handle)), [profiles, countryMap, memberSkills]) + + const isPreviousDisabled = currentPage <= 1 || isValidating + const isNextDisabled = isValidating || currentPage >= totalPages + + return ( + +
+
+
+ ) => { + setSelectedCountry(event.target.value || 'all') + setCurrentPage(1) + }} + placeholder='Select country' + /> +
+
+ ) => { + setSelectedOpenToWork((event.target.value || 'all') as OpenToWorkFilter) + setCurrentPage(1) + }} + placeholder='Select' + /> +
+
+ ) => { + const value = (event.target.value || []) as InputMultiselectOption[] + setSelectedSkills(value) + setCurrentPage(1) + }} + /> +
+
+
+ Fully Completed Profiles + {totalProfiles} +
+
+ + {isValidating && !data && ( +
+ +
+ )} + + {!isValidating && !!error && ( +
+ Failed to load profile completion data. +
+ )} + + {!error && !isValidating && displayedRows.length === 0 && ( +
+ No fully completed profiles found for the selected country. +
+ )} + + {!error && displayedRows.length > 0 && ( + <> +
+ + + + + + + + + + + + + {displayedRows.map(profile => ( + + + + + + + + + ))} + +
MemberHandleLocationOpen to WorkPrincipal Skills{' '}
+
+ {profile.photoURL && ( + {profile.handle} + )} + {profile.fullName || '-'} +
+
+ + {profile.handle} + + {profile.locationLabel || profile.countryLabel} + { + profile.openToWorkLabel === 'Yes' ? ( + + + {profile.openToWorkLabel} + + + ) : ( + + {profile.openToWorkLabel} + + ) + } + + {profile.displayedSkills && profile.displayedSkills.length > 0 ? ( +
+ {profile.displayedSkills.map(skill => ( + + {skill.name} + + ))} + {profile.additionalSkillsCount > 0 && ( + + + + + {profile.additionalSkillsCount} + {' '} + skills + + + )} +
+ ) : ( + '-' + )} +
+ + Go to profile + +
+
+
+ + Page + {' '} + {currentPage} + {' '} + of + {' '} + {totalPages} + +
+ + +
+
+ + )} +
+ ) +} + +export default ProfileCompletionPage diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/index.ts b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/index.ts new file mode 100644 index 000000000..4d99c8c31 --- /dev/null +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/index.ts @@ -0,0 +1 @@ +export { default as ProfileCompletionPage } from './ProfileCompletionPage' diff --git a/src/apps/customer-portal/src/pages/profile-completion/index.ts b/src/apps/customer-portal/src/pages/profile-completion/index.ts new file mode 100644 index 000000000..73dcadd92 --- /dev/null +++ b/src/apps/customer-portal/src/pages/profile-completion/index.ts @@ -0,0 +1 @@ +export * from './ProfileCompletionPage' diff --git a/src/apps/customer-portal/src/pages/profile-completion/profile-completion.routes.tsx b/src/apps/customer-portal/src/pages/profile-completion/profile-completion.routes.tsx new file mode 100644 index 000000000..42042bc0f --- /dev/null +++ b/src/apps/customer-portal/src/pages/profile-completion/profile-completion.routes.tsx @@ -0,0 +1,26 @@ +import { getRoutesContainer, lazyLoad, LazyLoadedComponent } from '~/libs/core' + +import { profileCompletionRouteId } from '../../config/routes.config' + +const ProfileCompletionPage: LazyLoadedComponent = lazyLoad( + () => import('./ProfileCompletionPage'), + 'ProfileCompletionPage', +) + +export const profileCompletionChildRoutes = [ + { + authRequired: true, + element: , + id: 'profile-completion-page', + route: '', + }, +] + +export const customerPortalProfileCompletionRoutes = [ + { + children: [...profileCompletionChildRoutes], + element: getRoutesContainer(profileCompletionChildRoutes), + id: profileCompletionRouteId, + route: profileCompletionRouteId, + }, +] 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 deleted file mode 100644 index 22116c969..000000000 --- a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss +++ /dev/null @@ -1,409 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.container { - display: flex; - flex-direction: column; -} - -:global([class*='ContentLayout-module_content-outer']) { - 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; - background: $black-5; -} - -.pageHero { - width: 100%; - height: 280px; - background-image: url('../../../lib/assets/talent-search-header.png'); - background-position: center; - background-repeat: no-repeat; - background-size: cover; -} - -.pageBody { - display: grid; - grid-template-columns: 443px 1fr; - gap: 24px; - margin-top: -232px; - padding: $sp-2 $sp-8 $sp-10 $sp-8; - position: relative; - z-index: 1; - font-family: $font-roboto; - - @include ltemd { - grid-template-columns: 1fr; - margin-top: -232px; - padding: $sp-3 $sp-3 0; - } -} - -.sidebar { - display: flex; - flex-direction: column; - gap: 0; - - @include ltemd { - margin-left: 0; - position: relative; - z-index: 2; - } -} - -.panel { - background: $tc-white; - border: 0; - border-radius: 16px; - padding: 20px; - display: flex; - flex-direction: column; - gap: $sp-2; -} - -.sidebar .panel + .panel { - border-top-left-radius: 0; - border-top-right-radius: 0; - border-top: 0; - padding-top: 14px; -} - -.panelTitle { - color: $black-100; - font-size: 16px; - line-height: 24px; - font-weight: 500; - margin: 0; -} - -.searchTabs { - display: flex; - gap: 40px; - border-bottom: 0; - padding-bottom: 0; - border-bottom: 1px solid $black-20; -} - -.tabButton { - border: 0; - background: transparent; - padding: 12px 0; - color: $black-60; - font-family: $font-roboto; - font-size: 16px; - line-height: 24px; - font-weight: 500; -} - -.activeTab { - color: $black-100; -} - -.jobDescriptionField { - margin-top: 0; - :global(textarea) { - min-height: 170px; - resize: vertical; - color: $black-100; - font-size: 14px; - line-height: 22px; - } -} - -.aiActions { - display: flex; - gap: $sp-1; - - :global(button) { - font-size: 14px; - } -} - -.errorMessage { - color: $red-100; - font-size: 13px; - line-height: 18px; - margin: 0; -} - -.filterBlock { - margin-bottom: 2px; - :global([class*='__value-container']) { - min-height: 18px; - } -} - -.skillsMultiselect { - :global([class*='__value-container']) { - gap: 6px; - } - - :global([class*='__multi-value']) { - margin: 0; - border-radius: 12px; - background: $black-10; - } - - :global([class*='__multi-value__label']) { - padding: 3px 8px; - color: $black-100; - font-size: 12px; - line-height: 14px; - font-weight: 500; - } - - :global([class*='__multi-value__remove']) { - margin: 0 6px 0 0; - padding: 2px; - color: $black-60; - - svg { - width: 14px; - height: 14px; - } - } -} - -.checkboxRow { - display: flex; - align-items: center; - gap: $sp-2; - color: $black-100; - font-size: 15px; - line-height: 21px; - font-weight: 400; - position: relative; - cursor: pointer; -} - -.checkboxLabelWithInfo { - display: inline-flex; - align-items: center; - gap: 6px; -} - -.infoIconButton { - display: inline-flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border: 0; - border-radius: 50%; - padding: 0; - background: transparent; - color: $black-60; - cursor: pointer; - - :global(svg) { - width: 18px; - height: 18px; - } -} - -.checkboxInput { - position: absolute; - opacity: 0; - pointer-events: none; -} - -.toggleControl { - width: 36px; - height: 20px; - border-radius: 10px; - background: $turq-160; - position: relative; - flex-shrink: 0; - - &::after { - content: ''; - position: absolute; - top: 3px; - left: 20px; - width: 14px; - height: 14px; - border-radius: 50%; - background: $tc-white; - } -} - -.checkboxInput:not(:checked) + .toggleControl { - background: $black-40; - - &::after { - left: 2px; - } -} - -.clearFiltersWrap { - margin-top: 6px; - align-self: flex-start; - display: flex; - gap: $sp-2; - - :global(button) { - font-size: 14px; - } -} - -.resultsPanel { - background: transparent; - border: 0; - border-radius: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 0; - overflow: visible; - min-height: 620px; -} - -.resultsPanelEmpty { - background: transparent; - border: 0; - border-radius: 0; -} - -.resultsContent { - display: flex; - flex-direction: column; - gap: $sp-4; - padding: 0; -} - -.resultsTop { - display: flex; - align-items: center; - justify-content: space-between; - gap: $sp-3; - padding-top: 0; - - @include ltemd { - flex-direction: column; - align-items: stretch; - } -} - -.matchingIndexSelect { - width: 184px; - - :global(.input-el) { - padding: 4px 10px 1px 10px; - } -} - -.sortControl { - display: flex; - align-items: center; - gap: 12px; - width: 300px; - justify-content: flex-end; - - :global([class*='__control']) { - min-height: 40px; - } - - :global(.input-el) { - margin-bottom: 0; - } -} - -.sortLabel { - color: $tc-white; - font-size: 16px; - line-height: 22px; - white-space: nowrap; - - @include ltemd { - color: $black-100; - } -} - -.foundText { - margin: 0; - color: $tc-white; - font-size: 16px; - line-height: 20px; - - @include ltemd { - color: $black-100; - } -} - -.foundTextCount { - font-weight: 700; -} - -.emptyState { - min-height: 470px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - padding: $sp-6; - margin-top: 20vh; - text-align: center; - color: $black-60; - - h4 { - margin: 0 0 $sp-2; - color: $black-100; - } - - p { - margin: 0 0 $sp-1; - max-width: 500px; - } -} - -.emptyStateTitle { - color: $black-100; - font-size: 24px; - line-height: 32px; - font-weight: 700; - margin: 0 0 $sp-2; -} - -.emptyStateDescription { - color: $black-100; - font-size: 16px; - line-height: 24px; - font-weight: 400; - margin: 0; -} - -.emptyStateSearchText { - font-weight: 700; -} - -.emptyIcon { - width: 80px; - height: 80px; - margin-bottom: $sp-3; -} - -.cardsGrid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 20px; - - @include ltemd { - grid-template-columns: 1fr; - } -} - -.loadMoreWrap { - display: flex; - justify-content: center; - margin-top: 8px; -} 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 deleted file mode 100644 index b45a923d7..000000000 --- a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx +++ /dev/null @@ -1,578 +0,0 @@ -/* eslint-disable complexity */ -/* eslint-disable react/jsx-no-bind */ -import { ChangeEvent, FC, FocusEvent, useCallback, useMemo, useRef, useState } from 'react' -import classNames from 'classnames' - -import { CountryLookup, useCountryLookup } from '~/libs/core' -import { - Button, - IconOutline, - InputMultiselect, - InputMultiselectOption, - InputTextarea, - Tooltip, -} from '~/libs/ui' -import { extractSkillsFromText, fetchSkillAutocompleteOptions } from '~/libs/shared' - -import { TalentResultCard } from '../components/TalentResultCard' -import { - MemberSearchPayload, - MEMBER_SEARCH_LIMIT, - PageWrapper, - searchMembers, - SearchTalent, -} from '../../../lib' - -import styles from './TalentSearchPage.module.scss' - -export const TalentSearchPage: FC = () => { - const searchGenerationRef = useRef(0) - - 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(false) - const [skillOptionsLoading, setSkillOptionsLoading] = useState(false) - const [selectedSkills, setSelectedSkills] = useState([]) - const [selectedCountries, setSelectedCountries] = useState([]) - const [onlyProfileComplete, setOnlyProfileComplete] = useState(false) - const [onlyOpenToWork, setOnlyOpenToWork] = useState(true) - const [onlyActive, setOnlyActive] = 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 [showSkillMatchOnCards, setShowSkillMatchOnCards] = useState(false) - const countryNameByCode = useMemo((): Map => new Map( - (countryLookup || []) - .filter(country => country.countryCode && country.country) - .map(country => [country.countryCode.toUpperCase(), country.country]), - ), [countryLookup]) - const countryFilterOptions = useMemo( - (): InputMultiselectOption[] => (countryLookup || []) - .map(country => ({ - label: country.country, - value: country.countryCode, - })) - .filter(option => option.label && option.value) - .sort((a, b) => String(a.label) - .localeCompare(String(b.label))), - [countryLookup], - ) - const selectedCountryCodesList = useMemo( - (): string[] => selectedCountries - .map(country => String(country.value || '') - .trim() - .toUpperCase()) - .filter(Boolean), - [selectedCountries], - ) - - 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. - const displayedResults = results - - const foundMembersCount = totalResults || displayedResults.length - const displayedResultsWithCountryName = useMemo( - () => displayedResults.map(talent => { - const code = String(talent.location || '') - .trim() - .toUpperCase() - const countryName = countryNameByCode.get(code) - - if (!countryName) { - return talent - } - - return { - ...talent, - location: countryName, - } - }), - [countryNameByCode, displayedResults], - ) - const hasMoreResults = results.length < totalResults - - const loadSkillOptions = useCallback(async (query: string): Promise => { - setSkillOptionsLoading(true) - try { - return await fetchSkillAutocompleteOptions(query) - } catch { - return [] - } finally { - setSkillOptionsLoading(false) - } - }, []) - const loadCountryOptions = useCallback(async (query: string): Promise => { - const normalizedQuery = query.trim() - .toLowerCase() - if (!normalizedQuery) { - return countryFilterOptions - } - - return countryFilterOptions.filter(option => String(option.label || '') - .toLowerCase() - .includes(normalizedQuery)) - }, [countryFilterOptions]) - - const runMemberSearch = useCallback(async ( - skillsToSearch: InputMultiselectOption[], - overrides?: { - append?: boolean - countries?: string[] - generation?: number - openToWork?: boolean - page?: number - profileComplete?: boolean - recentlyActive?: boolean - }, - ): Promise => { - const append = overrides?.append === true - - const countries = (overrides?.countries ?? selectedCountryCodesList) - .filter(Boolean) - const generation = overrides?.generation - const openToWork = overrides?.openToWork ?? onlyOpenToWork - const page = overrides?.page ?? 1 - const profileComplete = overrides?.profileComplete ?? onlyProfileComplete - const recentlyActive = overrides?.recentlyActive ?? onlyActive - const hasSkills = skillsToSearch.length > 0 - const payload: MemberSearchPayload = { - limit: MEMBER_SEARCH_LIMIT, - page, - skills: skillsToSearch - .map(skill => String(skill.value || '') - .trim()) - .filter(Boolean) - .map(skillId => ({ - id: skillId, - wins: 1, - })), - skillSearchType: 'OR', - sortBy: hasSkills ? 'matchIndex' : 'handle', - sortOrder: hasSkills ? 'desc' : 'asc', - } - - if (countries.length > 0) { - payload.countries = countries - } - - if (openToWork) { - payload.openToWork = true - } - - if (profileComplete) { - payload.profileComplete = true - } - - if (recentlyActive) { - payload.recentlyActive = true - } - - if (append) { - setIsLoadingMore(true) - } else { - setIsSearchingMembers(true) - } - - setErrorMessage('') - try { - const response = await searchMembers(payload) - // 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 - } - - const seen = new Set(prevResults.map(item => item.id)) - const merged = [...prevResults] - fetchedData.forEach(item => { - if (!seen.has(item.id)) { - seen.add(item.id) - merged.push(item) - } - }) - return merged - }) - setTotalResults(Number(response?.total || 0)) - setCurrentPage(Number(response?.page || page)) - 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) - } - } - }, [onlyActive, onlyOpenToWork, onlyProfileComplete, selectedCountryCodesList]) - - const clearAllFilters = useCallback((): void => { - setSelectedCountries([]) - setOnlyProfileComplete(false) - setOnlyOpenToWork(true) - setOnlyActive(true) - setSelectedSkills([]) - setErrorMessage('') - setLastSearchedDescription('') - }, []) - - const handleAiSearch = useCallback(async (): Promise => { - const normalizedDescription = jobDescription.trim() - if (!normalizedDescription || isExtractingSkills) { - 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 - : [] - const skillById = new Map() - - extractedSkills.forEach((skill: { id?: unknown; name?: unknown }) => { - const skillId = String(skill.id ?? '') - .trim() - const skillName = String(skill.name ?? '') - .trim() - - if (!skillId || !skillName) { - return - } - - skillById.set(skillId, { - label: skillName, - value: skillId, - }) - }) - - const extractedOptions = Array.from(skillById.values()) - setSelectedSkills(extractedOptions) - - if (extractedOptions.length === 0) { - setErrorMessage('No skills were extracted from the job description.') - return - } - - setLastSearchedDescription(normalizedDescription) - } catch { - if (searchGenerationRef.current !== generation) return - setErrorMessage('Failed to extract skills. Please try again.') - } finally { - setIsExtractingSkills(false) - - } - }, [isExtractingSkills, jobDescription]) - - const handleSearch = useCallback(async (): Promise => { - if (isSearchingMembers || selectedSkills.length === 0) { - return - } - - setHasSearched(true) - const hadSkills = selectedSkills.length > 0 - const searchSucceeded = await runMemberSearch(selectedSkills, { - countries: selectedCountryCodesList, - openToWork: onlyOpenToWork, - page: 1, - profileComplete: onlyProfileComplete, - recentlyActive: onlyActive, - }) - if (searchSucceeded) { - setLastAppliedSearchSignature(currentSearchSignature) - setShowSkillMatchOnCards(hadSkills) - } - }, [ - currentSearchSignature, - isSearchingMembers, - onlyActive, - onlyOpenToWork, - onlyProfileComplete, - runMemberSearch, - selectedCountryCodesList, - selectedSkills, - ]) - - const handleLoadMore = useCallback((): void => { - if (isLoadingMore || isSearchingMembers || !hasMoreResults) { - return - } - - runMemberSearch(selectedSkills, { - append: true, - page: currentPage + 1, - }) - }, [currentPage, hasMoreResults, isLoadingMore, isSearchingMembers, runMemberSearch, selectedSkills]) - const isAiExtractButtonDisabled = useMemo( - () => isExtractingSkills - || !jobDescription.trim() - || jobDescription.trim() === lastSearchedDescription, - [isExtractingSkills, jobDescription, lastSearchedDescription], - ) - return ( - -
-
-
- - -
- {shouldShowIntroState && ( -
-

- Paste a job description to AI-extract skills, or enter skills manually - to find talents -

-
- )} - - {!shouldShowIntroState && ( -
- {!isSearchingMembers && ( -
-

- We have found  - - {`${foundMembersCount} members`} - -  that match your search. -

-
- )} - {isSearchingMembers && ( -
-

Searching talent...

-
- )} - {!isSearchingMembers && displayedResults.length === 0 && ( -
-

No matching talent found

-

Try changing filters or using a different job description.

-
- )} - {!isSearchingMembers && displayedResults.length > 0 && ( - <> -
- {displayedResultsWithCountryName.map(talent => ( - - ))} -
- {hasMoreResults && ( -
- -
- )} - - )} -
- )} -
-
-
- - ) -} - -export default TalentSearchPage diff --git a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/index.ts b/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/index.ts deleted file mode 100644 index ecd3857a2..000000000 --- a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as TalentSearchPage } from './TalentSearchPage' diff --git a/src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/TalentResultCard.module.scss b/src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/TalentResultCard.module.scss deleted file mode 100644 index 2cea74382..000000000 --- a/src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/TalentResultCard.module.scss +++ /dev/null @@ -1,298 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.talentCard { - background: $tc-white; - border: 0; - border-radius: 12px; - padding: 0; - display: flex; - flex-direction: column; - overflow: hidden; -} - -.cardMain { - padding: 24px; -} - -.topRow { - display: flex; - gap: 16px; - align-items: flex-start; - width: 100%; -} - -.avatarWrap { - position: relative; - flex-shrink: 0; -} - -.profilePic { - width: 80px; - height: 80px; - - :global(span) { - font-size: 30px !important; - letter-spacing: 0.04em; - text-transform: uppercase; - } -} - -.verifiedBadge { - position: absolute; - right: 2px; - top: 2px; - width: 20px; - height: 20px; - background: $tc-white; - color: $turq-120; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 50%; - z-index: 2; - - :global(svg) { - width: 18px; - height: 18px; - display: block; - } -} - -.headContent { - display: flex; - flex-direction: column; - gap: 8px; - flex: 1; - min-width: 0; -} - -.cardHeader { - display: flex; - justify-content: space-between; - gap: $sp-2; - align-items: center; - min-width: 0; -} - -.handleText { - margin: 0; - color: $black-100; - font-size: 20px; - line-height: 26px; - font-weight: 700; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - @include ltemd { - font-size: 16px; - line-height: 22px; - } -} - -.nameText { - margin: 0; - color: $black-100; - font-size: 14px; - line-height: 20px; - font-weight: 500; -} - -.matchPill { - background: $teal-120; - color: $tc-white; - border-radius: 999px; - padding: 3px 10px; - font-size: 14px; - line-height: 22px; - font-weight: 500; - white-space: nowrap; - align-self: flex-start; - flex-shrink: 0; -} - -.locationRow { - display: flex; - align-items: flex-start; - gap: 6px; -} - -.locationIcon { - flex-shrink: 0; - width: 20px; - height: 20px; - margin-top: 1px; - color: $turq-160; -} - -.locationText { - margin: 0; - color: $black-100; - font-size: 14px; - line-height: 22px; - font-weight: 400; -} - -.statusRow { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 16px; -} - -.statusPill { - border-radius: 999px; - padding: 2px 10px; - font-size: 14px; - line-height: 22px; - font-weight: 500; -} - -.statusPillActive { - background: $green-25; - color: $turq-180; -} - -.statusPillInactive { - background: $black-10; - color: $black-80; -} - -.availability { - display: inline-flex; - align-items: center; - gap: 2px; - font-size: 14px; - line-height: 22px; - font-weight: 500; -} - -.availabilityYes { - color: $turq-160; -} - -.availabilityNo { - color: $black-80; -} - -.availabilityIcon { - flex-shrink: 0; - width: 18px; - height: 18px; -} - -.cardFooter { - display: flex; - align-items: center; - justify-content: space-between; - gap: $sp-2; - padding: 16px 24px 20px; - border-top: 1px solid $black-10; - font-size: 14px; - line-height: 22px; - font-weight: 400; -} - -.cardFooterWithoutMatch { - justify-content: flex-end; -} - -.footerMatched { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} - -.experienceLink { - display: inline-flex; - align-items: center; - gap: 4px; - flex-shrink: 0; - color: $black-100; - text-decoration: none; - - &:hover { - color: $turq-160; - } - - &:focus-visible { - outline: 2px solid $link-blue; - outline-offset: 2px; - border-radius: 2px; - } -} - -.experienceLinkIcon { - width: 16px; - height: 16px; - color: $turq-160; -} - -.matchedSkillsText { - margin: 0; - color: $black-100; - font-weight: 500; -} - -.infoButton { - display: inline-flex; - align-items: center; - justify-content: center; - margin: 0; - padding: 0; - border: 0; - background: transparent; - cursor: pointer; - color: $black-60; - border-radius: 50%; - - &:hover { - color: $black-80; - } - - &:focus-visible { - outline: 2px solid $link-blue; - outline-offset: 2px; - } -} - -.infoIcon { - width: 18px; - height: 18px; - display: block; -} - -.tooltipBody { - text-align: left; -} - -.tooltipTitle { - margin: 0 0 8px; - font-weight: 700; - font-size: 13px; - line-height: 18px; -} - -.tooltipLines { - margin: 0; - padding-left: 0; - list-style-type: disc; - list-style-position: inside; - font-size: 12px; - line-height: 16px; - font-weight: 400; -} - -.tooltipSkillLine { - display: list-item; - - & + & { - margin-top: 4px; - } -} - -.tooltipSkillName { - font-weight: 700; -} diff --git a/src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/TalentResultCard.tsx b/src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/TalentResultCard.tsx deleted file mode 100644 index 2b37a9c8b..000000000 --- a/src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/TalentResultCard.tsx +++ /dev/null @@ -1,205 +0,0 @@ -/* eslint-disable complexity */ -import { FC, ReactElement, useMemo } from 'react' -import classNames from 'classnames' - -import { EnvironmentConfig } from '~/config' -import { ProfilePicture } from '~/libs/shared' -import { IconOutline, IconSolid, Tooltip } from '~/libs/ui' - -import styles from './TalentResultCard.module.scss' - -interface MatchedSkill { - id: string - name: string - wins: number - submitted: number -} - -interface TalentResultCardTalent { - id: string - handle: string - isVerified: boolean - isRecentlyActive: boolean - location: string - matchIndex: number - matchedSkills: MatchedSkill[] - name: string - openToWork?: boolean - photoUrl?: string -} - -interface TalentResultCardProps { - talent: TalentResultCardTalent - showSkillMatch: boolean -} - -function getUniqueMatchedSkills(talent: TalentResultCardTalent): TalentResultCardTalent['matchedSkills'] { - const seen = new Set() - return talent.matchedSkills.filter((skill: MatchedSkill) => { - const key = `${skill.id}-${skill.name}` - if (seen.has(key)) { - return false - } - - seen.add(key) - return true - }) -} - -function matchedSkillStatsLabel(skill: MatchedSkill): string { - const parts: string[] = [] - if (skill.wins > 0) { - parts.push(`${skill.wins} wins`) - } - - if (skill.submitted > 0) { - parts.push(`${skill.submitted} submissions`) - } - - return parts.length > 0 ? `: ${parts.join(', ')}` : '' -} - -function buildMatchedSkillsTooltipContent( - count: number, - skills: MatchedSkill[], -): ReactElement { - return ( -
-

- {`${count} Matched Skills:`} -

-
    - {skills.map((skill: MatchedSkill) => ( -
  • - {skill.name} - {matchedSkillStatsLabel(skill)} -
  • - ))} -
-
- ) -} - -export const TalentResultCard: FC = (props: TalentResultCardProps) => { - const talent: TalentResultCardTalent = props.talent - const showSkillMatch = props.showSkillMatch - const uniqueSkills = useMemo(() => getUniqueMatchedSkills(talent), [talent]) - const isVerifiedProfile = talent.isVerified === true - const displayName = String(talent.name || '') - .trim() - const [firstName, ...lastNameParts] = displayName.split(/\s+/) - const lastName = lastNameParts.join(' ') - .trim() - - const isActive = talent.isRecentlyActive === true - const openToWork = talent.openToWork - const profileUrl = `${EnvironmentConfig.USER_PROFILE_URL}/${encodeURIComponent(talent.handle)}` - const displayHandle = String(talent.handle || '') - .trim() - const matchedSkillLabel = uniqueSkills.length === 1 ? 'matched skill' : 'matched skills' - - return ( -
-
-
-
- - {isVerifiedProfile && ( - - - - )} -
-
-
- {displayHandle} - {showSkillMatch && ( - - {`${talent.matchIndex}% Match`} - - )} -
-

{talent.name}

-
- - {talent.location} -
-
- - {isActive ? 'Active' : 'Inactive'} - - {openToWork !== undefined && ( - - {openToWork ? ( - - ) : ( - - )} - {openToWork ? 'Available' : 'Unavailable'} - - )} -
-
-
-
-
- {showSkillMatch && ( -
- - {`${uniqueSkills.length} ${matchedSkillLabel}`} - - {uniqueSkills.length > 0 && ( - - - - )} -
- )} - - Experience Match - - -
-
- ) -} - -export default TalentResultCard diff --git a/src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/index.ts b/src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/index.ts deleted file mode 100644 index 4dd223e55..000000000 --- a/src/apps/customer-portal/src/pages/talent-search/components/TalentResultCard/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as TalentResultCard } from './TalentResultCard' diff --git a/src/apps/customer-portal/src/pages/talent-search/talent-search.routes.tsx b/src/apps/customer-portal/src/pages/talent-search/talent-search.routes.tsx deleted file mode 100644 index c09998492..000000000 --- a/src/apps/customer-portal/src/pages/talent-search/talent-search.routes.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { getRoutesContainer, lazyLoad, LazyLoadedComponent } from '~/libs/core' - -import { talentSearchRouteId } from '../../config/routes.config' - -const TalentSearchPage: LazyLoadedComponent = lazyLoad( - () => import('./TalentSearchPage'), - 'TalentSearchPage', -) - -export const talentSearchChildRoutes = [ - { - authRequired: true, - element: , - id: 'talent-search-page', - route: '', - }, -] - -export const customerPortalTalentSearchRoutes = [ - { - children: [...talentSearchChildRoutes], - element: getRoutesContainer(talentSearchChildRoutes), - id: talentSearchRouteId, - route: talentSearchRouteId, - }, -] diff --git a/src/apps/talent-search/README.md b/src/apps/talent-search/README.md deleted file mode 100644 index e801afbbf..000000000 --- a/src/apps/talent-search/README.md +++ /dev/null @@ -1,6 +0,0 @@ -### Talent search - -This is an internal for finding members based on skills and other search facets. The main APIs used include: - -* Member API -* Match Engine API \ No newline at end of file diff --git a/src/apps/talent-search/index.ts b/src/apps/talent-search/index.ts deleted file mode 100644 index 6f39cd49b..000000000 --- a/src/apps/talent-search/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src' diff --git a/src/apps/talent-search/src/TalentSearchApp.tsx b/src/apps/talent-search/src/TalentSearchApp.tsx deleted file mode 100644 index a4f33350e..000000000 --- a/src/apps/talent-search/src/TalentSearchApp.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { FC, useContext } from 'react' -import { Outlet, Routes } from 'react-router-dom' - -import { routerContext, RouterContextData } from '~/libs/core' -import { SharedSwrConfig } from '~/libs/shared' - -import { toolTitle } from './talent-search.routes' - -const TalentSearchApp: FC<{}> = () => { - - const { getChildRoutes }: RouterContextData = useContext(routerContext) - - return ( - - - - {getChildRoutes(toolTitle)} - - - ) -} - -export default TalentSearchApp diff --git a/src/apps/talent-search/src/assets/background-m.webp b/src/apps/talent-search/src/assets/background-m.webp deleted file mode 100644 index 25b669c60611352b547f0d2aa9e25303847fcee3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19330 zcmeIaQ;;TI*Dd-yW!tuG+qP}n=(1g1wyiE#m)&I>UAA3a+Wmgt#lQE7bGhT(oHrvf z*I0ASm~&-DM&!sbSEh=zgoKYQ0H7%@qNJh3r3C{30EqtX9zZ|=C_q|NR0#(6Zx?_I zV`pmb3c>^c*gLqns7Q(sX=&>aK`sKI0Pp}f015!Y*wocYUR6TvAMbyB|7riv?P(bR z{Kv9D|NlPze|rBvdT?g;4rTxV5bdws#?0K+^e+zli>=J;&Hk~U{DVE+oZJ8a5T(Bu z&E@X||Kh)605p^TVy}O&<$rPPe|*$bMF08}002;MmjA&pHUD6f|6r8%f3T^Qi#-5< zGxXP=%+$%{Z+x78auNS`4u25v6++t0Bwl? z0M@ptyNkzvM8fDOP7;1F;QxB)x? z-he~Q!r<+Z(y-tSzr}ltzbi73t+ooH(;OO@Zfmh zwBS78(%@R)mf)V?;oxcDW#BE~!{AHchu}{T5D*v;ln|T{QV`k@HW0oLu@Jcs^$>j! za}fIwkC2d%*pRf4{E&)}#*l81;gFe-HITiK^N@#-FHo>hgitI{5>Pr&4p6~RX;4*A zJy7#d$53z3NYLcaJkW~JX3*Zy3D70bKcQ!!524>+kYOlc_+ivwY+!<5GGOXphG8~g z?qOkJNnp8Qm0_)617R~@8(@FI?!vypp}^6=iNfi@xx>Z7mBaPHt-;;H!^2a+3&HEa zyTQl9SHcg%Z^OSJpdv6JNF$gb1R!K1v?9zSTp>auk|PQu8X)>0rXe;V&LCbOK_gKh zi6R*zeM8Dd>OfjSdPGJ=W=2*-wnvUdu0kF|K0$#*p+FHwF+&MODMlGY*+&IMB}ElQ zHAM|UEkPYdJw$^*qePQNvq6hNt3{heyFo`mXG7OO_d?G`??&Ij0L7rdkjAjXNWf^s zSjPB+iH|9aX^t6%S%*1~`HY2&C4^;;6^&JowS@Htn+RJR+ZHM;XTx zCl6;B=K>cEmlxL*HyXDQcO4G|j|NW#&lj&4ZxZhjp8#JH-w8hxe-QtI0E0k?z=j}& zpoid;5S37X(3&uXu$SB7>`(r*qykLc$)Z)gpx#^B$%Y0WQ!Dr zl#A4YG=+44^oER(OrFe-teR|{9GaYq+=@J%e3bl|f|5d$B7≶)D{5Qkv3-vYK*} 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 diff --git a/src/apps/talent-search/src/assets/background.jpg b/src/apps/talent-search/src/assets/background.jpg deleted file mode 100644 index 8ee73d550233eb554b66346e8926627c846d02c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28106 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

c*$BQ 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 diff --git a/src/apps/talent-search/src/assets/background.webp b/src/apps/talent-search/src/assets/background.webp deleted file mode 100644 index fb2165c64150d53931631e55a1ed854b5abf9930..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19104 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 - - diff --git a/src/apps/talent-search/src/components/popular-skills/PopularSkills.module.scss b/src/apps/talent-search/src/components/popular-skills/PopularSkills.module.scss deleted file mode 100644 index 7079a4aad..000000000 --- a/src/apps/talent-search/src/components/popular-skills/PopularSkills.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.wrap { - margin-top: $sp-4; - margin-left: auto; - margin-right: auto; - text-align: center; - - :global(.body-medium-bold) { - margin-bottom: $sp-4; - } - - @include ltemd { - margin-top: $sp-8; - } -} - -.wrapTitle { - font-size: 20px; - line-height: 26px; - font-family: $font-roboto; - font-weight: $font-weight-bold; - margin-bottom: $sp-4; -} - -.pills { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; - gap: $sp-2; - max-width: 680px; -} diff --git a/src/apps/talent-search/src/components/popular-skills/PopularSkills.tsx b/src/apps/talent-search/src/components/popular-skills/PopularSkills.tsx deleted file mode 100644 index c7df349bb..000000000 --- a/src/apps/talent-search/src/components/popular-skills/PopularSkills.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { FC, useCallback } from 'react' - -import { SkillPill } from '~/libs/shared' -import { SearchUserSkill } from '~/libs/core' - -import { SKILL_SEARCH_LIMIT } from '../../config' - -import styles from './PopularSkills.module.scss' - -// TODO: Make this configurable, or read from a service. We need to discuss -// how we want to handle this. -// TODO: update this with the real list of popular skills -const popularSkills: SearchUserSkill[] = [ - { - id: '63bb7cfc-b0d4-4584-820a-18c503b4b0fe', - name: 'Java', - }, - { - id: '0d86f8cb-e509-4ca8-b9f8-e65d909cc6eb', - name: 'MySQL', - }, - { - id: '32899253-5989-4c33-9948-cad29c9e0ab0', - name: 'Node.js', - }, - { - id: '9c42c728-47ff-4b20-916c-560739bad1ee', - name: 'Cascading Style Sheets (CSS)', - }, - { - id: '16ee1403-8e73-497d-a766-623eefd3c806', - name: 'JavaScript', - }, - { - id: '99e5fc45-5fc0-4794-a578-f42dfabcbf74', - name: 'Machine Learning', - }, - { - id: 'a0da6acf-2cf8-48f0-ba4a-30d18bc75052', - name: 'Unit Testing', - }, - { - id: '7e8641e5-e5c1-4ab6-a8f4-1fd6a8686dbe', - name: 'Angular', - }, - { - id: 'f0597e53-9a6d-40d6-8639-4d5a9ead190f', - name: '.NET Framework', - }, - { - id: 'fcbac194-35ab-4a31-aa7c-a2867fff9c4b', - name: 'Python', - }, - { - id: 'adf9d7b9-d639-4a73-8772-673b3d4f41b0', - name: 'Android', - }, - { - id: '130323ce-7d88-4141-9e2b-904994f026a1', - name: 'Figma (Design Software)', - }, - { - id: '9eaf6049-402a-481c-ac82-87a0826128c7', - name: 'Microsoft Azure', - }, - { - id: 'ced0b36c-6057-48e1-a263-2588fb91296b', - name: 'Adobe Illustrator', - }, - { - id: 'be85b096-b841-45b4-a5cb-1d3ee7ce1126', - name: 'Docker', - }, - { - id: '4458454c-9a97-4332-a545-6546e240dab6', - name: 'React.js', - }, -] - -interface PopularSkillsProps { - onChange: (skills: SearchUserSkill[]) => void - selectedSkills: SearchUserSkill[] -} - -const PopularSkills: FC = props => { - - const toggleSkill = useCallback((skill: SearchUserSkill) => { - let newFilter: Array = [] - let deleted: boolean = false - - // Either delete the value from the list, if we're toggling one that's already in the list - // Or add the new item to the list - props.selectedSkills.forEach(filterSkill => { - if (filterSkill.id === skill.id) { - deleted = true - } else { - newFilter.push(filterSkill) - } - }) - if (deleted === false && props.selectedSkills.length >= SKILL_SEARCH_LIMIT) { - return - } - - if (deleted === false) { - newFilter = props.selectedSkills.concat(skill) - } - - props.onChange.call(undefined, newFilter) - }, [props.onChange, props.selectedSkills]) - - function isSelected(skill: SearchUserSkill): boolean { - return !!props.selectedSkills.find(s => s.id === skill.id) - } - - return ( -

- ) -} - -export default PopularSkills diff --git a/src/apps/talent-search/src/components/popular-skills/index.ts b/src/apps/talent-search/src/components/popular-skills/index.ts deleted file mode 100644 index 497b5f7d3..000000000 --- a/src/apps/talent-search/src/components/popular-skills/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as PopularSkills } from './PopularSkills' diff --git a/src/apps/talent-search/src/components/profile-match/ProfileMatch.module.scss b/src/apps/talent-search/src/components/profile-match/ProfileMatch.module.scss deleted file mode 100644 index 8360b454f..000000000 --- a/src/apps/talent-search/src/components/profile-match/ProfileMatch.module.scss +++ /dev/null @@ -1,52 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.wrap { - display: flex; - align-items: center; - justify-content: center; - color: $tc-white; - font-family: $font-roboto; - flex-direction: column; - - border-radius: 100px; - padding-bottom: 2px; - width: 100%; - aspect-ratio: 1 / 1; - - background: rgb(130,234,207); - background: linear-gradient( - 180deg, - $turq-50 0%, - $turq-50 45%, - $turq-100 50%, - $turq-100 67%, - $turq-120 70%, - $turq-140 80%, - $turq-180 90% - ); - background-size: 100% 3000px; - background-position: 0 0; - - font-size: 16px; - line-height: 26px; - font-weight: $font-weight-bold; - - &:global(.dark) { - color: $black-100; - } - - strong { - font-size: 23px; - line-height: 27px; - font-weight: 900; - } - - @include ltemd { - font-size: 10px; - line-height: 14px; - strong { - font-size: 12px; - line-height: 14px; - } - } -} diff --git a/src/apps/talent-search/src/components/profile-match/ProfileMatch.tsx b/src/apps/talent-search/src/components/profile-match/ProfileMatch.tsx deleted file mode 100644 index c7a7c36c2..000000000 --- a/src/apps/talent-search/src/components/profile-match/ProfileMatch.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { FC } from 'react' -import classNames from 'classnames' - -import styles from './ProfileMatch.module.scss' - -interface ProfileMatchProps { - className?: string - percent?: number -} - -const ProfileMatch: FC = props => { - const value = Math.round((props.percent ?? 0) * 100) - - return ( -
- - {value} - % - - Match -
- ) -} - -export default ProfileMatch diff --git a/src/apps/talent-search/src/components/profile-match/index.ts b/src/apps/talent-search/src/components/profile-match/index.ts deleted file mode 100644 index 6508c84e9..000000000 --- a/src/apps/talent-search/src/components/profile-match/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ProfileMatch } from './ProfileMatch' diff --git a/src/apps/talent-search/src/components/profile-skills-match/ProfileSkillsMatch.module.scss b/src/apps/talent-search/src/components/profile-skills-match/ProfileSkillsMatch.module.scss deleted file mode 100644 index d24a1726f..000000000 --- a/src/apps/talent-search/src/components/profile-skills-match/ProfileSkillsMatch.module.scss +++ /dev/null @@ -1,98 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.wrap { - width: 100%; - margin-bottom: $sp-6; - - @include ltemd { - margin-bottom: $sp-2; - } -} - -.highlightWrap { - - display: flex; - align-items: center; - gap: $sp-4; - padding: $sp-6; - - border-radius: $sp-2; - border: 2px solid $turq-100; - background: $turq-15; - - color: #073D2E; - - @include ltemd { - flex-direction: column; - padding: $sp-4 $sp-2; - } -} - -.matchPerc { - width: 128px; - aspect-ratio: 1 / 1; - border-radius: 50%; - background: $turq-75; - color: #073D2E; - flex: 0 0 auto; - - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - - strong { - font-weight: 900; - font-size: 32px; - line-height: 38px; - } - - span { - font-weight: 900; - font-size: 20px; - line-height: 24px; - } -} - -.infoWrap { - display: flex; - flex-direction: column; - gap: $sp-1; - @include ltemd { - gap: $sp-2; - } -} - -.skillsList { - display: flex; - align-items: center; - gap: $sp-2; - - flex-wrap: wrap; - @include ltemd { - gap: $sp-1; - } -} - -.missingSkills { - margin-top: $sp-1; - > span:last-child { - color: $turq-180; - margin-left: $sp-1; - } -} - -.additionalSkills { - margin-top: $sp-6; - - .skillsList { - margin-top: $sp-6; - } - - @include ltemd { - .skillsList { - margin-top: $sp-3; - } - } -} diff --git a/src/apps/talent-search/src/components/profile-skills-match/ProfileSkillsMatch.tsx b/src/apps/talent-search/src/components/profile-skills-match/ProfileSkillsMatch.tsx deleted file mode 100644 index 642b099da..000000000 --- a/src/apps/talent-search/src/components/profile-skills-match/ProfileSkillsMatch.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { FC } from 'react' -import classNames from 'classnames' - -import { UserSkill } from '~/libs/core' -import { isSkillVerified, SkillPill } from '~/libs/shared' - -import { useIsMatchingSkill } from '../../lib/utils' - -import styles from './ProfileSkillsMatch.module.scss' - -interface ProfileSkillsMatchProps { - matchValue: number - profileSkills: Pick[] - queriedSkills: UserSkill[] -} - -const ProfileSkillsMatch: FC = props => { - const isMatchingSkill = useIsMatchingSkill(props.queriedSkills) - const matchedSkills = props.profileSkills.filter(isMatchingSkill) - const provenMatched = matchedSkills.filter(isSkillVerified) - const selfSkillmatched = matchedSkills.filter(s => !isSkillVerified(s)) - const missingSkills = props.queriedSkills.filter(qs => !matchedSkills.find(ms => ms.id === qs.id)) - - return ( -
-
-
- - {Math.round(props.matchValue * 100)} - % - - Match -
-
- {provenMatched.length > 0 && ( - <> -
- {provenMatched.length} - {` matched proven skill${provenMatched.length > 1 ? 's' : ''}`} -
-
- {provenMatched.map(skill => ( - - ))} -
- - )} - {selfSkillmatched.length > 0 && ( - <> -
- {selfSkillmatched.length} - {` matched self proclaimed skill${selfSkillmatched.length > 1 ? 's' : ''}`} -
-
- {selfSkillmatched.map(skill => ( - - ))} -
- - )} - {missingSkills.length > 0 && ( -
- Missing skills: - - { - missingSkills - .map(s => s.name) - .join(', ') - } - -
- )} -
-
-
- ) -} - -export default ProfileSkillsMatch diff --git a/src/apps/talent-search/src/components/profile-skills-match/index.ts b/src/apps/talent-search/src/components/profile-skills-match/index.ts deleted file mode 100644 index 5f03a8f70..000000000 --- a/src/apps/talent-search/src/components/profile-skills-match/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ProfileSkillsMatch } from './ProfileSkillsMatch' diff --git a/src/apps/talent-search/src/components/search-input/SearchInput.module.scss b/src/apps/talent-search/src/components/search-input/SearchInput.module.scss deleted file mode 100644 index 880ad8bcc..000000000 --- a/src/apps/talent-search/src/components/search-input/SearchInput.module.scss +++ /dev/null @@ -1,44 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.searchIcon{ - margin-right: 10px; - - position: relative; - z-index: 1; - - cursor: pointer; - color: $black-40; - transition: 0.15s ease-in-out; - - &:hover { - color: $black-100; - } - - &.disabled { - pointer-events: none; - opacity: 0.4; - } - - &:before { - content: ""; - display: block; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: $sp-12; - height: $sp-12; - } - - svg { - width: $sp-6; - height: $sp-6; - stroke: currentColor; - color: inherit; - } -} - -.maxLimit { - font-size: 14px; - color: $tc-white; -} diff --git a/src/apps/talent-search/src/components/search-input/SearchInput.tsx b/src/apps/talent-search/src/components/search-input/SearchInput.tsx deleted file mode 100644 index d8f17c4e2..000000000 --- a/src/apps/talent-search/src/components/search-input/SearchInput.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { FC, MouseEvent, Ref, useMemo } from 'react' -import classNames from 'classnames' - -import { IconOutline, InputMultiselectOption } from '~/libs/ui' -import { InputSkillSelector } from '~/libs/shared' -import { UserSkill } from '~/libs/core' - -import { - SKILL_SEARCH_LIMIT, - SKILL_SEARCH_MINIMUM, -} from '../../config' - -import styles from './SearchInput.module.scss' - -interface SearchInputProps { - className?: string - readonly autoFocus?: boolean - onChange: (skills: Pick[]) => void - skills: UserSkill[] - onSearch?: () => void - inputRef?: Ref -} - -const SearchInput: FC = props => { - const skills = useMemo(() => props.skills.map(s => ({ - category: s.category, - id: s.id, - levels: [], - name: s.name, - })), [props.skills]) - const canSearch = skills.length >= SKILL_SEARCH_MINIMUM - - function onChange(ev: any): void { - const options = (ev.target.value as unknown) as InputMultiselectOption[] - props.onChange(options.map(v => ({ - id: v.value, - name: v.label as string, - }))) - } - - function handleSearchSubmit(): void { - if (!canSearch) { - return - } - - props.onSearch?.() - } - - function handleSearchClick(ev: MouseEvent): void { - ev.preventDefault() - ev.stopPropagation() - - handleSearchSubmit() - } - - const searchIcon = ( -
- -
- ) - - return ( -
- - {skills.length > 0 && skills.length < SKILL_SEARCH_MINIMUM && ( -
- {`Please select at least ${SKILL_SEARCH_MINIMUM} skills to search`} -
- )} - {skills.length >= SKILL_SEARCH_LIMIT && ( -
- {`You can only search up to ${SKILL_SEARCH_LIMIT} skills at one time`} -
- )} -
- ) -} - -export default SearchInput diff --git a/src/apps/talent-search/src/components/search-input/index.ts b/src/apps/talent-search/src/components/search-input/index.ts deleted file mode 100644 index 73dcba1b6..000000000 --- a/src/apps/talent-search/src/components/search-input/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as SearchInput } from './SearchInput' diff --git a/src/apps/talent-search/src/components/talent-card/TalentCard.module.scss b/src/apps/talent-search/src/components/talent-card/TalentCard.module.scss deleted file mode 100644 index 18277632e..000000000 --- a/src/apps/talent-search/src/components/talent-card/TalentCard.module.scss +++ /dev/null @@ -1,171 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.wrap { - padding: $sp-6; - border-radius: $sp-4; - - background: $tc-white; - - font-family: $font-barlow; - color: $black-100; - - @include ltelg { - width: 100%; - } - - @include ltemd { - padding: $sp-3; - } -} - -.topWrap { - display: flex; - gap: $sp-6; - @include ltemd { - gap: $sp-3; - } -} - -.profilePic { - width: 188px; - max-width: 188px; - - @media (max-width: 1140px) { - width: 120px; - max-width: 120px; - } - - @include ltemd { - width: 94px; - max-width: 94px; - } -} - -.detailsContainer { - flex: 1 1 auto; - - padding: $sp-5 0 $sp-45; - display: flex; - gap: $sp-4; - - @include ltemd { - padding: 0; - gap: $sp-3; - } -} - -.talentInfo { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: $sp-3; - margin-bottom: $sp-45; - flex: 1 1 auto; - - @include ltemd { - gap: $sp-1; - margin-bottom: $sp-2; - } - - &Name { - font-size: 30px; - line-height: 32px; - color: $black-100; - - @include ltemd { - font-size: 14px; - line-height: 20px; - font-weight: 500; - } - } - - &Handle { - color: $black-60; - > span { - display: block; - } - } - - &Location { - color: $black-100; - - display: flex; - align-items: center; - gap: $sp-2; - - @include ltemd { - margin-right: -58px; - } - } - -} - -.profileMatch { - width: 83px; - flex: 0 0 auto; - - @include ltemd { - width: 46px; - } -} - -.skillsContainer { - margin-top: $sp-5; - border-top: 1px solid $black-10; - padding-top: $sp-5; - display: flex; - flex-direction: column; - gap: $sp-2; - position: relative; - - &Title { - position: absolute; - top: 0; - left: 50%; - background: $tc-white; - transform: translate(-50%, -50%); - padding: $sp-2 $sp-4; - color: $black-60; - white-space: nowrap; - } - - &:global(.overline) { - text-transform: uppercase; - } - - @include ltemd { - padding-top: $sp-4; - margin-top: $sp-4; - } -} - -.unmatchedSkills { - display: flex; - - color: $black-100; - padding: 7px $sp-3; - font-size: 14px; - line-height: 16px; - - border: 1px solid $black-20; - border-radius: $sp-6; - - order: 999; -} - -.skillsWrap { - display: flex; - align-items: center; - gap: $sp-1; - flex-wrap: wrap; - position: relative; - - &:global(.init) { - max-height: 116px; - overflow: hidden; - - .unmatchedSkills { - order: -1; - } - } -} diff --git a/src/apps/talent-search/src/components/talent-card/TalentCard.tsx b/src/apps/talent-search/src/components/talent-card/TalentCard.tsx deleted file mode 100644 index d1ccb3067..000000000 --- a/src/apps/talent-search/src/components/talent-card/TalentCard.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { FC, useEffect, useMemo, useRef } from 'react' -import { Link } from 'react-router-dom' -import { orderBy } from 'lodash' -import classNames from 'classnames' -import codes from 'country-calling-code' - -import { IconSolid } from '~/libs/ui' -import { isSkillVerified, ProfilePicture, SkillPill } from '~/libs/shared' -import { NamesAndHandleAppearance, UserSkill } from '~/libs/core' - -import { ProfileMatch } from '../profile-match' -import { Member } from '../../lib/models' -import { TALENT_SEARCH_PATHS } from '../../talent-search.routes' -import { useIsMatchingSkill } from '../../lib/utils' - -import styles from './TalentCard.module.scss' - -const getCountry = (countryCode: string): string => ( - codes.find(c => c.isoCode3 === countryCode || c.isoCode2 === countryCode)?.country ?? countryCode ?? '' -) - -const getAddrString = (city: string, country: string): string => ( - [city, country].filter(Boolean) - .join(', ') -) - -function isOverflow(el: HTMLElement): boolean { - const parentHeight = el.parentElement?.offsetHeight ?? 0 - const bottom = el.offsetTop + el.offsetHeight - return bottom > parentHeight -} - -interface TalentCardProps { - queriedSkills: UserSkill[] - member: Member - match?: number -} - -const TalentCard: FC = props => { - const restLabelRef = useRef(null) - const skillsWrapRef = useRef(null) - const talentRoute = `${TALENT_SEARCH_PATHS.talent}/${props.member.handle}` - const isMatchingSkill = useIsMatchingSkill(props.queriedSkills) - const address = props.member.addresses?.[0] ?? {} - - const matchedSkills = useMemo(() => ( - orderBy( - props.member.skills, - [isSkillVerified, a => a.name], - ['desc', 'asc'], - ) - .filter(isMatchingSkill) - ), [isMatchingSkill, props.member.skills]) - - const matchState = useMemo(() => ({ - matchValue: props.match, - queriedSkills: props.queriedSkills, - }), [props.match, props.queriedSkills]) - - // after initial render, check and limit to 3 rows of skills - useEffect(() => { - if (!skillsWrapRef.current) { - return - } - - // check if there are more than 3 rows of skills displayed initially, and hide them - const skillPillEls = [].slice.call(skillsWrapRef.current.childNodes) - const hiddenEls: HTMLElement[] = skillPillEls.filter(isOverflow) - - if (!hiddenEls.length && restLabelRef.current?.innerText.match(/^\+0/)) { - restLabelRef.current.style.display = 'none' - return - } - - if (hiddenEls.length > 1) { - const firstHidden = skillPillEls[skillPillEls.findIndex(s => s === hiddenEls[0]) - 1] - hiddenEls.push(firstHidden) - } - - hiddenEls.forEach(c => { c.style.display = 'none' }) - - // remove css height limit from the skillsWrap el - skillsWrapRef.current.classList.toggle('init', false) - - // if there are hidden skill pills, show the "+N more matched skills" pill - if (hiddenEls.length && restLabelRef.current) { - restLabelRef.current.innerText = `+${hiddenEls.length} more matched skill${hiddenEls.length > 1 ? 's' : ''}` - } - }, [matchedSkills]) - - return ( - -
- -
-
- {props.member.namesAndHandleAppearance !== NamesAndHandleAppearance.handleOnly && ( -
- {props.member.firstName} - {' '} - {props.member.lastName?.slice(0, 1) || ''} -
- )} - {props.member.namesAndHandleAppearance !== NamesAndHandleAppearance.nameOnly && ( -
- - {props.member.handle} - -
- )} - {(!!props.member.addresses?.length || !!props.member.homeCountryCode) && ( -
- - - {getAddrString(address.city, getCountry(props.member.homeCountryCode))} - -
- )} -
-
- -
-
-
-
-
- {`${matchedSkills.length} Matched skills`} -
-
-
+0 more matched skill
- {matchedSkills.length > 0 && matchedSkills.map(skill => ( - - ))} -
-
- - ) -} - -export default TalentCard diff --git a/src/apps/talent-search/src/components/talent-card/index.ts b/src/apps/talent-search/src/components/talent-card/index.ts deleted file mode 100644 index 2982b7c8d..000000000 --- a/src/apps/talent-search/src/components/talent-card/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as TalentCard } from './TalentCard' diff --git a/src/apps/talent-search/src/config/constants.ts b/src/apps/talent-search/src/config/constants.ts deleted file mode 100644 index b2716a613..000000000 --- a/src/apps/talent-search/src/config/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const SKILL_SEARCH_LIMIT = 7 -export const SKILL_SEARCH_MINIMUM = 2 diff --git a/src/apps/talent-search/src/config/index.ts b/src/apps/talent-search/src/config/index.ts deleted file mode 100644 index f87cf0102..000000000 --- a/src/apps/talent-search/src/config/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './constants' diff --git a/src/apps/talent-search/src/index.ts b/src/apps/talent-search/src/index.ts deleted file mode 100644 index 19db451f9..000000000 --- a/src/apps/talent-search/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { talentSearchRoutes, TALENT_SEARCH_PATHS } from './talent-search.routes' diff --git a/src/apps/talent-search/src/lib/models/Member.ts b/src/apps/talent-search/src/lib/models/Member.ts deleted file mode 100644 index b29e61658..000000000 --- a/src/apps/talent-search/src/lib/models/Member.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NamesAndHandleAppearance, UserSkill } from '~/libs/core' - -import MemberAddress from './MemberAddress' -import MemberMaxRating from './MemberMaxRating' -import MemberStats from './MemberStats' - -export default interface Member { - addresses: MemberAddress[]; - accountAge: number; - competitionCountryCode: string; - country: string; - createdAt: number; - description: string; - email: string; - skills: Array; - firstName: string; - handle: string; - homeCountryCode: string; - lastName: string; - namesAndHandleAppearance: NamesAndHandleAppearance - maxRating: MemberMaxRating; - numberOfChallengesPlaced: number; - numberOfChallengesWon: number; - photoURL: string; - skillScore: number; - stats: Array; - status: string; - userId: number; - verified: string; -} diff --git a/src/apps/talent-search/src/lib/models/MemberAddress.ts b/src/apps/talent-search/src/lib/models/MemberAddress.ts deleted file mode 100644 index 2137a2523..000000000 --- a/src/apps/talent-search/src/lib/models/MemberAddress.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default interface MemberAddress { - streetAddr1: string - streetAddr2: string - city: string - stateCode: string - zip: string -} diff --git a/src/apps/talent-search/src/lib/models/MemberDisplayName.ts b/src/apps/talent-search/src/lib/models/MemberDisplayName.ts deleted file mode 100644 index 4c9469275..000000000 --- a/src/apps/talent-search/src/lib/models/MemberDisplayName.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum MemberDisplayName { - both = 'namesAndHandle', - handleOnly = 'handleOnly', - nameOnly = 'namesOnly', -} diff --git a/src/apps/talent-search/src/lib/models/MemberMaxRating.ts b/src/apps/talent-search/src/lib/models/MemberMaxRating.ts deleted file mode 100644 index 69e70bb4a..000000000 --- a/src/apps/talent-search/src/lib/models/MemberMaxRating.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface MemberMaxRating { - rating:number; - track:string; - subTrack:string; - ratingColor:string; -} diff --git a/src/apps/talent-search/src/lib/models/MemberStats.ts b/src/apps/talent-search/src/lib/models/MemberStats.ts deleted file mode 100644 index 474bc9988..000000000 --- a/src/apps/talent-search/src/lib/models/MemberStats.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default interface MemberStats { - challenges:number; - wins:number; - mostRecentEventDate:number; - mostRecentEventName:string; - mostRecentSubmission:number; -} diff --git a/src/apps/talent-search/src/lib/models/index.ts b/src/apps/talent-search/src/lib/models/index.ts deleted file mode 100644 index 7ac93e125..000000000 --- a/src/apps/talent-search/src/lib/models/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type { default as Member } from './Member' -export type { default as MemberMaxRating } from './MemberMaxRating' -export type { default as MemberStats } from './MemberStats' diff --git a/src/apps/talent-search/src/lib/services/index.ts b/src/apps/talent-search/src/lib/services/index.ts deleted file mode 100644 index cb5327857..000000000 --- a/src/apps/talent-search/src/lib/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './use-fetch-talent-matches' diff --git a/src/apps/talent-search/src/lib/services/use-fetch-talent-matches.spec.ts b/src/apps/talent-search/src/lib/services/use-fetch-talent-matches.spec.ts deleted file mode 100644 index 1f4f9f9a0..000000000 --- a/src/apps/talent-search/src/lib/services/use-fetch-talent-matches.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { SKILL_SEARCH_MINIMUM } from '../../config' - -import { - canSearchTalentMatches, - isTalentSearchLoading, -} from './use-fetch-talent-matches' - -jest.mock('~/config', () => ({ - EnvironmentConfig: { - API: { - V6: 'https://api.topcoder.com/v6', - }, - }, -}), { virtual: true }) - -jest.mock('~/libs/core', () => ({ - xhrGetPaginatedAsync: jest.fn(), -}), { virtual: true }) - -jest.mock('swr', () => jest.fn()) - -const createSkills = (count: number): Array<{ id: string; name: string }> => ( - Array.from({ length: count }, (_, index) => ({ - id: `skill-${index + 1}`, - name: `Skill ${index + 1}`, - })) -) - -describe('useFetchTalentMatches', () => { - it('requires at least the configured minimum number of skills to search', () => { - expect(canSearchTalentMatches(createSkills(SKILL_SEARCH_MINIMUM - 1) as any)) - .toBe(false) - expect(canSearchTalentMatches(createSkills(SKILL_SEARCH_MINIMUM) as any)) - .toBe(true) - }) - - it('shows loading only when search can run and no data or error exists', () => { - expect(isTalentSearchLoading(true, undefined, undefined)) - .toBe(true) - expect(isTalentSearchLoading(false, undefined, undefined)) - .toBe(false) - expect(isTalentSearchLoading(true, { data: [] } as any, undefined)) - .toBe(false) - }) - - it('exits loading state when the search request fails', () => { - expect(isTalentSearchLoading(true, undefined, new Error('request failed'))) - .toBe(false) - }) -}) diff --git a/src/apps/talent-search/src/lib/services/use-fetch-talent-matches.ts b/src/apps/talent-search/src/lib/services/use-fetch-talent-matches.ts deleted file mode 100644 index 5583abc85..000000000 --- a/src/apps/talent-search/src/lib/services/use-fetch-talent-matches.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { uniqBy } from 'lodash' -import { useCallback, useEffect, useMemo, useState } from 'react' -import useSWR, { SWRResponse } from 'swr' - -import type { PaginatedResponse, UserSkill } from '~/libs/core' -import { EnvironmentConfig } from '~/config' -import { xhrGetPaginatedAsync } from '~/libs/core' -import type Member from '@talentSearch/lib/models/Member' - -import { SKILL_SEARCH_MINIMUM } from '../../config' - -export interface TalentMatchesResponse { - error: boolean, - loading: boolean, - matches: Member[], - page: number, - ready: boolean, - total: number, - totalPages: number, -} - -export function canSearchTalentMatches(skills: ReadonlyArray): boolean { - return skills.length >= SKILL_SEARCH_MINIMUM -} - -export function isTalentSearchLoading( - canSearch: boolean, - data: PaginatedResponse | undefined, - error: unknown, -): boolean { - return canSearch && !error && !data?.data -} - -export function useFetchTalentMatches( - skills: ReadonlyArray, - page: number, - pageSize: number, -): TalentMatchesResponse { - const canSearch = canSearchTalentMatches(skills) - const searchParams = [ - ...skills.map(s => `id=${s.id}`), - 'sortBy=skillScore', - 'includeStats=false', - `page=${page}`, - `perPage=${pageSize}`, - ].join('&') - - const url = `${EnvironmentConfig.API.V6}/members/searchBySkills?${searchParams}` - - const { data, error }: SWRResponse, unknown> = useSWR( - url, - xhrGetPaginatedAsync, - { - isPaused: () => !canSearch, - refreshInterval: 0, - revalidateOnFocus: false, - }, - ) - - const matches = useMemo(() => data?.data ?? [], [data]) - - return { - error: !!error, - loading: isTalentSearchLoading(canSearch, data, error), - matches: matches ?? [], - page: data?.page ?? 0, - ready: !!data?.data, - total: data?.total ?? 0, - totalPages: data?.totalPages ?? 0, - } -} - -export interface InfiniteTalentMatchesResposne { - fetchNext: () => void - hasNext: boolean - matches: Member[] - page: number - loading: boolean - total: number -} - -export function useInfiniteTalentMatches( - skills: ReadonlyArray, - pageSize: number = 10, -): InfiniteTalentMatchesResposne { - const [matches, setMatches] = useState([] as Member[]) - const [page, setPage] = useState(1) - const matchResponse = useFetchTalentMatches(skills, page, pageSize) - - const fetchNext = useCallback(() => { - setPage(p => p + 1) - }, []) - - // clear matches when skills array is updated - useEffect(() => { - setMatches([]) - setPage(1) - }, [skills]) - - // when we have new matches, concatenate the response to the matches array - useEffect(() => { - setMatches(m => uniqBy([...m, ...matchResponse.matches], 'userId')) - }, [matchResponse.matches]) - - return { - fetchNext, - hasNext: matchResponse.page < matchResponse.totalPages, - loading: matchResponse.loading && skills.length > 0, - matches, - page, - total: matchResponse.total, - } -} diff --git a/src/apps/talent-search/src/lib/utils/index.ts b/src/apps/talent-search/src/lib/utils/index.ts deleted file mode 100644 index 68cf4184f..000000000 --- a/src/apps/talent-search/src/lib/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './skills.utils' diff --git a/src/apps/talent-search/src/lib/utils/search-query.tsx b/src/apps/talent-search/src/lib/utils/search-query.tsx deleted file mode 100644 index d55764fca..000000000 --- a/src/apps/talent-search/src/lib/utils/search-query.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' -import { useSearchParams } from 'react-router-dom' - -import { SearchUserSkill, UserSkill } from '~/libs/core' - -export const encodeUrlQuerySearch = (skills: SearchUserSkill[]): string => ( - skills - .map(s => `q=${encodeURIComponent(`${s.name}::${s.id}`)}`) - .join('&') -) - -export const parseUrlQuerySearch = (params: string[]): SearchUserSkill[] => ( - params.map(p => { - const [name, id] = p.split('::') - return { id, name } - }) -) - -export const useUrlQuerySearchParms = (paramName: string): [ - UserSkill[], - (s: SearchUserSkill[]) => void -] => { - const [params, updateParams] = useSearchParams() - - const [skills, setSkills] = useState([]) - - const handleUpdateSearch = useCallback((newSkills: SearchUserSkill[]) => { - const searchParams = encodeUrlQuerySearch(newSkills) - updateParams(`${searchParams}`) - }, [updateParams]) - - // update search input whenever the url data changes - useEffect(() => { - setSkills(parseUrlQuerySearch(params.getAll(paramName)) as UserSkill[]) - }, [params, paramName]) - - return [skills, handleUpdateSearch] -} diff --git a/src/apps/talent-search/src/lib/utils/skills.utils.tsx b/src/apps/talent-search/src/lib/utils/skills.utils.tsx deleted file mode 100644 index 8ad98a9c7..000000000 --- a/src/apps/talent-search/src/lib/utils/skills.utils.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useCallback } from 'react' - -import { UserSkill } from '~/libs/core' - -export type IsMatchingSkillFn = (skill: Pick) => boolean - -export const useIsMatchingSkill = (skills: UserSkill[]): IsMatchingSkillFn => { - const isMatchingSkill = useCallback((skill: Pick) => ( - !!skills.find(s => skill.id === s.id) - ), [skills]) - - return isMatchingSkill -} diff --git a/src/apps/talent-search/src/routes/search-page/SearchPage.module.scss b/src/apps/talent-search/src/routes/search-page/SearchPage.module.scss deleted file mode 100644 index 533fc2cba..000000000 --- a/src/apps/talent-search/src/routes/search-page/SearchPage.module.scss +++ /dev/null @@ -1,89 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.contentLayout { - background-image: url("../../assets/background.webp"); - background-size: cover; - background-repeat: no-repeat; - - @include ltemd { - background-image: url("../../assets/background-m.webp"); - } -} - -.contentLayout-inner { - padding: 86px $sp-12 $sp-12; - color: $tc-white; - @include ltemd { - padding: $sp-9 0 0; - } -} - -.searchHeader{ - text-align: center; -} - -.searchHeaderText{ - font-family: $font-roboto; - font-weight: 500; - font-size: 30px; - line-height: 32px; - letter-spacing: 0.5px; -} - -.subHeader{ - margin-top: 49px; - text-align: center; -} - -.subHeaderText{ - font-family: $font-barlow; - font-weight: 400; - font-size: 25px; - line-height: 30px; - color: $tc-white; - -} -.searchPrompt{ - font-family: $font-roboto; - font-weight: 700; - font-size: 16px; - line-height: 48px; - color: $tc-white; - display: block; - - @include ltemd { - line-height: 24px; - margin-bottom: $sp-2; - } -} - -.searchOptions{ - margin-left: auto; - margin-right: auto; - padding: 70px 32px 32px 32px; - max-width: 931px; - width: 100%; - - @include ltemd { - padding: 49px 0 0; - } -} - -.headerErrorWrap { - display: flex; - justify-content: center; -} - -.headerError { - display: flex; - align-items: center; - gap: $sp-3; - - padding: $sp-4 $sp-8; - border-radius: 10px; - background: $purple-140; - - @include ltemd { - padding: $sp-4; - } -} diff --git a/src/apps/talent-search/src/routes/search-page/SearchPage.tsx b/src/apps/talent-search/src/routes/search-page/SearchPage.tsx deleted file mode 100644 index d129fcc80..000000000 --- a/src/apps/talent-search/src/routes/search-page/SearchPage.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { FC, useRef, useState } from 'react' -import { useNavigate, useSearchParams } from 'react-router-dom' - -import type { UserSkill } from '~/libs/core' -import { ContentLayout, IconOutline } from '~/libs/ui' - -import { SearchInput } from '../../components/search-input' -import { PopularSkills } from '../../components/popular-skills' -import { TALENT_SEARCH_PATHS } from '../../talent-search.routes' -import { SKILL_SEARCH_MINIMUM } from '../../config' -import { encodeUrlQuerySearch } from '../../lib/utils/search-query' - -import styles from './SearchPage.module.scss' - -export const SearchPage: FC = () => { - const [params] = useSearchParams() - const isMissingProfileRoute = params.get('memberNotFound') !== null - - const searchInputRef = useRef() - const navigate = useNavigate() - const [skillsFilter, setSkillsFilter] = useState([]) - - function navigateToResults(): void { - if (skillsFilter.length < SKILL_SEARCH_MINIMUM) { - return - } - - const searchParams = encodeUrlQuerySearch(skillsFilter) - navigate(`${TALENT_SEARCH_PATHS.results}?${searchParams}`) - } - - function handleSelectSkillFilter(filter: Pick[]): void { - setSkillsFilter(filter as UserSkill[]) - searchInputRef.current?.focus() - } - - function renderHeader(): JSX.Element { - return isMissingProfileRoute ? ( - <> -
-
- - We were unable to locate that profile -
-
-
-
- You can also try finding members through our Talent Search: -
-
- - ) : ( - <> -
- Looking for a technology expert? -
-
-
- Search thousands of skills to match with our global experts. -
-
- - ) - } - - return ( - - {renderHeader()} -
- Search by skills - -
- -
- ) -} - -export default SearchPage diff --git a/src/apps/talent-search/src/routes/search-page/index.ts b/src/apps/talent-search/src/routes/search-page/index.ts deleted file mode 100644 index 983e5d359..000000000 --- a/src/apps/talent-search/src/routes/search-page/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as SearchPage } from './SearchPage' diff --git a/src/apps/talent-search/src/routes/search-results-page/SearchResultsPage.module.scss b/src/apps/talent-search/src/routes/search-results-page/SearchResultsPage.module.scss deleted file mode 100644 index 6364f7c45..000000000 --- a/src/apps/talent-search/src/routes/search-results-page/SearchResultsPage.module.scss +++ /dev/null @@ -1,127 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.headerWrap { - min-height: 156px; -} - -.headerContentLayout { - height: 100%; - background-image: url("../../assets/background.jpg"); - background-size: cover; - background-repeat: no-repeat; - position: relative; - z-index: 2; - - &Inner { - padding: 0 $sp-8; - } - - @include ltemd { - &Inner { - padding: 0; - } - } -} - -.summaryText { - margin: $sp-4 0 $sp-5; - text-align: center; - color: $black-60; - - display: flex; - align-items: center; - justify-content: center; - - > span { - display: block; - margin: 0 auto; - flex: 1 1 auto; - } - - :global(.highlighting) { - color: $black-100; - } - - @include ltemd { - flex-direction: column; - gap: $sp-2; - } -} - -.skillsPill { - flex: 0 0 auto; - - &:first-child { - opacity: 0; - visibility: hidden; - pointer-events: none; - } - - @include ltemd { - &:first-child { - display: none; - } - } -} - -.searchInput { - margin-top: $sp-10; - @include ltemd { - margin-top: $sp-4; - } -} - -.contentLayout { - background: $black-5; - padding: $sp-2; - position: relative; - z-index: 1; - - .contentLayout-inner { - padding-bottom: $sp-12; - } - - @include ltemd { - padding: $sp-2 0; - } -} - -.resultsWrap { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: $sp-9; - - @include ltelg { - display: flex; - flex-wrap: wrap; - } - @include ltemd { - gap: $sp-5; - } -} - -.moreBtn { - margin-top: $sp-8; - display: flex; - justify-content: center; -} - -.noResultsNormal { - font-family: 'Roboto'; - font-style: normal; - font-weight: 400; - font-size: 20px; - line-height: 23px; - text-align: center; -} - -.noResultsBold { - font-family: 'Roboto'; - font-style: normal; - font-weight: 700; - font-size: 20px; - line-height: 23px; - text-align: center; - margin-top: 20px; - margin-bottom: 40px; -} \ No newline at end of file diff --git a/src/apps/talent-search/src/routes/search-results-page/SearchResultsPage.tsx b/src/apps/talent-search/src/routes/search-results-page/SearchResultsPage.tsx deleted file mode 100644 index e96405d10..000000000 --- a/src/apps/talent-search/src/routes/search-results-page/SearchResultsPage.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { FC, useCallback, useEffect, useState } from 'react' -import classNames from 'classnames' - -import { EnvironmentConfig } from '~/config' -import { Button, ContentLayout, LinkButton, LoadingCircles } from '~/libs/ui' -import { HowSkillsWorkModal } from '~/libs/shared' - -import { TalentCard } from '../../components/talent-card' -import { SearchInput } from '../../components/search-input' -import { - InfiniteTalentMatchesResposne, - useInfiniteTalentMatches, -} from '../../lib/services' -import { useUrlQuerySearchParms } from '../../lib/utils/search-query' -import { SKILL_SEARCH_MINIMUM } from '../../config' - -import { getLetsTalkUrl } from './letsTalkUrl' -import styles from './SearchResultsPage.module.scss' - -const SearchResultsPage: FC = () => { - const [showSkillsModal, setShowSkillsModal] = useState(false) - - const [currentPage, setCurrentPage] = useState(1) - const itemsPerPage = 10 - - const [skills, setSkills] = useUrlQuerySearchParms('q') - const { - loading, - matches, - fetchNext, - hasNext, - total, - }: InfiniteTalentMatchesResposne = useInfiniteTalentMatches(skills) - const paginatedMatches = matches.slice(0, currentPage * itemsPerPage) - - useEffect(() => { - const handleScroll: () => void = () => { - const scrollY = window.scrollY - const visibleHeight = window.innerHeight - const fullHeight = document.body.scrollHeight - const footerElem = document.getElementById('footer-nav-el') - const footerHeight = (footerElem && footerElem.offsetHeight) || 650 - if (scrollY + visibleHeight >= fullHeight - (footerHeight + 100)) { - // Scroll near bottom - setCurrentPage(prev => { - const maxPages = Math.ceil(matches.length / itemsPerPage) - return prev < maxPages ? prev + 1 : prev - }) - } - } - - window.addEventListener('scroll', handleScroll) - return () => window.removeEventListener('scroll', handleScroll) - }, [matches]) - - const toggleSkillsModal = useCallback(() => setShowSkillsModal(s => !s), []) - - const skillsModalTriggerBtn = ( -
-
- ) - - return ( - <> -
- - - -
- -
- {loading ? ( - <> - Finding experts that match your search... - - ) : !skills.length ? ( - - Search thousands of skills to match with our global experts. - - ) : skills.length < SKILL_SEARCH_MINIMUM ? ( - - {`Please select at least ${SKILL_SEARCH_MINIMUM} skills to search`} - - ) : !total ? ( - -
- Topcoder is supported by amazing talent from all over the world. -
-
- Please contact us, so we can connect you to experts with these skills. -
-
- -
-
- ) : ( - <> - {skillsModalTriggerBtn} - - We found  - - {total} -  Experts - -  that match your search - - {skillsModalTriggerBtn} - - )} -
-
- {paginatedMatches.map(member => ( - - ))} -
- {loading && } - - {hasNext && ( -
- -
- )} - {showSkillsModal && ( - - )} -
- - ) -} - -export default SearchResultsPage diff --git a/src/apps/talent-search/src/routes/search-results-page/index.ts b/src/apps/talent-search/src/routes/search-results-page/index.ts deleted file mode 100644 index 2f36f2509..000000000 --- a/src/apps/talent-search/src/routes/search-results-page/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as SearchResultsPage } from './SearchResultsPage' diff --git a/src/apps/talent-search/src/routes/search-results-page/letsTalkUrl.spec.ts b/src/apps/talent-search/src/routes/search-results-page/letsTalkUrl.spec.ts deleted file mode 100644 index 76e6110a3..000000000 --- a/src/apps/talent-search/src/routes/search-results-page/letsTalkUrl.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { getLetsTalkUrl } from './letsTalkUrl' - -describe('getLetsTalkUrl', () => { - it('returns the lets-talk url for dev domain', () => { - expect(getLetsTalkUrl('https://www.topcoder-dev.com')) - .toBe('https://www.topcoder-dev.com/lets-talk') - }) - - it('returns the lets-talk url for prod domain', () => { - expect(getLetsTalkUrl('https://www.topcoder.com')) - .toBe('https://www.topcoder.com/lets-talk') - }) -}) diff --git a/src/apps/talent-search/src/routes/search-results-page/letsTalkUrl.ts b/src/apps/talent-search/src/routes/search-results-page/letsTalkUrl.ts deleted file mode 100644 index 381043919..000000000 --- a/src/apps/talent-search/src/routes/search-results-page/letsTalkUrl.ts +++ /dev/null @@ -1 +0,0 @@ -export const getLetsTalkUrl = (topcoderUrl: string): string => `${topcoderUrl}/lets-talk` diff --git a/src/apps/talent-search/src/routes/talent-page/TalentPage.module.scss b/src/apps/talent-search/src/routes/talent-page/TalentPage.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/apps/talent-search/src/routes/talent-page/TalentPage.tsx b/src/apps/talent-search/src/routes/talent-page/TalentPage.tsx deleted file mode 100644 index ac77bc9c8..000000000 --- a/src/apps/talent-search/src/routes/talent-page/TalentPage.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { FC, ReactNode } from 'react' -import { Location, useLocation } from 'react-router-dom' - -import { MemberProfileContext, MemberProfilePage } from '@profiles/member-profile' -import { UserSkill } from '~/libs/core' - -import { ProfileSkillsMatch } from '../../components/profile-skills-match' -import { getTalentStatsRoute } from '../../talent-search.routes' - -const TalentPage: FC = () => { - const { state }: Location = useLocation() - - function skillsRenderer(profileSkills: Pick[]): ReactNode { - return ( - - ) - } - - return ( - - - - ) -} - -export default TalentPage diff --git a/src/apps/talent-search/src/routes/talent-page/index.ts b/src/apps/talent-search/src/routes/talent-page/index.ts deleted file mode 100644 index a58ff9dfc..000000000 --- a/src/apps/talent-search/src/routes/talent-page/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as TalentPage } from './TalentPage' diff --git a/src/apps/talent-search/src/styles/main.vendor.scss b/src/apps/talent-search/src/styles/main.vendor.scss deleted file mode 100644 index be351bb49..000000000 --- a/src/apps/talent-search/src/styles/main.vendor.scss +++ /dev/null @@ -1,5 +0,0 @@ -// This file can import 3rd parties css/scss files globally -// without applying CSS Modules - -@import "~react-redux-toastr/src/styles/index"; -@import "~react-responsive-modal/styles"; diff --git a/src/apps/talent-search/src/talent-search.routes.tsx b/src/apps/talent-search/src/talent-search.routes.tsx deleted file mode 100644 index 021eaca94..000000000 --- a/src/apps/talent-search/src/talent-search.routes.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { AppSubdomain, EnvironmentConfig, ToolTitle } from '~/config' -import { lazyLoad, LazyLoadedComponent, PlatformRoute, UserRole } from '~/libs/core' - -import './styles/main.vendor.scss' - -const TalentSearchAppRoot: LazyLoadedComponent = lazyLoad(() => import('./TalentSearchApp')) -const SearchPage: LazyLoadedComponent = lazyLoad(() => import('./routes/search-page'), 'SearchPage') -const SearchResultsPage: LazyLoadedComponent = lazyLoad( - () => import('./routes/search-results-page'), - 'SearchResultsPage', -) -const TalentPage: LazyLoadedComponent = lazyLoad( - () => import('./routes/talent-page'), - 'TalentPage', -) -const MemberBadgesPage: LazyLoadedComponent = lazyLoad( - () => import('@profiles/member-badges'), - 'MemberBadgesPage', -) - -const isOnAppSubdomain = EnvironmentConfig.SUBDOMAIN === AppSubdomain.talentSearch -export const rootRoute: string = ( - isOnAppSubdomain ? '' : `/${AppSubdomain.talentSearch}` -) - -export const TALENT_SEARCH_PATHS = { - absoluteUrl: `//${AppSubdomain.talentSearch}.${EnvironmentConfig.TC_DOMAIN}`, - results: `${rootRoute}/results`, - root: rootRoute, - talent: `${rootRoute}/talent`, -} - -export const toolTitle: string = ToolTitle.talentSearch - -const isAdminRestricted = EnvironmentConfig.RESTRICT_TALENT_SEARCH - -export const getTalentRoute = (userHandle: string): string => ( - `${rootRoute}/talent/${userHandle.toLowerCase()}` -) - -export const getTalentStatsRoute = ( - userHandle: string, - track?: string, - subTrack?: string, -): string => ( - `${getTalentRoute(userHandle)}${track ? `/stats/${track}` : ''}${!(track && subTrack) ? '' : `/${subTrack}`}` -) - -export const talentSearchRoutes: ReadonlyArray = [ - { - authRequired: isAdminRestricted, - children: [ - { - element: , - route: '/', - }, - { - element: , - route: '/results', - }, - { - element: , - route: '/talent/:memberHandle', - }, - { - element: , - id: 'MemberProfilePageSub', - route: '/talent/:memberHandle/stats/*', - }, - { - element: , - id: 'MemberBadgesPage', - route: '/talent/:memberHandle/badges', - }, - ], - domain: AppSubdomain.talentSearch, - element: , - id: toolTitle, - navConfig: { - showSalesCta: true, - }, - rolesRequired: isAdminRestricted ? [ - UserRole.administrator, - ] : undefined, - route: rootRoute, - }, -] From 97d40e8e2af5b1e82b5da1b43304816764186db7 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Thu, 11 Jun 2026 17:45:14 +0530 Subject: [PATCH 4/5] Clean up talent-search unused code and linting errors --- src/apps/customer-portal/src/config/routes.config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/apps/customer-portal/src/config/routes.config.ts b/src/apps/customer-portal/src/config/routes.config.ts index a33c25ad3..6cf5ffb2a 100644 --- a/src/apps/customer-portal/src/config/routes.config.ts +++ b/src/apps/customer-portal/src/config/routes.config.ts @@ -7,3 +7,5 @@ export const rootRoute: string = EnvironmentConfig.SUBDOMAIN === AppSubdomain.customer ? '' : `/${AppSubdomain.customer}` + +export const profileCompletionRouteId = 'profile-completion' From 5e9c72a68e0f3b35d6fb26b9900ae4f016eb5e71 Mon Sep 17 00:00:00 2001 From: Harshit Chudasama Date: Fri, 12 Jun 2026 12:08:52 +0530 Subject: [PATCH 5/5] updated platform ui to remove talent-search --- .github/workflows/code_reviewer.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/code_reviewer.yml diff --git a/.github/workflows/code_reviewer.yml b/.github/workflows/code_reviewer.yml deleted file mode 100644 index 02f198a18..000000000 --- a/.github/workflows/code_reviewer.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: AI PR Reviewer - -on: - pull_request: - types: - - opened - - synchronize -permissions: - pull-requests: write -jobs: - tc-ai-pr-review: - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v3 - - - name: TC AI PR Reviewer - uses: topcoder-platform/tc-ai-pr-reviewer@master - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # The GITHUB_TOKEN is there by default so you just need to keep it like it is and not necessarily need to add it as secret as it will throw an error. [More Details](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret) - LAB45_API_KEY: ${{ secrets.LAB45_API_KEY }} - exclude: '**/*.json, **/*.md, **/*.jpg, **/*.png, **/*.jpeg, **/*.bmp, **/*.webp' # Optional: exclude patterns separated by commas