From db8379bdc8c0a489147d2c5541a7a9c89ab39e77 Mon Sep 17 00:00:00 2001 From: Jeff Sieu Date: Sat, 22 Oct 2022 14:19:04 +0800 Subject: [PATCH] [questions][ui] decouple landing and browse pages --- .../questions/QuestionsNavigation.ts | 2 +- apps/portal/src/pages/questions/browse.tsx | 405 +++++++++++++++++ apps/portal/src/pages/questions/index.tsx | 423 +----------------- 3 files changed, 417 insertions(+), 413 deletions(-) create mode 100644 apps/portal/src/pages/questions/browse.tsx diff --git a/apps/portal/src/components/questions/QuestionsNavigation.ts b/apps/portal/src/components/questions/QuestionsNavigation.ts index 704e6a59..f46d2388 100644 --- a/apps/portal/src/components/questions/QuestionsNavigation.ts +++ b/apps/portal/src/components/questions/QuestionsNavigation.ts @@ -1,7 +1,7 @@ import type { ProductNavigationItems } from '~/components/global/ProductNavigation'; const navigation: ProductNavigationItems = [ - { href: '/questions', name: 'Home' }, + { href: '/questions/browse', name: 'Browse' }, { href: '/questions/lists', name: 'My Lists' }, { href: '/questions/my-questions', name: 'My Questions' }, { href: '/questions/history', name: 'History' }, diff --git a/apps/portal/src/pages/questions/browse.tsx b/apps/portal/src/pages/questions/browse.tsx new file mode 100644 index 00000000..c4ade6a7 --- /dev/null +++ b/apps/portal/src/pages/questions/browse.tsx @@ -0,0 +1,405 @@ +import { subMonths, subYears } from 'date-fns'; +import Head from 'next/head'; +import Router, { useRouter } from 'next/router'; +import { useEffect, useMemo, useState } from 'react'; +import { NoSymbolIcon } from '@heroicons/react/24/outline'; +import type { QuestionsQuestionType } from '@prisma/client'; +import { SlideOut, Typeahead } from '@tih/ui'; + +import QuestionOverviewCard from '~/components/questions/card/question/QuestionOverviewCard'; +import ContributeQuestionCard from '~/components/questions/ContributeQuestionCard'; +import FilterSection from '~/components/questions/filter/FilterSection'; +import QuestionSearchBar from '~/components/questions/QuestionSearchBar'; + +import type { QuestionAge } from '~/utils/questions/constants'; +import { APP_TITLE } from '~/utils/questions/constants'; +import { ROLES } from '~/utils/questions/constants'; +import { + COMPANIES, + LOCATIONS, + QUESTION_AGES, + QUESTION_TYPES, +} from '~/utils/questions/constants'; +import createSlug from '~/utils/questions/createSlug'; +import { + useSearchFilter, + useSearchFilterSingle, +} from '~/utils/questions/useSearchFilter'; +import { trpc } from '~/utils/trpc'; + +export default function QuestionsBrowsePage() { + const router = useRouter(); + + const [selectedCompanies, setSelectedCompanies, areCompaniesInitialized] = + useSearchFilter('companies'); + const [ + selectedQuestionTypes, + setSelectedQuestionTypes, + areQuestionTypesInitialized, + ] = useSearchFilter('questionTypes', { + queryParamToValue: (param) => { + const uppercaseParam = param.toUpperCase(); + return ( + QUESTION_TYPES.find( + (questionType) => questionType.value.toUpperCase() === uppercaseParam, + )?.value ?? null + ); + }, + }); + const [ + selectedQuestionAge, + setSelectedQuestionAge, + isQuestionAgeInitialized, + ] = useSearchFilterSingle('questionAge', { + defaultValue: 'all', + queryParamToValue: (param) => { + const uppercaseParam = param.toUpperCase(); + return ( + QUESTION_AGES.find( + (questionAge) => questionAge.value.toUpperCase() === uppercaseParam, + )?.value ?? null + ); + }, + }); + + const [selectedRoles, setSelectedRoles, areRolesInitialized] = + useSearchFilter('roles'); + const [selectedLocations, setSelectedLocations, areLocationsInitialized] = + useSearchFilter('locations'); + + const today = useMemo(() => new Date(), []); + const startDate = useMemo(() => { + return selectedQuestionAge === 'last-year' + ? subYears(new Date(), 1) + : selectedQuestionAge === 'last-6-months' + ? subMonths(new Date(), 6) + : selectedQuestionAge === 'last-month' + ? subMonths(new Date(), 1) + : undefined; + }, [selectedQuestionAge]); + + const { data: questions } = trpc.useQuery( + [ + 'questions.questions.getQuestionsByFilter', + { + companyNames: selectedCompanies, + endDate: today, + locations: selectedLocations, + questionTypes: selectedQuestionTypes, + roles: selectedRoles, + startDate, + }, + ], + { + keepPreviousData: true, + }, + ); + + const utils = trpc.useContext(); + const { mutate: createQuestion } = trpc.useMutation( + 'questions.questions.create', + { + onSuccess: () => { + utils.invalidateQueries('questions.questions.getQuestionsByFilter'); + }, + }, + ); + + const [loaded, setLoaded] = useState(false); + const [filterDrawerOpen, setFilterDrawerOpen] = useState(false); + + const companyFilterOptions = useMemo(() => { + return COMPANIES.map((company) => ({ + ...company, + checked: selectedCompanies.includes(company.value), + })); + }, [selectedCompanies]); + + const questionTypeFilterOptions = useMemo(() => { + return QUESTION_TYPES.map((questionType) => ({ + ...questionType, + checked: selectedQuestionTypes.includes(questionType.value), + })); + }, [selectedQuestionTypes]); + + const questionAgeFilterOptions = useMemo(() => { + return QUESTION_AGES.map((questionAge) => ({ + ...questionAge, + checked: selectedQuestionAge === questionAge.value, + })); + }, [selectedQuestionAge]); + + const roleFilterOptions = useMemo(() => { + return ROLES.map((role) => ({ + ...role, + checked: selectedRoles.includes(role.value), + })); + }, [selectedRoles]); + + const locationFilterOptions = useMemo(() => { + return LOCATIONS.map((location) => ({ + ...location, + checked: selectedLocations.includes(location.value), + })); + }, [selectedLocations]); + + const areFiltersInitialized = useMemo(() => { + return ( + areCompaniesInitialized && + areQuestionTypesInitialized && + isQuestionAgeInitialized && + areRolesInitialized && + areLocationsInitialized + ); + }, [ + areCompaniesInitialized, + areQuestionTypesInitialized, + isQuestionAgeInitialized, + areRolesInitialized, + areLocationsInitialized, + ]); + + const { pathname } = router; + useEffect(() => { + if (areFiltersInitialized) { + // Router.replace used instead of router.replace to avoid + // the page reloading itself since the router.replace + // callback changes on every page load + Router.replace({ + pathname, + query: { + companies: selectedCompanies, + locations: selectedLocations, + questionAge: selectedQuestionAge, + questionTypes: selectedQuestionTypes, + roles: selectedRoles, + }, + }); + + setLoaded(true); + } + }, [ + areFiltersInitialized, + loaded, + pathname, + selectedCompanies, + selectedRoles, + selectedLocations, + selectedQuestionAge, + selectedQuestionTypes, + ]); + + if (!loaded) { + return null; + } + const filterSidebar = ( +
+ ( + {}} + onSelect={({ value }) => { + onOptionChange(value, true); + }} + /> + )} + onOptionChange={(optionValue, checked) => { + if (checked) { + setSelectedCompanies([...selectedCompanies, optionValue]); + } else { + setSelectedCompanies( + selectedCompanies.filter((company) => company !== optionValue), + ); + } + }} + /> + { + if (checked) { + setSelectedQuestionTypes([...selectedQuestionTypes, optionValue]); + } else { + setSelectedQuestionTypes( + selectedQuestionTypes.filter( + (questionType) => questionType !== optionValue, + ), + ); + } + }} + /> + { + setSelectedQuestionAge(optionValue); + }} + /> + ( + {}} + onSelect={({ value }) => { + onOptionChange(value, true); + }} + /> + )} + onOptionChange={(optionValue, checked) => { + if (checked) { + setSelectedRoles([...selectedRoles, optionValue]); + } else { + setSelectedRoles( + selectedRoles.filter((role) => role !== optionValue), + ); + } + }} + /> + ( + {}} + onSelect={({ value }) => { + onOptionChange(value, true); + }} + /> + )} + onOptionChange={(optionValue, checked) => { + if (checked) { + setSelectedLocations([...selectedLocations, optionValue]); + } else { + setSelectedLocations( + selectedLocations.filter((location) => location !== optionValue), + ); + } + }} + /> +
+ ); + + return ( + <> + + Home - {APP_TITLE} + +
+
+
+
+
+ { + createQuestion({ + companyId: data.company, + content: data.questionContent, + location: data.location, + questionType: data.questionType, + role: data.role, + seenAt: data.date, + }); + }} + /> + { + setFilterDrawerOpen(!filterDrawerOpen); + }} + onSortChange={(value) => { + // eslint-disable-next-line no-console + console.log(value); + }} + /> +
+ {(questions ?? []).map((question) => ( + + ))} + {questions?.length === 0 && ( +
+ +

Nothing found. Try changing your search filters.

+
+ )} +
+
+
+
+ + { + setFilterDrawerOpen(false); + }}> + {filterSidebar} + +
+
+ + ); +} diff --git a/apps/portal/src/pages/questions/index.tsx b/apps/portal/src/pages/questions/index.tsx index 000f7220..0f5f07f1 100644 --- a/apps/portal/src/pages/questions/index.tsx +++ b/apps/portal/src/pages/questions/index.tsx @@ -1,435 +1,34 @@ -import { subMonths, subYears } from 'date-fns'; import Head from 'next/head'; -import Router, { useRouter } from 'next/router'; -import { useEffect, useMemo, useState } from 'react'; -import { NoSymbolIcon } from '@heroicons/react/24/outline'; -import type { QuestionsQuestionType } from '@prisma/client'; -import { SlideOut, Typeahead } from '@tih/ui'; +import { useRouter } from 'next/router'; -import QuestionOverviewCard from '~/components/questions/card/question/QuestionOverviewCard'; -import ContributeQuestionCard from '~/components/questions/ContributeQuestionCard'; -import FilterSection from '~/components/questions/filter/FilterSection'; import type { LandingQueryData } from '~/components/questions/LandingComponent'; import LandingComponent from '~/components/questions/LandingComponent'; -import QuestionSearchBar from '~/components/questions/QuestionSearchBar'; -import type { QuestionAge } from '~/utils/questions/constants'; import { APP_TITLE } from '~/utils/questions/constants'; -import { ROLES } from '~/utils/questions/constants'; -import { - COMPANIES, - LOCATIONS, - QUESTION_AGES, - QUESTION_TYPES, -} from '~/utils/questions/constants'; -import createSlug from '~/utils/questions/createSlug'; -import { - useSearchFilter, - useSearchFilterSingle, -} from '~/utils/questions/useSearchFilter'; -import { trpc } from '~/utils/trpc'; export default function QuestionsHomePage() { const router = useRouter(); - const [selectedCompanies, setSelectedCompanies, areCompaniesInitialized] = - useSearchFilter('companies'); - const [ - selectedQuestionTypes, - setSelectedQuestionTypes, - areQuestionTypesInitialized, - ] = useSearchFilter('questionTypes', { - queryParamToValue: (param) => { - const uppercaseParam = param.toUpperCase(); - return ( - QUESTION_TYPES.find( - (questionType) => questionType.value.toUpperCase() === uppercaseParam, - )?.value ?? null - ); - }, - }); - const [ - selectedQuestionAge, - setSelectedQuestionAge, - isQuestionAgeInitialized, - ] = useSearchFilterSingle('questionAge', { - defaultValue: 'all', - queryParamToValue: (param) => { - const uppercaseParam = param.toUpperCase(); - return ( - QUESTION_AGES.find( - (questionAge) => questionAge.value.toUpperCase() === uppercaseParam, - )?.value ?? null - ); - }, - }); - - const [selectedRoles, setSelectedRoles, areRolesInitialized] = - useSearchFilter('roles'); - const [selectedLocations, setSelectedLocations, areLocationsInitialized] = - useSearchFilter('locations'); - - const today = useMemo(() => new Date(), []); - const startDate = useMemo(() => { - return selectedQuestionAge === 'last-year' - ? subYears(new Date(), 1) - : selectedQuestionAge === 'last-6-months' - ? subMonths(new Date(), 6) - : selectedQuestionAge === 'last-month' - ? subMonths(new Date(), 1) - : undefined; - }, [selectedQuestionAge]); - - const { data: questions } = trpc.useQuery( - [ - 'questions.questions.getQuestionsByFilter', - { - companyNames: selectedCompanies, - endDate: today, - locations: selectedLocations, - questionTypes: selectedQuestionTypes, - roles: selectedRoles, - startDate, - }, - ], - { - keepPreviousData: true, - }, - ); - - const utils = trpc.useContext(); - const { mutate: createQuestion } = trpc.useMutation( - 'questions.questions.create', - { - onSuccess: () => { - utils.invalidateQueries('questions.questions.getQuestionsByFilter'); - }, - }, - ); - - const [hasLanded, setHasLanded] = useState(false); - const [loaded, setLoaded] = useState(false); - const [filterDrawerOpen, setFilterDrawerOpen] = useState(false); - - const companyFilterOptions = useMemo(() => { - return COMPANIES.map((company) => ({ - ...company, - checked: selectedCompanies.includes(company.value), - })); - }, [selectedCompanies]); - - const questionTypeFilterOptions = useMemo(() => { - return QUESTION_TYPES.map((questionType) => ({ - ...questionType, - checked: selectedQuestionTypes.includes(questionType.value), - })); - }, [selectedQuestionTypes]); - - const questionAgeFilterOptions = useMemo(() => { - return QUESTION_AGES.map((questionAge) => ({ - ...questionAge, - checked: selectedQuestionAge === questionAge.value, - })); - }, [selectedQuestionAge]); - - const roleFilterOptions = useMemo(() => { - return ROLES.map((role) => ({ - ...role, - checked: selectedRoles.includes(role.value), - })); - }, [selectedRoles]); - - const locationFilterOptions = useMemo(() => { - return LOCATIONS.map((location) => ({ - ...location, - checked: selectedLocations.includes(location.value), - })); - }, [selectedLocations]); - const handleLandingQuery = async (data: LandingQueryData) => { const { company, location, questionType } = data; - setSelectedCompanies([company]); - setSelectedRoles([]); - setSelectedLocations([location]); - setSelectedQuestionTypes([questionType as QuestionsQuestionType]); - setHasLanded(true); + // Go to browse page + router.push({ + pathname: '/questions/browse', + query: { + companies: [company], + locations: [location], + questionTypes: [questionType], + }, + }); }; - const areFiltersInitialized = useMemo(() => { - return ( - areCompaniesInitialized && - areQuestionTypesInitialized && - isQuestionAgeInitialized && - areRolesInitialized && - areLocationsInitialized - ); - }, [ - areCompaniesInitialized, - areQuestionTypesInitialized, - isQuestionAgeInitialized, - areRolesInitialized, - areLocationsInitialized, - ]); - - const { pathname } = router; - useEffect(() => { - if (areFiltersInitialized) { - // Router.replace used instead of router.replace to avoid - // the page reloading itself since the router.replace - // callback changes on every page load - Router.replace({ - pathname, - query: { - companies: selectedCompanies, - locations: selectedLocations, - questionAge: selectedQuestionAge, - questionTypes: selectedQuestionTypes, - roles: selectedRoles, - }, - }); - const hasFilter = - selectedCompanies.length > 0 || - selectedRoles.length > 0 || - selectedLocations.length > 0 || - selectedQuestionAge !== 'all' || - selectedQuestionTypes.length > 0; - if (hasFilter) { - setHasLanded(true); - } - - setLoaded(true); - } - }, [ - areFiltersInitialized, - hasLanded, - loaded, - pathname, - selectedCompanies, - selectedRoles, - selectedLocations, - selectedQuestionAge, - selectedQuestionTypes, - ]); - - if (!loaded) { - return null; - } - const filterSidebar = ( -
- ( - {}} - onSelect={({ value }) => { - onOptionChange(value, true); - }} - /> - )} - onOptionChange={(optionValue, checked) => { - if (checked) { - setSelectedCompanies([...selectedCompanies, optionValue]); - } else { - setSelectedCompanies( - selectedCompanies.filter((company) => company !== optionValue), - ); - } - }} - /> - { - if (checked) { - setSelectedQuestionTypes([...selectedQuestionTypes, optionValue]); - } else { - setSelectedQuestionTypes( - selectedQuestionTypes.filter( - (questionType) => questionType !== optionValue, - ), - ); - } - }} - /> - { - setSelectedQuestionAge(optionValue); - }} - /> - ( - {}} - onSelect={({ value }) => { - onOptionChange(value, true); - }} - /> - )} - onOptionChange={(optionValue, checked) => { - if (checked) { - setSelectedRoles([...selectedRoles, optionValue]); - } else { - setSelectedRoles( - selectedRoles.filter((role) => role !== optionValue), - ); - } - }} - /> - ( - {}} - onSelect={({ value }) => { - onOptionChange(value, true); - }} - /> - )} - onOptionChange={(optionValue, checked) => { - if (checked) { - setSelectedLocations([...selectedLocations, optionValue]); - } else { - setSelectedLocations( - selectedLocations.filter((location) => location !== optionValue), - ); - } - }} - /> -
- ); - return ( <> Home - {APP_TITLE} - {!hasLanded ? ( - - ) : ( -
-
-
-
-
- { - createQuestion({ - companyId: data.company, - content: data.questionContent, - location: data.location, - questionType: data.questionType, - role: data.role, - seenAt: data.date, - }); - }} - /> - { - setFilterDrawerOpen(!filterDrawerOpen); - }} - onSortChange={(value) => { - // eslint-disable-next-line no-console - console.log(value); - }} - /> -
- {(questions ?? []).map((question) => ( - - ))} - {questions?.length === 0 && ( -
- -

Nothing found. Try changing your search filters.

-
- )} -
-
-
-
- - { - setFilterDrawerOpen(false); - }}> - {filterSidebar} - -
-
- )} + ); }