import { subMonths, subYears } from 'date-fns'; import Head from 'next/head'; import Router, { useRouter } from 'next/router'; import { useEffect, useMemo, useState } from 'react'; import { Bars3BottomLeftIcon } from '@heroicons/react/20/solid'; import { NoSymbolIcon } from '@heroicons/react/24/outline'; import type { QuestionsQuestionType } from '@prisma/client'; import type { TypeaheadOption } from '@tih/ui'; import { useToast } from '@tih/ui'; import { Button, SlideOut } from '@tih/ui'; import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics'; import QuestionOverviewCard from '~/components/questions/card/question/QuestionOverviewCard'; import ContributeQuestionCard from '~/components/questions/ContributeQuestionCard'; import FilterSection from '~/components/questions/filter/FilterSection'; import PaginationLoadMoreButton from '~/components/questions/PaginationLoadMoreButton'; import QuestionSearchBar from '~/components/questions/QuestionSearchBar'; import CompanyTypeahead from '~/components/questions/typeahead/CompanyTypeahead'; import LocationTypeahead from '~/components/questions/typeahead/LocationTypeahead'; import RoleTypeahead from '~/components/questions/typeahead/RoleTypeahead'; import type { JobTitleType } from '~/components/shared/JobTitles'; import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; import type { QuestionAge } from '~/utils/questions/constants'; import { QUESTION_SORT_TYPES } from '~/utils/questions/constants'; import { APP_TITLE } from '~/utils/questions/constants'; import { QUESTION_AGES, QUESTION_TYPES } from '~/utils/questions/constants'; import createSlug from '~/utils/questions/createSlug'; import { locationOptionToSlug } from '~/utils/questions/locationSlug'; import relabelQuestionAggregates from '~/utils/questions/relabelQuestionAggregates'; import { useSearchParam, useSearchParamSingle, } from '~/utils/questions/useSearchParam'; import { trpc } from '~/utils/trpc'; import type { Location } from '~/types/questions.d'; import { SortType } from '~/types/questions.d'; import { SortOrder } from '~/types/questions.d'; function sortOrderToString(value: SortOrder): string | null { switch (value) { case SortOrder.ASC: return 'ASC'; case SortOrder.DESC: return 'DESC'; default: return null; } } function sortTypeToString(value: SortType): string | null { switch (value) { case SortType.TOP: return 'TOP'; case SortType.NEW: return 'NEW'; case SortType.ENCOUNTERS: return 'ENCOUNTERS'; default: return null; } } export default function QuestionsBrowsePage() { const router = useRouter(); const { event } = useGoogleAnalytics(); const [query, setQuery] = useState(''); const [ selectedCompanySlugs, setSelectedCompanySlugs, areCompaniesInitialized, ] = useSearchParam('companies'); const [ selectedQuestionTypes, setSelectedQuestionTypes, areQuestionTypesInitialized, ] = useSearchParam('questionTypes', { stringToParam: (param) => { const uppercaseParam = param.toUpperCase(); return ( QUESTION_TYPES.find( (questionType) => questionType.value.toUpperCase() === uppercaseParam, )?.value ?? null ); }, }); const [ selectedQuestionAge, setSelectedQuestionAge, isQuestionAgeInitialized, ] = useSearchParamSingle('questionAge', { defaultValue: 'all', stringToParam: (param) => { const uppercaseParam = param.toUpperCase(); return ( QUESTION_AGES.find( (questionAge) => questionAge.value.toUpperCase() === uppercaseParam, )?.value ?? null ); }, }); const [selectedRoles, setSelectedRoles, areRolesInitialized] = useSearchParam('roles'); const [selectedLocations, setSelectedLocations, areLocationsInitialized] = useSearchParam('locations', { paramToString: locationOptionToSlug, stringToParam: (param) => { const [countryId, stateId, cityId, id, label, value] = param.split('-'); return { cityId, countryId, id, label, stateId, value }; }, }); const [sortOrder, setSortOrder, isSortOrderInitialized] = useSearchParamSingle('sortOrder', { defaultValue: SortOrder.DESC, paramToString: sortOrderToString, stringToParam: (param) => { const uppercaseParam = param.toUpperCase(); if (uppercaseParam === 'ASC') { return SortOrder.ASC; } if (uppercaseParam === 'DESC') { return SortOrder.DESC; } return null; }, }); const [sortType, setSortType, isSortTypeInitialized] = useSearchParamSingle('sortType', { defaultValue: SortType.TOP, paramToString: sortTypeToString, stringToParam: (param) => { const uppercaseParam = param.toUpperCase(); if (uppercaseParam === 'NEW') { return SortType.NEW; } if (uppercaseParam === 'TOP') { return SortType.TOP; } if (uppercaseParam === 'ENCOUNTERS') { return SortType.ENCOUNTERS; } return null; }, }); const hasFilters = useMemo( () => selectedCompanySlugs.length > 0 || selectedQuestionTypes.length > 0 || selectedQuestionAge !== 'all' || selectedRoles.length > 0 || selectedLocations.length > 0, [ selectedCompanySlugs, selectedQuestionTypes, selectedQuestionAge, selectedRoles, selectedLocations, ], ); 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 questionsInfiniteQuery = trpc.useInfiniteQuery( [ 'questions.questions.getQuestionsByFilterAndContent', { // TODO: Enable filtering by countryIds and stateIds cityIds: selectedLocations .map(({ cityId }) => cityId) .filter((id) => id !== undefined) as Array, companyIds: selectedCompanySlugs.map((slug) => slug.split('_')[0]), content: query, countryIds: [], endDate: today, limit: 10, questionTypes: selectedQuestionTypes, roles: selectedRoles, sortOrder, sortType, startDate, stateIds: [], }, ], { getNextPageParam: (lastPage) => lastPage.nextCursor, keepPreviousData: true, }, ); const { data: questionsQueryData } = questionsInfiniteQuery; const questionCount = useMemo(() => { if (!questionsQueryData) { return undefined; } return questionsQueryData.pages.reduce( (acc, page) => acc + (page.data.length as number), 0, ); }, [questionsQueryData]); const utils = trpc.useContext(); const { mutate: createQuestion } = trpc.useMutation( 'questions.questions.user.create', { onSuccess: () => { utils.invalidateQueries('questions.questions.getQuestionsByFilter'); event({ action: 'questions.create_question', category: 'engagement', label: 'create_question', }); showToast({ title: `Thank you for submitting your question!`, variant: 'success', }); }, }, ); const [loaded, setLoaded] = useState(false); const [filterDrawerOpen, setFilterDrawerOpen] = useState(false); 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 areSearchOptionsInitialized = useMemo(() => { return ( areCompaniesInitialized && areQuestionTypesInitialized && isQuestionAgeInitialized && areRolesInitialized && areLocationsInitialized && isSortTypeInitialized && isSortOrderInitialized ); }, [ areCompaniesInitialized, areQuestionTypesInitialized, isQuestionAgeInitialized, areRolesInitialized, areLocationsInitialized, isSortTypeInitialized, isSortOrderInitialized, ]); const { pathname } = router; useEffect(() => { if (areSearchOptionsInitialized) { // 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: selectedCompanySlugs, locations: selectedLocations.map(locationOptionToSlug), questionAge: selectedQuestionAge, questionTypes: selectedQuestionTypes, roles: selectedRoles, sortOrder: sortOrderToString(sortOrder), sortType: sortTypeToString(sortType), }, }); setLoaded(true); } }, [ areSearchOptionsInitialized, loaded, pathname, selectedCompanySlugs, selectedRoles, selectedLocations, selectedQuestionAge, selectedQuestionTypes, sortOrder, sortType, ]); const { showToast } = useToast(); const selectedCompanyOptions = useMemo(() => { return selectedCompanySlugs.map((company) => { const [id, label] = company.split('_'); return { checked: true, id, label, value: id, }; }); }, [selectedCompanySlugs]); const selectedRoleOptions = useMemo(() => { return selectedRoles.map((role) => ({ checked: true, id: role, label: getLabelForJobTitleType(role as JobTitleType), value: role, })); }, [selectedRoles]); const selectedLocationOptions = useMemo(() => { return selectedLocations.map((location) => ({ checked: true, ...location, })); }, [selectedLocations]); if (!loaded) { return null; } const filterSidebar = (
); return ( <> Home - {APP_TITLE}
{ const { cityId, countryId, stateId } = data.location; createQuestion({ cityId, companyId: data.company, content: data.questionContent, countryId, questionType: data.questionType, role: data.role.value, seenAt: data.date, stateId, }); }} />
{ setFilterDrawerOpen(!filterDrawerOpen); }} onQueryChange={(newQuery) => { setQuery(newQuery); }} onSortOrderChange={setSortOrder} onSortTypeChange={setSortType} />
{(questionsQueryData?.pages ?? []).flatMap( ({ data: questions }) => questions.map((question) => { const { companyCounts, countryCounts, roleCounts } = relabelQuestionAggregates( question.aggregatedQuestionEncounters, ); return ( ); }), )} {questionCount !== 0 && ( )} {questionCount === 0 && (

Nothing found.

{hasFilters &&

Try changing your search criteria.

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