[resumes][refactor] use CountriesTypeahead instead

pull/519/head
Yangshun Tay 2 years ago
parent 79500b8a35
commit c0e9b6c138

@ -1,73 +0,0 @@
import type { ComponentProps } from 'react';
import { useMemo, useState } from 'react';
import type { TypeaheadOption } from '@tih/ui';
import { Typeahead } from '@tih/ui';
import { trpc } from '~/utils/trpc';
type BaseProps = Pick<
ComponentProps<typeof Typeahead>,
| 'disabled'
| 'errorMessage'
| 'isLabelHidden'
| 'placeholder'
| 'required'
| 'textSize'
>;
type Props = BaseProps &
Readonly<{
onSelect: (option: TypeaheadOption | null) => void;
selectedValues?: Set<string>;
value?: TypeaheadOption | null;
}>;
// TODO: Merge with CountriesTypeahead instead.
export default function ResumeLocationTypeahead({
onSelect,
selectedValues = new Set(),
value,
...props
}: Props) {
const [query, setQuery] = useState('');
const countries = trpc.useQuery([
'locations.countries.list',
{
name: query,
},
]);
const options = useMemo(() => {
const { data } = countries;
if (data == null) {
return [];
}
return (
data
// Client-side sorting by position of query string appearing
// in the country name since we can't do that in Prisma.
.sort((a, b) => a.name.indexOf(query) - b.name.indexOf(query))
.map(({ id, name }) => ({
id,
label: name,
value: id,
}))
.filter((option) => !selectedValues.has(option.value))
);
}, [countries, query, selectedValues]);
return (
<Typeahead
label="Location"
minQueryLength={2}
noResultsMessage="No location found"
nullable={true}
options={options}
value={value}
onQueryChange={setQuery}
onSelect={onSelect}
{...props}
/>
);
}

@ -17,11 +17,25 @@ type BaseProps = Pick<
type Props = BaseProps &
Readonly<{
excludedValues?: Set<string>;
label?: string;
onSelect: (option: TypeaheadOption | null) => void;
value?: TypeaheadOption | null;
}>;
function stringPositionComparator(a: string, b: string, query: string): number {
const normalizedQueryString = query.trim().toLocaleLowerCase();
const positionA = a.toLocaleLowerCase().indexOf(normalizedQueryString);
const positionB = b.toLocaleLowerCase().indexOf(normalizedQueryString);
return (
(positionA === -1 ? 9999 : positionA) -
(positionB === -1 ? 9999 : positionB)
);
}
export default function CountriesTypeahead({
excludedValues,
label = 'Country',
onSelect,
value,
...props
@ -38,23 +52,37 @@ export default function CountriesTypeahead({
return (
<Typeahead
label="Country"
minQueryLength={2}
label={label}
noResultsMessage="No countries found"
nullable={true}
options={(data ?? [])
// Client-side sorting by position of query string appearing
// in the country name since we can't do that in Prisma.
.sort(
(a, b) =>
a.name.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) -
b.name.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()),
)
.sort((a, b) => {
const normalizedQueryString = query.trim().toLocaleLowerCase();
if (
a.code.toLocaleLowerCase() === normalizedQueryString ||
b.code.toLocaleLowerCase() === normalizedQueryString
) {
return stringPositionComparator(
a.code,
b.code,
normalizedQueryString,
);
}
return stringPositionComparator(
a.name,
b.name,
normalizedQueryString,
);
})
.map(({ id, name }) => ({
id,
label: name,
value: id,
}))}
}))
.filter((option) => !excludedValues?.has(option.value))}
value={value}
onQueryChange={setQuery}
onSelect={onSelect}

@ -25,9 +25,9 @@ import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
import ResumeFilterPill from '~/components/resumes/browse/ResumeFilterPill';
import ResumeListItems from '~/components/resumes/browse/ResumeListItems';
import ResumeExperienceTypeahead from '~/components/resumes/shared/ResumeExperienceTypeahead';
import ResumeLocationTypeahead from '~/components/resumes/shared/ResumeLocationTypeahead';
import ResumeRoleTypeahead from '~/components/resumes/shared/ResumeRoleTypeahead';
import ResumeSignInButton from '~/components/resumes/shared/ResumeSignInButton';
import CountriesTypeahead from '~/components/shared/CountriesTypeahead';
import loginPageHref from '~/components/shared/loginPageHref';
import type { Filter, FilterId, Shortcut } from '~/utils/resumes/resumeFilters';
@ -344,12 +344,13 @@ export default function ResumeHomePage() {
);
case 'location':
return (
<ResumeLocationTypeahead
isLabelHidden={true}
placeholder="Select locations"
selectedValues={
<CountriesTypeahead
excludedValues={
new Set(userFilters[filterId].map(({ value }) => value))
}
isLabelHidden={true}
label="Location"
placeholder="Select countries"
onSelect={onSelect}
/>
);

@ -21,10 +21,10 @@ import {
} from '@tih/ui';
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
import ResumeLocationTypeahead from '~/components/resumes/shared/ResumeLocationTypeahead';
import ResumeRoleTypeahead from '~/components/resumes/shared/ResumeRoleTypeahead';
import ResumeSubmissionGuidelines from '~/components/resumes/submit-form/ResumeSubmissionGuidelines';
import Container from '~/components/shared/Container';
import CountriesTypeahead from '~/components/shared/CountriesTypeahead';
import loginPageHref from '~/components/shared/loginPageHref';
import { RESUME_STORAGE_KEY } from '~/constants/file-storage-keys';
@ -318,9 +318,10 @@ export default function SubmitResumeForm({
control={control}
name="location"
render={({ field: { value } }) => (
<ResumeLocationTypeahead
<CountriesTypeahead
disabled={isLoading}
placeholder="Select a location"
label="Location"
placeholder="Enter a country"
required={true}
value={value}
onSelect={(option) => onSelect('location', option)}

Loading…
Cancel
Save