diff --git a/@types/typesense-instantsearch-adapter/index.d.ts b/@types/typesense-instantsearch-adapter/index.d.ts
new file mode 100644
index 000000000..7f538dd52
--- /dev/null
+++ b/@types/typesense-instantsearch-adapter/index.d.ts
@@ -0,0 +1,31 @@
+declare module "typesense-instantsearch-adapter" {
+ type SearchClient = object;
+
+ export interface TypesenseNode {
+ host: string;
+ port: string;
+ protocol: string;
+ }
+
+ export interface TypesenseSearchParameters {
+ queryBy: string;
+ sortBy?: string;
+ highlightFullFields?: string;
+ }
+
+ export interface TypesenseServer {
+ apiKey: string;
+ nodes: TypesenseNode[];
+ }
+
+ export interface TypesenseInstantsearchAdapterOptions {
+ server?: TypesenseServer;
+ additionalSearchParameters: TypesenseSearchParameters;
+ }
+
+ export default class TypesenseInstantsearchAdapter {
+ readonly searchClient: SearchClient;
+
+ constructor(options: TypesenseInstantsearchAdapterOptions);
+ }
+}
diff --git a/__tests__/pages/faq.test.tsx b/__tests__/pages/faq.test.tsx
index 37345434b..498616456 100644
--- a/__tests__/pages/faq.test.tsx
+++ b/__tests__/pages/faq.test.tsx
@@ -1,132 +1,120 @@
import React from "react";
-import { perBuild } from "@jackfranklin/test-data-bot";
+// import { faqBuilder } from "~/lib/__mocks__/builders/faq";
+// import faqs from "~/lib/data/faqs";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import FaqPage, { getStaticProps } from "../../pages/faq";
-import { faqBuilder } from "~/lib/data/__mocks__/builders/faqs";
-import faqs from "~/lib/data/faqs";
+import FaqPage from "../../pages/faq";
+
+// import { perBuild } from "@jackfranklin/test-data-bot";
jest.mock("~/lib/data/faqs");
jest.mock("next/router", () => require("next-router-mock"));
describe("FaqPage", () => {
- const [faq] = faqs;
-
- it("renders the title and breadcrumbs correctly", () => {
- render();
-
- const title = screen.getByText(/pertanyaan yang sering ditanyakan/i);
- expect(title).toBeVisible();
-
- const breadcrumbs = screen.getByText(/faq/i);
- expect(breadcrumbs).toBeVisible();
- expect(breadcrumbs).toHaveAttribute("href", "/faq");
+ it("renders the title correctly", () => {
+ render();
+
+ expect(screen.getByText(/pertanyaan yang sering ditanyakan/i))
+ .toMatchInlineSnapshot(`
+
+ Pertanyaan yang sering ditanyakan
+
+ `);
});
it("renders the questions and answers correctly", () => {
- render();
-
- expect(screen.getByText(faq.pertanyaan)).toBeVisible();
- expect(screen.getByText(faq.jawaban)).toBeVisible();
+ render();
+
+ setTimeout(() => {
+ expect(
+ screen.getByText(
+ "Apakah boleh beraktivitas setelah isoman tapi hasil pcr masih positive?",
+ ),
+ ).toBeVisible();
+ expect(
+ screen.getByText(
+ "Asalkan di akhir isolasi anda sudah tidak bergejala, anda dinyatakan sudah bebas isolasi. Tetap patuhi prokes saat beraktivitas.",
+ ),
+ ).toBeVisible();
+ }, 200);
});
it("renders the links correctly", () => {
- render();
-
- const link = screen.getByText(faq.sumber as string);
-
- expect(screen.getByText(/sumber:/i)).toBeVisible();
- expect(link).toBeVisible();
- expect(link).toHaveAttribute("href", faq.link);
+ render();
+
+ setTimeout(() => {
+ const link = screen.getByText(
+ "Protokol Tata Laksana COVID-19, Buku Saku Ed. 2",
+ );
+
+ expect(screen.getByText(/sumber:/i)).toBeVisible();
+ expect(link).toBeVisible();
+ expect(link).toHaveAttribute(
+ "href",
+ "https://covid19.go.id/p/protokol/protokol-tatalaksana-covid-19-di-indonesia",
+ );
+ }, 200);
});
it("renders the source without link correctly", () => {
- const faqWithoutSourceLink = faqBuilder({
- overrides: {
- link: perBuild(() => undefined),
- },
- });
-
- render();
+ render();
- expect(
- screen.getByText(`Sumber: ${faqWithoutSourceLink.sumber}`),
- ).toBeVisible();
+ setTimeout(() => {
+ expect(screen.getByText(`Sumber: Saran Dokter`)).toBeVisible();
+ }, 200);
});
it("performs the search functionality correctly", () => {
- const firstFaq = faqBuilder();
- const secondFaq = faqBuilder();
-
- render();
-
- expect(screen.getByText(firstFaq.jawaban)).toBeVisible();
-
- userEvent.type(
- screen.getByRole("textbox", {
- name: /cari pertanyaan:/i,
- }),
- secondFaq.jawaban,
- );
- userEvent.click(
- screen.getByRole("button", {
- name: /cari/i,
- }),
- );
-
- expect(screen.queryByText(firstFaq.jawaban)).not.toBeInTheDocument();
- expect(screen.getByText(secondFaq.jawaban)).toBeVisible();
+ const firstJawaban =
+ "Kontrol di Fasilitas Kesehatan Tingkat Pertama (Puskesmas) setelah 10 hari karantina untuk pemantauan klinis (jika tanpa gejala).";
+ const secondJawaban =
+ "Tanpa gejala / derajat ringan tidak perlu dirawat di rumah sakit. Derajat sedang / berat lebih baik dirawat di rumah sakit.";
+
+ render();
+
+ setTimeout(() => {
+ expect(screen.getByText(firstJawaban)).toBeVisible();
+
+ userEvent.type(
+ screen.getByRole("textbox", {
+ name: /cari pertanyaan:/i,
+ }),
+ secondJawaban,
+ );
+ userEvent.click(
+ screen.getByRole("button", {
+ name: /cari/i,
+ }),
+ );
+
+ expect(screen.queryByText(firstJawaban)).not.toBeInTheDocument();
+ expect(screen.getByText(secondJawaban)).toBeVisible();
+ }, 200);
});
it("performs the filter functionality correctly", () => {
- const firstFaq = faqBuilder();
- const secondFaq = faqBuilder();
-
- render();
-
- expect(screen.getByText(firstFaq.jawaban)).toBeVisible();
-
- userEvent.selectOptions(
- screen.getByRole("combobox", {
- name: /kategori pertanyaan/i,
- }),
- secondFaq.kategori_pertanyaan,
- );
-
- expect(screen.queryByText(firstFaq.jawaban)).not.toBeInTheDocument();
- expect(screen.getByText(secondFaq.jawaban)).toBeVisible();
- });
-
- it("performs empty state correctly", () => {
- const firstFaq = faqBuilder();
- const secondFaq = faqBuilder();
-
- render();
-
- expect(screen.getByText(firstFaq.jawaban)).toBeVisible();
-
- userEvent.type(
- screen.getByRole("textbox", {
- name: /cari pertanyaan:/i,
- }),
- secondFaq.jawaban,
- );
- userEvent.click(
- screen.getByRole("button", {
- name: /cari/i,
- }),
- );
-
- expect(screen.queryByText(firstFaq.jawaban)).not.toBeInTheDocument();
- expect(screen.getByText(/Pertanyaan tidak ditemukan/i)).toBeVisible();
- });
-});
-
-describe("getStaticProps", () => {
- it("returns the props from the faq-sheets correctly", () => {
- expect(getStaticProps({})).toEqual({
- props: { faqs },
- });
+ const firstJawaban =
+ "Kontrol di Fasilitas Kesehatan Tingkat Pertama (Puskesmas) setelah 10 hari karantina untuk pemantauan klinis (jika tanpa gejala).";
+ const secondKategori = "Kontak Erat";
+ const secondJawaban = "2-14 hari";
+
+ render();
+
+ setTimeout(() => {
+ expect(screen.getByText(firstJawaban)).toBeVisible();
+
+ userEvent.selectOptions(
+ screen.getByRole("combobox", {
+ name: /kategori pertanyaan/i,
+ }),
+ secondKategori,
+ );
+
+ expect(screen.queryByText(firstJawaban)).not.toBeInTheDocument();
+ expect(screen.getByText(secondJawaban)).toBeVisible();
+ }, 200);
});
});
diff --git a/components/faq-list.tsx b/components/faq-list.tsx
new file mode 100644
index 000000000..1f2f21b72
--- /dev/null
+++ b/components/faq-list.tsx
@@ -0,0 +1,98 @@
+import { ExclamationCircleIcon } from "@heroicons/react/solid";
+import htmr from "htmr";
+import { useMemo } from "react";
+import { EmptyState } from "~/components/ui/empty-state";
+import { FaqListSkeleton } from "~/components/ui/skeleton-loading";
+import { Faq } from "~/lib/data/faqs";
+
+type FaqListProps = {
+ data: Faq[];
+ isLoading: boolean;
+};
+
+function groupBy(data: T[], key: U) {
+ return data.reduce((acc: any, currentValue: any) => {
+ const groupKey = currentValue[key];
+ if (!acc[groupKey]) {
+ acc[groupKey] = [];
+ }
+ acc[groupKey].push(currentValue);
+ return acc;
+ }, {});
+}
+
+export function FAQList(props: FaqListProps) {
+ const { data, isLoading } = props;
+ const listFaqs = useMemo(() => {
+ return groupBy(data, "kategori_pertanyaan");
+ }, [data]);
+ const listFaqsKeys = Object.keys(listFaqs as Record);
+ return (
+
+ {listFaqsKeys.map((category: string) => (
+
+
+
+ {listFaqs[category].map((question: Faq) => (
+
+
-
+ {question.pertanyaan}
+
+
-
+
+ {htmr(question.jawaban.replace(/\n/g, "
"))}
+
+
+ Sumber:{" "}
+ {question.link ? (
+
+ {question.sumber}
+
+ ) : (
+ question.sumber
+ )}
+
+
+
+ ))}
+
+
+ ))}
+
+ {listFaqsKeys.length === 0 && !isLoading && (
+
+
+
+ )}
+
+ {isLoading &&
}
+
+ );
+}
diff --git a/components/search/custom-hits.tsx b/components/search/custom-hits.tsx
new file mode 100644
index 000000000..42116927e
--- /dev/null
+++ b/components/search/custom-hits.tsx
@@ -0,0 +1,18 @@
+import { StateResultsProvided } from "react-instantsearch-core";
+import { connectStateResults } from "react-instantsearch-dom";
+import { FAQList } from "~/components/faq-list";
+import { Faq } from "~/lib/data/faqs";
+
+function Hits(stateResults: StateResultsProvided) {
+ const { searchResults, isSearchStalled } = stateResults;
+ let results: Faq[];
+
+ try {
+ results = searchResults.hits as unknown as Faq[];
+ } catch (e) {
+ results = [];
+ }
+ return ;
+}
+
+export default connectStateResults(Hits);
diff --git a/components/search/custom-instant-search.tsx b/components/search/custom-instant-search.tsx
new file mode 100644
index 000000000..160821874
--- /dev/null
+++ b/components/search/custom-instant-search.tsx
@@ -0,0 +1,206 @@
+import React, { useCallback, useState } from "react";
+
+import { useRouter } from "next/router";
+import {
+ Configure,
+ connectRefinementList,
+ SearchState,
+} from "react-instantsearch-core";
+import {
+ InstantSearch,
+ InstantSearchProps,
+ RefinementItem,
+} from "react-instantsearch-dom";
+import { debounce } from "ts-debounce";
+import CustomHits from "~/components/search/custom-hits";
+import CustomRefinementList from "~/components/search/custom-refinement-list";
+import CustomSearchBox from "~/components/search/custom-search-box";
+import { RefinementModal } from "~/components/search/refinement-modal";
+import { getQueryParams } from "~/lib/string-utils";
+
+type FilterSetting = {
+ field: string;
+ title: string;
+};
+
+interface CustomInstantSearchProps extends InstantSearchProps {
+ itemName: string;
+ filterSettings?: FilterSetting[];
+ withFilterModal?: boolean;
+}
+
+const DEBOUNCE_TIME = 300;
+const VirtualRefinementList = connectRefinementList(() => null);
+
+export function CustomInstantSearch({
+ itemName,
+ filterSettings,
+ indexName,
+ searchClient,
+ withFilterModal = false,
+}: CustomInstantSearchProps) {
+ const router = useRouter();
+ const urlToSearchState = () => {
+ const searchParams: SearchState = {};
+ if (typeof window !== "undefined") {
+ const queryParams: {} = getQueryParams(window.location.search);
+ if (Object.keys(queryParams).length) {
+ let keywordsParam: string = "";
+ const filtersParam: { [key: string]: string[] } = {};
+ const filterFields = filterSettings?.map((cur) => cur.field) ?? [];
+ Object.entries(queryParams).forEach(([key, value]) => {
+ if (key == "q") {
+ keywordsParam = value as string;
+ if (keywordsParam) {
+ searchParams.query = keywordsParam;
+ }
+ } else if (key == "sort") {
+ const sortParam: string = value as string;
+ if (sortParam) {
+ searchParams.sort = sortParam;
+ }
+ } else if (filterFields.includes(key)) {
+ if (value) {
+ filtersParam[key] = [value as string];
+ }
+ }
+ });
+ if (Object.keys(filtersParam as {}).length) {
+ searchParams.refinementList = filtersParam;
+ }
+ }
+ }
+ return searchParams;
+ };
+
+ const [searchState, setSearchState] = useState(urlToSearchState());
+ const [refinementList, setRefinementList] = useState<{
+ [key: string]: string[];
+ }>({});
+ const [isFilterModalOpen, setFilterModalOpen] = useState(false);
+
+ const createURL = (state: SearchState) => {
+ let isDefaultRoute: boolean = !state.query && state.page === 1;
+ if (filterSettings?.length) {
+ filterSettings.forEach((filterSetting) => {
+ isDefaultRoute =
+ isDefaultRoute &&
+ state.refinementList?.[filterSetting.field].length === 0;
+ });
+ }
+
+ if (isDefaultRoute) {
+ return "";
+ }
+
+ const queryParameters: string[] = [];
+
+ if (state.query) {
+ queryParameters.push(`q=${encodeURIComponent(state.query)}`);
+ }
+ if (filterSettings?.length) {
+ filterSettings.forEach((filterSetting) => {
+ if (state.refinementList?.[filterSetting.field]) {
+ queryParameters.push(
+ `${filterSetting.field}=${encodeURIComponent(
+ state.refinementList[filterSetting.field][0],
+ )}`,
+ );
+ }
+ });
+ }
+
+ return `${window.location.pathname}${
+ queryParameters.length ? `?${queryParameters.join("&")}` : ``
+ }`;
+ };
+
+ const searchStateToUrl = (searchStateParam: SearchState) =>
+ createURL(searchStateParam);
+
+ const debouncedUpdateUrlParams = useCallback(
+ debounce(
+ (updatedSearchState: SearchState) =>
+ router.push(searchStateToUrl(updatedSearchState), undefined, {
+ shallow: true,
+ }),
+ DEBOUNCE_TIME,
+ ),
+ [],
+ );
+
+ const onSearchStateChange = (updatedSearchState: SearchState) => {
+ if (withFilterModal) {
+ if (isFilterModalOpen && updatedSearchState.refinementList) {
+ setRefinementList(
+ updatedSearchState.refinementList as { [key: string]: string[] },
+ );
+ } else {
+ updatedSearchState.refinementList = refinementList;
+ }
+ }
+ void debouncedUpdateUrlParams(updatedSearchState);
+ setSearchState(updatedSearchState);
+ };
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/components/search/custom-refinement-list.tsx b/components/search/custom-refinement-list.tsx
new file mode 100644
index 000000000..6f331dc4e
--- /dev/null
+++ b/components/search/custom-refinement-list.tsx
@@ -0,0 +1,47 @@
+import { ChangeEvent } from "react";
+
+import {
+ connectRefinementList,
+ RefinementListProvided,
+} from "react-instantsearch-core";
+import { FormLabel } from "~/components/ui/forms/form-label";
+import { InputSelect } from "~/components/ui/forms/input-select";
+
+interface CustomRefinementListProvided extends RefinementListProvided {
+ readonly title: string;
+}
+
+function RefinementList({
+ title,
+ items,
+ currentRefinement,
+ refine,
+}: CustomRefinementListProvided) {
+ function handleFilterChange(event: ChangeEvent) {
+ const filterValue = event.target.value;
+ if (filterValue) {
+ refine([filterValue]);
+ } else {
+ refine([]);
+ }
+ }
+
+ return (
+
+ {title}
+
+
+ {items.map((item) => (
+
+ ))}
+
+
+ );
+}
+
+export default connectRefinementList(RefinementList);
diff --git a/components/search/custom-search-box.tsx b/components/search/custom-search-box.tsx
new file mode 100644
index 000000000..33f11ea37
--- /dev/null
+++ b/components/search/custom-search-box.tsx
@@ -0,0 +1,56 @@
+import React from "react";
+
+import { FilterIcon } from "@heroicons/react/outline";
+import { SearchBoxProvided } from "react-instantsearch-core";
+import { connectSearchBox } from "react-instantsearch-dom";
+import { FormGroup } from "~/components/ui/forms/form-group";
+import { FormLabel } from "~/components/ui/forms/form-label";
+import { InputText } from "~/components/ui/forms/input-text";
+
+interface CustomSearchBoxProvided extends SearchBoxProvided {
+ readonly itemName: string;
+ readonly placeholderText?: string;
+ readonly hasFilter?: boolean;
+ readonly onFilterButtonClick?: () => void;
+}
+
+function SearchBox({
+ currentRefinement,
+ refine,
+ itemName,
+ placeholderText,
+ hasFilter,
+ onFilterButtonClick,
+}: CustomSearchBoxProvided) {
+ return (
+
+ Cari {itemName}:
+
+ refine(e.currentTarget.value)}
+ placeholder={placeholderText}
+ type="search"
+ value={currentRefinement}
+ />
+ {hasFilter && onFilterButtonClick && (
+
+ )}
+
+
+ );
+}
+
+export default connectSearchBox(SearchBox);
diff --git a/components/search/refinement-modal.tsx b/components/search/refinement-modal.tsx
new file mode 100644
index 000000000..51d28b187
--- /dev/null
+++ b/components/search/refinement-modal.tsx
@@ -0,0 +1,117 @@
+import { Fragment, useRef } from "react";
+
+import { Dialog, Transition } from "@headlessui/react";
+import { XIcon } from "@heroicons/react/solid";
+import { RefinementItem } from "react-instantsearch-dom";
+import CustomRefinementList from "~/components/search/custom-refinement-list";
+
+type FilterSetting = {
+ field: string;
+ title: string;
+};
+
+export interface RefinementModalProps {
+ isOpen: boolean;
+ onToggle: (value: boolean) => void;
+ filterSettings?: FilterSetting[];
+ defaultRefinementList?: { [key: string]: string[] };
+}
+
+export function RefinementModal({
+ isOpen,
+ onToggle,
+ filterSettings,
+ defaultRefinementList,
+}: RefinementModalProps) {
+ const closeButtonRef = useRef(null);
+ const renderFilterForms = () => {
+ return (
+ <>
+ {filterSettings?.length && (
+
+ {filterSettings.map((filterSetting, idx) => (
+ []) =>
+ items.sort((a, b) => a.label.localeCompare(b.label))
+ }
+ />
+ ))}
+
+ )}
+ >
+ );
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/lib/typesense.ts b/lib/typesense.ts
new file mode 100644
index 000000000..03592708a
--- /dev/null
+++ b/lib/typesense.ts
@@ -0,0 +1,27 @@
+import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
+
+export function typesenseSearch({
+ queryBy,
+ defaultSort,
+}: {
+ queryBy: string[];
+ defaultSort?: string;
+}) {
+ const searchAdapter = new TypesenseInstantSearchAdapter({
+ server: {
+ apiKey: "FByczfHEjsTCihgkkYlq2YbAgUKMoyVP",
+ nodes: [
+ {
+ host: "public-api.trustmedis.id",
+ port: "443",
+ protocol: "https",
+ },
+ ],
+ },
+ additionalSearchParameters: {
+ queryBy: queryBy.join(),
+ sortBy: defaultSort ? defaultSort : "",
+ },
+ });
+ return searchAdapter.searchClient;
+}
diff --git a/package.json b/package.json
index 898e7aa47..2558dfe0c 100644
--- a/package.json
+++ b/package.json
@@ -41,8 +41,11 @@
"preact": "^10.5.14",
"react": "17.0.2",
"react-dom": "17.0.2",
+ "react-instantsearch-core": "^6.12.0",
+ "react-instantsearch-dom": "^6.12.0",
"ts-debounce": "^3.0.0",
- "typeface-inter": "^3.18.1"
+ "typeface-inter": "^3.18.1",
+ "typesense-instantsearch-adapter": "^2.0.1"
},
"devDependencies": {
"@babel/eslint-plugin": "7.14.5",
@@ -61,6 +64,7 @@
"@types/nprogress": "^0.2.0",
"@types/prettier": "^2.3.2",
"@types/react": "17.0.13",
+ "@types/react-instantsearch-dom": "^6.12.0",
"@typescript-eslint/parser": "^4.28.5",
"autoprefixer": "10.3.0",
"chalk": "^4.1.1",
diff --git a/pages/faq.tsx b/pages/faq.tsx
index 806d7b3ed..2d3f58d46 100644
--- a/pages/faq.tsx
+++ b/pages/faq.tsx
@@ -1,64 +1,21 @@
-import { useMemo } from "react";
-
-import { ExclamationCircleIcon } from "@heroicons/react/solid";
-import htmr from "htmr";
-import { GetStaticProps } from "next";
import { NextSeo } from "next-seo";
import { BackButton } from "~/components/layout/back-button";
import { Page } from "~/components/layout/page";
import { PageContent } from "~/components/layout/page-content";
import { PageHeader } from "~/components/layout/page-header";
-import { SearchForm } from "~/components/search-form";
-import { EmptyState } from "~/components/ui/empty-state";
-import { FaqListSkeleton } from "~/components/ui/skeleton-loading";
-import faqs, { Faq } from "~/lib/data/faqs";
-import { useSearch } from "~/lib/hooks/use-search";
-
-type FaqPageProps = {
- faqs: Faq[];
-};
-
-function groupBy(data: T[], key: U) {
- return data.reduce((acc: any, currentValue: any) => {
- const groupKey = currentValue[key];
- if (!acc[groupKey]) {
- acc[groupKey] = [];
- }
- acc[groupKey].push(currentValue);
- return acc;
- }, {});
-}
+import { CustomInstantSearch } from "~/components/search/custom-instant-search";
+import { typesenseSearch } from "~/lib/typesense";
const meta = {
title: "Pertanyaan yang sering ditanyakan",
};
-export default function FaqPage(props: FaqPageProps) {
- const { faqs: faq } = props;
- const [
- filteredQuestions,
- handleSubmitKeywords,
- urlParams,
- filterItems,
- isLoading,
- ] = useSearch({
- items: faq,
- fieldNames: ["kategori_pertanyaan", "pertanyaan", "jawaban"],
- aggregationSettings: [
- { field: "kategori_pertanyaan", title: "Kategori Pertanyaan" },
- ],
+export default function FaqPage() {
+ const searchClient = typesenseSearch({
+ queryBy: ["kategori_pertanyaan", "pertanyaan", "jawaban"],
+ defaultSort: "order:asc",
});
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const listFaqs = useMemo(() => {
- return groupBy(
- filteredQuestions,
- "kategori_pertanyaan",
- );
- }, [filteredQuestions]);
-
- const listFaqsKeys = Object.keys(listFaqs as Record);
-
return (
@@ -74,89 +31,15 @@ export default function FaqPage(props: FaqPageProps) {
title="Pertanyaan yang sering ditanyakan"
/>
-
-
-
- {listFaqsKeys.map((category: string) => (
-
-
-
- {listFaqs[category].map((question: Faq) => (
-
-
-
- {question.pertanyaan}
-
-
-
-
- {htmr(question.jawaban)}
-
-
- Sumber:{" "}
- {question.link ? (
-
- {question.sumber}
-
- ) : (
- question.sumber
- )}
-
-
-
- ))}
-
-
- ))}
-
- {listFaqsKeys.length === 0 && !isLoading && (
-
-
-
- )}
-
- {isLoading &&
}
-
);
}
-
-export const getStaticProps: GetStaticProps = () => {
- return {
- props: {
- faqs,
- },
- };
-};
diff --git a/yarn.lock b/yarn.lock
index 2f519e6ee..51e50a636 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,110 @@
# yarn lockfile v1
+"@algolia/cache-browser-local-storage@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.10.3.tgz#3bf81e0f66a4a1079a75914a987eb1ef432c7c68"
+ integrity sha512-TD1N7zg5lb56/PLjjD4bBl2eccEvVHhC7yfgFu2r9k5tf+gvbGxEZ3NhRZVKu2MObUIcEy2VR4LVLxOQu45Hlg==
+ dependencies:
+ "@algolia/cache-common" "4.10.3"
+
+"@algolia/cache-common@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.10.3.tgz#311b2b5ae06d55300f4230944c99bc39ad15847d"
+ integrity sha512-q13cPPUmtf8a2suBC4kySSr97EyulSXuxUkn7l1tZUCX/k1y5KNheMp8npBy8Kc8gPPmHpacxddRSfOncjiKFw==
+
+"@algolia/cache-in-memory@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.10.3.tgz#697e4994538426272ea29ccf2b32b46ea4c48862"
+ integrity sha512-JhPajhOXAjUP+TZrZTh6KJpF5VKTKyWK2aR1cD8NtrcVHwfGS7fTyfXfVm5BqBqkD9U0gVvufUt/mVyI80aZww==
+ dependencies:
+ "@algolia/cache-common" "4.10.3"
+
+"@algolia/client-account@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.10.3.tgz#f2cbefb1abce74c341115607d6af199df1b056ae"
+ integrity sha512-S/IsJB4s+e1xYctdpW3nAbwrR2y3pjSo9X21fJGoiGeIpTRdvQG7nydgsLkhnhcgAdLnmqBapYyAqMGmlcyOkg==
+ dependencies:
+ "@algolia/client-common" "4.10.3"
+ "@algolia/client-search" "4.10.3"
+ "@algolia/transporter" "4.10.3"
+
+"@algolia/client-analytics@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.10.3.tgz#43d934ef8df0cf551c78e6b2e9f2452e7fb27d93"
+ integrity sha512-vlHTbBqJktRgclh3v7bPQLfZvFIqY4erNFIZA5C7nisCj9oLeTgzefoUrr+R90+I+XjfoLxnmoeigS1Z1yg1vw==
+ dependencies:
+ "@algolia/client-common" "4.10.3"
+ "@algolia/client-search" "4.10.3"
+ "@algolia/requester-common" "4.10.3"
+ "@algolia/transporter" "4.10.3"
+
+"@algolia/client-common@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.10.3.tgz#c4257dd5c57c5c8ec4bd48a7b1897573e372d403"
+ integrity sha512-uFyP2Z14jG2hsFRbAoavna6oJf4NTXaSDAZgouZUZlHlBp5elM38sjNeA5HR9/D9J/GjwaB1SgB7iUiIWYBB4w==
+ dependencies:
+ "@algolia/requester-common" "4.10.3"
+ "@algolia/transporter" "4.10.3"
+
+"@algolia/client-personalization@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.10.3.tgz#58c800f90ab8ab4aa29abdf29a97e89e6bda419e"
+ integrity sha512-NS7Nx8EJ/nduGXT8CFo5z7kLF0jnFehTP3eC+z+GOEESH3rrs7uR12IZHxv5QhQswZa9vl925zCOZDcDVoENCg==
+ dependencies:
+ "@algolia/client-common" "4.10.3"
+ "@algolia/requester-common" "4.10.3"
+ "@algolia/transporter" "4.10.3"
+
+"@algolia/client-search@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.10.3.tgz#aa6b02c2d528cb264830f276739b7f68b58988ef"
+ integrity sha512-Zwnp2G94IrNFKWCG/k7epI5UswRkPvL9FCt7/slXe2bkjP2y/HA37gzRn+9tXoLVRwd7gBzrtOA4jFKIyjrtVw==
+ dependencies:
+ "@algolia/client-common" "4.10.3"
+ "@algolia/requester-common" "4.10.3"
+ "@algolia/transporter" "4.10.3"
+
+"@algolia/logger-common@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.10.3.tgz#6773d2e38581bf9ac57e2dda02f0c4f1bc72ce94"
+ integrity sha512-M6xi+qov2bkgg1H9e1Qtvq/E/eKsGcgz8RBbXNzqPIYoDGZNkv+b3b8YMo3dxd4Wd6M24HU1iqF3kmr1LaXndg==
+
+"@algolia/logger-console@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.10.3.tgz#bd8bdc1f9dba89db37be25d673ac1f2e68de7913"
+ integrity sha512-vVgRI7b4PHjgBdRkv/cRz490twvkLoGdpC4VYzIouSrKj8SIVLRhey3qgXk7oQXi3xoxVAv6NrklHfpO8Bpx0w==
+ dependencies:
+ "@algolia/logger-common" "4.10.3"
+
+"@algolia/requester-browser-xhr@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.10.3.tgz#81ae8f6caf562a28f96102f03da7f4b19bba568c"
+ integrity sha512-4WIk1zreFbc1EF6+gsfBTQvwSNjWc20zJAAExRWql/Jq5yfVHmwOqi/CajA53/cXKFBqo80DAMRvOiwP+hOLYw==
+ dependencies:
+ "@algolia/requester-common" "4.10.3"
+
+"@algolia/requester-common@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.10.3.tgz#c3112393cff97be79863bc28de76f9c69b2f5a95"
+ integrity sha512-PNfLHmg0Hujugs3rx55uz/ifv7b9HVdSFQDb2hj0O5xZaBEuQCNOXC6COrXR8+9VEfqp2swpg7zwgtqFxh+BtQ==
+
+"@algolia/requester-node-http@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.10.3.tgz#75ea7805ac0ba25a1124989d8632ef39c31441c1"
+ integrity sha512-A9ZcGfEvgqf0luJApdNcIhsRh6MShn2zn2tbjwjGG1joF81w+HUY+BWuLZn56vGwAA9ZB9n00IoJJpxibbfofg==
+ dependencies:
+ "@algolia/requester-common" "4.10.3"
+
+"@algolia/transporter@4.10.3":
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.10.3.tgz#0aeee752923957cffe63e4cf1c7a22ca48d96dde"
+ integrity sha512-n1lRyKDbrckbMEgm7QXtj3nEWUuzA3aKLzVQ43/F/RCFib15j4IwtmYhXR6OIBRSc7+T0Hm48S0J6F+HeYCQkw==
+ dependencies:
+ "@algolia/cache-common" "4.10.3"
+ "@algolia/logger-common" "4.10.3"
+ "@algolia/requester-common" "4.10.3"
+
"@aws-crypto/crc32@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-1.0.0.tgz#6a0164fd92bb365860ba6afb5dfef449701eb8ca"
@@ -1110,6 +1214,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.9.2":
+ version "7.14.8"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446"
+ integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.9.6":
version "7.14.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
@@ -1117,13 +1228,6 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.9.2":
- version "7.14.8"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446"
- integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==
- dependencies:
- regenerator-runtime "^0.13.4"
-
"@babel/template@^7.14.5", "@babel/template@^7.3.3":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4"
@@ -2166,6 +2270,32 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
+"@types/react-instantsearch-core@*":
+ version "6.10.4"
+ resolved "https://registry.yarnpkg.com/@types/react-instantsearch-core/-/react-instantsearch-core-6.10.4.tgz#daac0857600d486c2ecfd41aa5127dccfe9d8927"
+ integrity sha512-Qdc7h2bkXaVljbNL2wg0hRLN7FLTi7NeaD+VLvjF8A5M41olgEIIzdoDye+OfiB/COk4ynXbw0bqxhhNUA6gxw==
+ dependencies:
+ "@types/react" "*"
+ algoliasearch ">=4"
+ algoliasearch-helper ">=3"
+
+"@types/react-instantsearch-dom@^6.12.0":
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/@types/react-instantsearch-dom/-/react-instantsearch-dom-6.12.0.tgz#716be1b48193bbb65271205a9bd112a65d8a2661"
+ integrity sha512-O08H+ye4e4kEnYHmMrov9FPNRDJwfCWthNZf4aztqahpU8LSbAiuFQGVy84SHUvg/jfNcG4333SsVnAQLtbS7A==
+ dependencies:
+ "@types/react" "*"
+ "@types/react-instantsearch-core" "*"
+
+"@types/react@*":
+ version "17.0.15"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.15.tgz#c7533dc38025677e312606502df7656a6ea626d0"
+ integrity sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
"@types/react@17.0.13":
version "17.0.13"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.13.tgz#6b7c9a8f2868586ad87d941c02337c6888fb874f"
@@ -2554,6 +2684,33 @@ ajv@^8.0.1:
require-from-string "^2.0.2"
uri-js "^4.2.2"
+algoliasearch-helper@>=3, algoliasearch-helper@^3.5.3:
+ version "3.5.4"
+ resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.5.4.tgz#21b20ab8a258daa9dde9aef2daa5e8994cd66077"
+ integrity sha512-t+FLhXYnPZiwjYe5ExyN962HQY8mi3KwRju3Lyf6OBgtRdx30d6mqvtClXf5NeBihH45Xzj6t4Y5YyvAI432XA==
+ dependencies:
+ events "^1.1.1"
+
+algoliasearch@>=4:
+ version "4.10.3"
+ resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.10.3.tgz#22df4bb02fbf13a765b18b85df8745ee9c04f00a"
+ integrity sha512-OLY0AWlPKGLbSaw14ivMB7BT5fPdp8VdzY4L8FtzZnqmLKsyes24cltGlf7/X96ACkYEcT390SReCDt/9SUIRg==
+ dependencies:
+ "@algolia/cache-browser-local-storage" "4.10.3"
+ "@algolia/cache-common" "4.10.3"
+ "@algolia/cache-in-memory" "4.10.3"
+ "@algolia/client-account" "4.10.3"
+ "@algolia/client-analytics" "4.10.3"
+ "@algolia/client-common" "4.10.3"
+ "@algolia/client-personalization" "4.10.3"
+ "@algolia/client-search" "4.10.3"
+ "@algolia/logger-common" "4.10.3"
+ "@algolia/logger-console" "4.10.3"
+ "@algolia/requester-browser-xhr" "4.10.3"
+ "@algolia/requester-common" "4.10.3"
+ "@algolia/requester-node-http" "4.10.3"
+ "@algolia/transporter" "4.10.3"
+
anser@1.4.9:
version "1.4.9"
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760"
@@ -3677,6 +3834,11 @@ classnames@2.2.6:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
+classnames@^2.2.5:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
+ integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
+
clean-stack@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
@@ -5411,6 +5573,11 @@ eventemitter2@^6.4.3:
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.4.tgz#aa96e8275c4dbeb017a5d0e03780c65612a1202b"
integrity sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw==
+events@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
+ integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
+
events@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
@@ -8621,6 +8788,11 @@ logalot@^2.0.0:
figures "^1.3.5"
squeak "^1.0.0"
+loglevel@^1.7.1:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
+ integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
+
longest-streak@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
@@ -10489,7 +10661,7 @@ prompts@^2.0.1, prompts@^2.2.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@15.7.2, prop-types@^15.7.2:
+prop-types@15.7.2, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -10744,6 +10916,33 @@ react-dom@17.0.2:
object-assign "^4.1.1"
scheduler "^0.20.2"
+react-fast-compare@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
+ integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
+
+react-instantsearch-core@^6.12.0:
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/react-instantsearch-core/-/react-instantsearch-core-6.12.0.tgz#f4bd044c0b5939d2129b95d3bf298e407a41a2b5"
+ integrity sha512-k7/sQLai5fhhiQxKFt7e3lA13SJpp843raQ6f8EtJL/bdFZPDHnOS3fPrxV/sduBkIrX7Hv+DrEOY1KkJGu2sA==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+ algoliasearch-helper "^3.5.3"
+ prop-types "^15.6.2"
+ react-fast-compare "^3.0.0"
+
+react-instantsearch-dom@^6.12.0:
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/react-instantsearch-dom/-/react-instantsearch-dom-6.12.0.tgz#fb2a95643cfa896b4199131e70d0de308baaf932"
+ integrity sha512-/UMnmX0SPkvT8yOL2mAAfjz89ohGSZScb9X6hql9hydcS94cGoojysIUHJ/C+/1f8ciF2zni6I8AYddMdaqrSg==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+ algoliasearch-helper "^3.5.3"
+ classnames "^2.2.5"
+ prop-types "^15.6.2"
+ react-fast-compare "^3.0.0"
+ react-instantsearch-core "^6.12.0"
+
react-intersection-observer@8.32.0:
version "8.32.0"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.32.0.tgz#47249332e12e8bb99ed35a10bb7dd10446445a7b"
@@ -12554,6 +12753,21 @@ typescript@4.3.5:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
+typesense-instantsearch-adapter@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/typesense-instantsearch-adapter/-/typesense-instantsearch-adapter-2.0.1.tgz#51c70120d36e05f698c4dd962f9347454d536c45"
+ integrity sha512-LsP0rb9pcZdEQr1QBH9g5kD34Us5qWIUAlyRy/vxy2KOT5wOUH28ElsByaZ6gFmtIyC9iOeGMigR9LQv3EW+Xg==
+ dependencies:
+ typesense "^0.14.0"
+
+typesense@^0.14.0:
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/typesense/-/typesense-0.14.0.tgz#c7a61ccadcd5186e3700a973188d6b93648b2fdd"
+ integrity sha512-9ZLkjwhCYXCZuYmvS6En5hJm3AMhFZMbZ7hRMVFJlyK3essNqd8MSWEt2bpAvbvTN+1FgGLBQPJnpYdsbkVsKg==
+ dependencies:
+ axios "^0.21.1"
+ loglevel "^1.7.1"
+
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"