diff --git a/apps/portal/public/logo.svg b/apps/portal/public/logo.svg new file mode 100644 index 00000000..ed21574f --- /dev/null +++ b/apps/portal/public/logo.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/portal/src/components/questions/ContributeQuestionCard.tsx b/apps/portal/src/components/questions/ContributeQuestionCard.tsx new file mode 100644 index 00000000..bd6c4d78 --- /dev/null +++ b/apps/portal/src/components/questions/ContributeQuestionCard.tsx @@ -0,0 +1,102 @@ +import type { ComponentProps, ForwardedRef } from 'react'; +import { useState } from 'react'; +import { forwardRef } from 'react'; +import type { UseFormRegisterReturn } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; +import { + BuildingOffice2Icon, + CalendarDaysIcon, + QuestionMarkCircleIcon, +} from '@heroicons/react/24/outline'; +import { Button, TextInput } from '@tih/ui'; + +import ContributeQuestionModal from './ContributeQuestionModal'; + +export type ContributeQuestionData = { + company: string; + date: Date; + questionContent: string; + questionType: string; +}; + +type TextInputProps = ComponentProps; + +type FormTextInputProps = Omit & + Pick, 'onChange'>; + +function FormTextInputWithRef( + props: FormTextInputProps, + ref?: ForwardedRef, +) { + const { onChange, ...rest } = props; + return ( + onChange(event)} + /> + ); +} + +const FormTextInput = forwardRef(FormTextInputWithRef); + +export type ContributeQuestionCardProps = { + onSubmit: (data: ContributeQuestionData) => void; +}; + +export default function ContributeQuestionCard({ + onSubmit, +}: ContributeQuestionCardProps) { + const { register, handleSubmit } = useForm(); + const [isOpen, setOpen] = useState(false); + return ( + <> +
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + ); +} diff --git a/apps/portal/src/components/questions/ContributeQuestionModal.tsx b/apps/portal/src/components/questions/ContributeQuestionModal.tsx new file mode 100644 index 00000000..1da146e8 --- /dev/null +++ b/apps/portal/src/components/questions/ContributeQuestionModal.tsx @@ -0,0 +1,96 @@ +import type { Dispatch, SetStateAction } from 'react'; +import { Fragment, useState } from 'react'; +import { Dialog, Transition } from '@headlessui/react'; + +import Checkbox from './ui-patch/Checkbox'; + +export type ContributeQuestionModalProps = { + contributeState: boolean; + setContributeState: Dispatch>; +}; + +export default function ContributeQuestionModal({ + contributeState, + setContributeState, +}: ContributeQuestionModalProps) { + const [canSubmit, setCanSubmit] = useState(false); + + const handleCheckSimilarQuestions = (checked: boolean) => { + setCanSubmit(checked); + }; + + return ( + + setContributeState(false)}> + +
+ + +
+
+ + +
+
+
+ + Question Draft + +
+

+ Question Contribution form +

+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+
+
+
+ ); +} diff --git a/apps/portal/src/components/questions/NavBar.tsx b/apps/portal/src/components/questions/NavBar.tsx new file mode 100644 index 00000000..99b76457 --- /dev/null +++ b/apps/portal/src/components/questions/NavBar.tsx @@ -0,0 +1,56 @@ +import Link from 'next/link'; + +const navigation = [ + { href: '/questions/landing', name: '*Landing*' }, + { href: '/questions', name: 'Home' }, + { href: '#', name: 'My Lists' }, + { href: '#', name: 'My Questions' }, + { href: '#', name: 'History' }, +]; + +export default function NavBar() { + return ( +
+ +
+ ); +} diff --git a/apps/portal/src/components/questions/QuestionOverviewCard.tsx b/apps/portal/src/components/questions/QuestionOverviewCard.tsx new file mode 100644 index 00000000..31435c04 --- /dev/null +++ b/apps/portal/src/components/questions/QuestionOverviewCard.tsx @@ -0,0 +1,72 @@ +import { + ChatBubbleBottomCenterTextIcon, + ChevronDownIcon, + ChevronUpIcon, + EyeIcon, +} from '@heroicons/react/24/outline'; +import { Badge, Button } from '@tih/ui'; + +export type QuestionOverviewCardProps = { + answerCount: number; + content: string; + location: string; + role: string; + similarCount: number; + timestamp: string; + upvoteCount: number; +}; + +export default function QuestionOverviewCard({ + answerCount, + content, + similarCount, + upvoteCount, + timestamp, + role, + location, +}: QuestionOverviewCardProps) { + return ( +
+
+
+
+
+ +

+ {timestamp} · {location} · {role} +

+
+

{content}

+
+
+
+
+ ); +} diff --git a/apps/portal/src/components/questions/QuestionSearchBar.tsx b/apps/portal/src/components/questions/QuestionSearchBar.tsx new file mode 100644 index 00000000..02ba0a89 --- /dev/null +++ b/apps/portal/src/components/questions/QuestionSearchBar.tsx @@ -0,0 +1,42 @@ +import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; +import { Select, TextInput } from '@tih/ui'; + +export type SortOption = { + label: string; + value: string; +}; + +export type QuestionSearchBarProps> = { + onSortChange?: (sortValue: SortOptions[number]['value']) => void; + sortOptions: SortOptions; + sortValue: SortOptions[number]['value']; +}; + +export default function QuestionSearchBar< + SortOptions extends Array, +>({ + onSortChange, + sortOptions, + sortValue, +}: QuestionSearchBarProps) { + return ( +
+
+ +
+ Sort by: + +
+ ); +} diff --git a/apps/portal/src/components/questions/filter/FilterSection.tsx b/apps/portal/src/components/questions/filter/FilterSection.tsx new file mode 100644 index 00000000..5954e669 --- /dev/null +++ b/apps/portal/src/components/questions/filter/FilterSection.tsx @@ -0,0 +1,62 @@ +import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; +import { Collapsible, TextInput } from '@tih/ui'; + +import Checkbox from '../ui-patch/Checkbox'; + +export type FilterOptions = { + checked: boolean; + label: string; + value: string; +}; + +export type FilterSectionProps = { + label: string; + onOptionChange: (optionValue: string, checked: boolean) => void; + options: Array; +} & ( + | { + searchPlaceholder: string; + showAll?: never; + } + | { + searchPlaceholder?: never; + showAll: true; + } +); + +export default function FilterSection({ + label, + options, + searchPlaceholder, + showAll, + onOptionChange, +}: FilterSectionProps) { + return ( +
+ +
+ {!showAll && ( + + )} +
+ {options.map((option) => ( + { + onOptionChange(option.value, checked); + }} + /> + ))} +
+
+
+
+ ); +} diff --git a/apps/portal/src/components/questions/ui-patch/Checkbox.tsx b/apps/portal/src/components/questions/ui-patch/Checkbox.tsx new file mode 100644 index 00000000..413c12fe --- /dev/null +++ b/apps/portal/src/components/questions/ui-patch/Checkbox.tsx @@ -0,0 +1,25 @@ +import { useId } from 'react'; + +export type CheckboxProps = { + checked: boolean; + label: string; + onChange: (checked: boolean) => void; +}; + +export default function Checkbox({ label, checked, onChange }: CheckboxProps) { + const id = useId(); + return ( +
+ onChange(event.target.checked)} + /> + +
+ ); +} diff --git a/apps/portal/src/pages/questions/index.tsx b/apps/portal/src/pages/questions/index.tsx index f8fbc5ec..cf6611e3 100644 --- a/apps/portal/src/pages/questions/index.tsx +++ b/apps/portal/src/pages/questions/index.tsx @@ -1,10 +1,202 @@ -import QuestionBankTitle from '~/components/questions/QuestionBankTitle'; +import { useMemo, useState } from 'react'; + +import ContributeQuestionCard from '~/components/questions/ContributeQuestionCard'; +import type { FilterOptions } from '~/components/questions/filter/FilterSection'; +import FilterSection from '~/components/questions/filter/FilterSection'; +import NavBar from '~/components/questions/NavBar'; +import QuestionOverviewCard from '~/components/questions/QuestionOverviewCard'; +import QuestionSearchBar from '~/components/questions/QuestionSearchBar'; + +type FilterChoices = Array>; + +const companies: FilterChoices = [ + { + label: 'Google', + value: 'Google', + }, + { + label: 'Meta', + value: 'meta', + }, +]; + +// Code, design, behavioral +const questionTypes: FilterChoices = [ + { + label: 'Code', + value: 'code', + }, + { + label: 'Design', + value: 'design', + }, + { + label: 'Behavioral', + value: 'behavioral', + }, +]; + +const questionAges: FilterChoices = [ + { + label: 'Last month', + value: 'last-month', + }, + { + label: 'Last 6 months', + value: 'last-6-months', + }, + { + label: 'Last year', + value: 'last-year', + }, +]; + +const locations: FilterChoices = [ + { + label: 'Singapore', + value: 'singapore', + }, +]; export default function QuestionsHomePage() { + const [selectedCompanies, setSelectedCompanies] = useState>([]); + const [selectedQuestionTypes, setSelectedQuestionTypes] = useState< + Array + >([]); + const [selectedQuestionAges, setSelectedQuestionAges] = useState< + Array + >([]); + const [selectedLocations, setSelectedLocations] = useState>([]); + + const companyFilterOptions = useMemo(() => { + return companies.map((company) => ({ + ...company, + checked: selectedCompanies.includes(company.value), + })); + }, [selectedCompanies]); + + const questionTypeFilterOptions = useMemo(() => { + return questionTypes.map((questionType) => ({ + ...questionType, + checked: selectedQuestionTypes.includes(questionType.value), + })); + }, [selectedQuestionTypes]); + + const questionAgeFilterOptions = useMemo(() => { + return questionAges.map((questionAge) => ({ + ...questionAge, + checked: selectedQuestionAges.includes(questionAge.value), + })); + }, [selectedQuestionAges]); + + const locationFilterOptions = useMemo(() => { + return locations.map((location) => ({ + ...location, + checked: selectedLocations.includes(location.value), + })); + }, [selectedLocations]); + return ( -
-
- +
+
+ +
+
+
+

Filter by

+
+ { + if (checked) { + setSelectedCompanies((prev) => [...prev, optionValue]); + } else { + setSelectedCompanies((prev) => + prev.filter((company) => company !== optionValue), + ); + } + }} + /> + { + if (checked) { + setSelectedQuestionTypes((prev) => [...prev, optionValue]); + } else { + setSelectedQuestionTypes((prev) => + prev.filter((questionType) => questionType !== optionValue), + ); + } + }} + /> + { + if (checked) { + setSelectedQuestionAges((prev) => [...prev, optionValue]); + } else { + setSelectedQuestionAges((prev) => + prev.filter((questionAge) => questionAge !== optionValue), + ); + } + }} + /> + { + if (checked) { + setSelectedLocations((prev) => [...prev, optionValue]); + } else { + setSelectedLocations((prev) => + prev.filter((location) => location !== optionValue), + ); + } + }} + /> +
+
+
+
+
+ { + // eslint-disable-next-line no-console + console.log(data); + }} + /> + + +
+
+
); diff --git a/packages/ui/src/Collapsible/Collapsible.tsx b/packages/ui/src/Collapsible/Collapsible.tsx index 93e5ba10..211c54fd 100644 --- a/packages/ui/src/Collapsible/Collapsible.tsx +++ b/packages/ui/src/Collapsible/Collapsible.tsx @@ -23,7 +23,7 @@ export default function Collapsible({ children, defaultOpen, label }: Props) { /> {label} - + {children}