From 199fc1a8b999524ecca1c69a74ff9527c910420d Mon Sep 17 00:00:00 2001 From: Peirong <35712975+peironggg@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:08:07 +0800 Subject: [PATCH] [resumes][feat] url search params (#429) * [resumes][feat] adapt useSearchParams * [resumes][feat] clickable button from review info tags --- apps/portal/src/pages/resumes/[resumeId].tsx | 92 +++++++++++++++- apps/portal/src/pages/resumes/browse.tsx | 100 ++++++++++++++---- apps/portal/src/pages/resumes/submit.tsx | 6 +- .../browse => utils/resumes}/resumeFilters.ts | 14 +-- .../src/utils/resumes/useSearchParams.ts | 26 +++++ 5 files changed, 202 insertions(+), 36 deletions(-) rename apps/portal/src/{components/resumes/browse => utils/resumes}/resumeFilters.ts (93%) create mode 100644 apps/portal/src/utils/resumes/useSearchParams.ts diff --git a/apps/portal/src/pages/resumes/[resumeId].tsx b/apps/portal/src/pages/resumes/[resumeId].tsx index 38c7cf31..0d3ec979 100644 --- a/apps/portal/src/pages/resumes/[resumeId].tsx +++ b/apps/portal/src/pages/resumes/[resumeId].tsx @@ -21,9 +21,25 @@ import ResumeCommentsList from '~/components/resumes/comments/ResumeCommentsList import ResumePdf from '~/components/resumes/ResumePdf'; import ResumeExpandableText from '~/components/resumes/shared/ResumeExpandableText'; +import type { + FilterOption, + LocationFilter, +} from '~/utils/resumes/resumeFilters'; +import { + BROWSE_TABS_VALUES, + EXPERIENCES, + INITIAL_FILTER_STATE, + LOCATIONS, + ROLES, + SORT_OPTIONS, +} from '~/utils/resumes/resumeFilters'; import { trpc } from '~/utils/trpc'; import SubmitResumeForm from './submit'; +import type { + ExperienceFilter, + RoleFilter, +} from '../../utils/resumes/resumeFilters'; export default function ResumeReviewPage() { const ErrorPage = ( @@ -57,7 +73,8 @@ export default function ResumeReviewPage() { }, }); const userIsOwner = - session?.user?.id != null && session.user.id === detailsQuery.data?.userId; + session?.user?.id !== undefined && + session.user.id === detailsQuery.data?.userId; const [isEditMode, setIsEditMode] = useState(false); const [showCommentsForm, setShowCommentsForm] = useState(false); @@ -79,6 +96,46 @@ export default function ResumeReviewPage() { } }; + const onInfoTagClick = ({ + locationLabel, + experienceLabel, + roleLabel, + }: { + experienceLabel?: string; + locationLabel?: string; + roleLabel?: string; + }) => { + const getFilterValue = ( + label: string, + filterOptions: Array< + FilterOption + >, + ) => filterOptions.find((option) => option.label === label)?.value; + + router.push({ + pathname: '/resumes/browse', + query: { + currentPage: JSON.stringify(1), + searchValue: JSON.stringify(''), + shortcutSelected: JSON.stringify('all'), + sortOrder: JSON.stringify(SORT_OPTIONS.LATEST), + tabsValue: JSON.stringify(BROWSE_TABS_VALUES.ALL), + userFilters: JSON.stringify({ + ...INITIAL_FILTER_STATE, + ...(locationLabel && { + location: [getFilterValue(locationLabel, LOCATIONS)], + }), + ...(roleLabel && { + role: [getFilterValue(roleLabel, ROLES)], + }), + ...(experienceLabel && { + experience: [getFilterValue(experienceLabel, EXPERIENCES)], + }), + }), + }, + }); + }; + const onEditButtonClick = () => { setIsEditMode(true); }; @@ -199,21 +256,48 @@ export default function ResumeReviewPage() { aria-hidden="true" className="mr-1.5 h-5 w-5 flex-shrink-0 text-indigo-400" /> - {detailsQuery.data.role} +
{ + return ( + isTabsValueInit && + isSortOrderInit && + isSearchValueInit && + isShortcutInit && + isCurrentPageInit && + isUserFiltersInit + ); + }, [ + isTabsValueInit, + isSortOrderInit, + isSearchValueInit, + isShortcutInit, + isCurrentPageInit, + isUserFiltersInit, + ]); useEffect(() => { setCurrentPage(1); - }, [userFilters, sortOrder, searchValue]); + }, [userFilters, sortOrder, setCurrentPage, searchValue]); + + useEffect(() => { + // Router.replace used instead of router.replace to avoid + // the page reloading itself since the router.replace + // callback changes on every page load + if (!isSearchOptionsInit) { + return; + } + + Router.replace({ + pathname: router.pathname, + query: { + currentPage: JSON.stringify(currentPage), + searchValue: JSON.stringify(searchValue), + shortcutSelected: JSON.stringify(shortcutSelected), + sortOrder: JSON.stringify(sortOrder), + tabsValue: JSON.stringify(tabsValue), + userFilters: JSON.stringify(userFilters), + }, + }); + }, [ + tabsValue, + sortOrder, + searchValue, + userFilters, + shortcutSelected, + currentPage, + router.pathname, + isSearchOptionsInit, + ]); const allResumesQuery = trpc.useQuery( [ @@ -509,7 +569,7 @@ export default function ResumeHomePage() { key={key} isSelected={sortOrder === key} label={value} - onClick={() => setSortOrder(key)}> + onClick={() => setSortOrder(value)}> ))}
diff --git a/apps/portal/src/pages/resumes/submit.tsx b/apps/portal/src/pages/resumes/submit.tsx index 7360c9de..182a32bd 100644 --- a/apps/portal/src/pages/resumes/submit.tsx +++ b/apps/portal/src/pages/resumes/submit.tsx @@ -19,14 +19,10 @@ import { TextInput, } from '@tih/ui'; -import { - EXPERIENCES, - LOCATIONS, - ROLES, -} from '~/components/resumes/browse/resumeFilters'; import SubmissionGuidelines from '~/components/resumes/submit-form/SubmissionGuidelines'; import { RESUME_STORAGE_KEY } from '~/constants/file-storage-keys'; +import { EXPERIENCES, LOCATIONS, ROLES } from '~/utils/resumes/resumeFilters'; import { trpc } from '~/utils/trpc'; const FILE_SIZE_LIMIT_MB = 3; diff --git a/apps/portal/src/components/resumes/browse/resumeFilters.ts b/apps/portal/src/utils/resumes/resumeFilters.ts similarity index 93% rename from apps/portal/src/components/resumes/browse/resumeFilters.ts rename to apps/portal/src/utils/resumes/resumeFilters.ts index e0c4b0b5..1731647f 100644 --- a/apps/portal/src/components/resumes/browse/resumeFilters.ts +++ b/apps/portal/src/utils/resumes/resumeFilters.ts @@ -4,7 +4,7 @@ export type CustomFilter = { numComments: number; }; -type RoleFilter = +export type RoleFilter = | 'Android Engineer' | 'Backend Engineer' | 'DevOps Engineer' @@ -12,7 +12,7 @@ type RoleFilter = | 'Full-Stack Engineer' | 'iOS Engineer'; -type ExperienceFilter = +export type ExperienceFilter = | 'Entry Level (0 - 2 years)' | 'Freshman' | 'Junior' @@ -21,7 +21,7 @@ type ExperienceFilter = | 'Senior' | 'Sophomore'; -type LocationFilter = 'India' | 'Singapore' | 'United States'; +export type LocationFilter = 'India' | 'Singapore' | 'United States'; export type FilterValue = ExperienceFilter | LocationFilter | RoleFilter; @@ -54,10 +54,10 @@ export const BROWSE_TABS_VALUES = { STARRED: 'starred', }; -export const SORT_OPTIONS: Record = { - latest: 'Latest', - popular: 'Popular', - topComments: 'Most Comments', +export const SORT_OPTIONS: Record = { + LATEST: 'latest', + POPULAR: 'popular', + TOPCOMMENTS: 'topComments', }; export const ROLES: Array> = [ diff --git a/apps/portal/src/utils/resumes/useSearchParams.ts b/apps/portal/src/utils/resumes/useSearchParams.ts new file mode 100644 index 00000000..0bea502c --- /dev/null +++ b/apps/portal/src/utils/resumes/useSearchParams.ts @@ -0,0 +1,26 @@ +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; + +export const useSearchParams = (name: string, defaultValue: T) => { + const [isInitialized, setIsInitialized] = useState(false); + const router = useRouter(); + + const [filters, setFilters] = useState(defaultValue); + + useEffect(() => { + if (router.isReady && !isInitialized) { + // Initialize from url query params + const query = router.query[name]; + if (query) { + const parsedQuery = + typeof query === 'string' ? JSON.parse(query) : query; + setFilters(parsedQuery); + } + setIsInitialized(true); + } + }, [isInitialized, name, router]); + + return [filters, setFilters, isInitialized] as const; +}; + +export default useSearchParams;