;
+
+const Layout = ({
+ header,
+ sideContent,
+ bodyContent,
+ bodyHeader,
+ bodyFooter,
+}: LayoutProps) => {
+ const { hiddenBody } = useIsInitialStateWithoutFilter();
+
+ const { sidebarToggleManager } = useUIContext();
+
+ return (
+
+
+ {!hiddenBody && (
+
+
+
+
+
+
+ {bodyContent}
+ {bodyFooter}
+
+
+ )}
+
+ );
+};
+
+export default Layout;
diff --git a/src/layout/Metadata.tsx b/src/layout/Metadata.tsx
new file mode 100644
index 00000000..bcfe7f7f
--- /dev/null
+++ b/src/layout/Metadata.tsx
@@ -0,0 +1,51 @@
+import Head from "next/head";
+import Script from "next/script";
+import React from "react";
+
+const Metadata = () => {
+ return (
+ <>
+
+ Bitcoin Search
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Metadata;
diff --git a/src/layout/SideBar.tsx b/src/layout/SideBar.tsx
index 39efef3f..11b25804 100644
--- a/src/layout/SideBar.tsx
+++ b/src/layout/SideBar.tsx
@@ -1,60 +1,65 @@
-import { Sorting } from "@elastic/react-search-ui-views";
-
import React from "react";
import CustomMultiCheckboxFacet from "../components/customMultiCheckboxFacet/CustomMultiCheckboxFacet";
import { getFacetFields, getFacetWithSearch } from "../config/config-helper";
-import useIsInitialStateWithoutFilter from "../hooks/useIsInitialStateWithoutFilter";
import Facet from "@/components/sidebarFacet/Facet";
import SortingFacet from "@/components/sidebarFacet/SortingFacet";
+import SortingView from "@/components/sidebarFacet/Sorting/SortingView";
+import ResultSize from "@/components/sidebarFacet/ResultSize";
+import FilterMenu from "@/components/sidebarFacet/FilterMenu";
+import ShowFilterResultsMobile from "@/components/sidebarFacet/ShowFilterResultsMobile";
+import useUIContext from "@/hooks/useUIContext";
const SideBar = () => {
- const { hiddenBody, hiddenHomeFacet } = useIsInitialStateWithoutFilter();
+ const { sidebarToggleManager } = useUIContext();
+ const isMobile = window
+ ? window.matchMedia("(max-width: 600px)").matches
+ : false;
- if (hiddenBody) {
- return null;
- }
+ const sortCallback = () => {
+ if (isMobile) {
+ sidebarToggleManager.updater(false);
+ }
+ };
+ const facetCallback = () => {
+ if (isMobile) {
+ sidebarToggleManager.updater(false);
+ }
+ };
return (
-
- {hiddenHomeFacet
- ? getFacetFields().map((field) => (
-
- ))
- : getFacetFields()
- .filter((field) => !getFacetWithSearch().includes(field))
- .map((field) => (
-
- ))}
+
+
+
+
+
+ {getFacetFields().map((field) => (
+
+ ))}
+
);
};
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
new file mode 100644
index 00000000..a6d223ba
--- /dev/null
+++ b/src/pages/404.tsx
@@ -0,0 +1,37 @@
+import Link from "next/link";
+
+import Footer from "@/components/footer/Footer";
+import NavBar from "@/components/navBar/NavBar";
+
+export default function Custom404() {
+ return (
+
+
+
+
+
+
+ 404 - Page Not Found
+
+
+ The page you are looking for might have been removed, had its name
+ changed or is temporarily unavailable.
+
+
+ Go to Homepage
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 85fa2230..01427ff9 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -1,3 +1,9 @@
+import "@fontsource/geist-sans/400.css";
+import "@fontsource/geist-sans/500.css";
+import "@fontsource/geist-sans/600.css";
+import "@fontsource/geist-sans/700.css";
+import "@fontsource/geist-sans/800.css";
+
import "../styles/globals.css";
import "../styles/custom.scss";
import "../components/customResults/styles.results.scss";
@@ -6,20 +12,24 @@ import "../components/footer/footer.scss";
import "../components/loadingBar/loadingBar.scss";
import "../components/noResultsCard/noResults.scss";
import { ChakraProvider } from "@chakra-ui/react";
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import {
+ Hydrate,
+ QueryClient,
+ QueryClientProvider,
+} from "@tanstack/react-query";
import { SearchQueryProvider } from "@/context/SearchQueryContext";
import {
buildAutocompleteQueryConfig,
- buildFacetConfigFromConfig,
- buildSearchOptionsFromConfig,
getConfig,
} from "@/config/config-helper";
import AppSearchAPIConnector from "@elastic/search-ui-app-search-connector";
import { SearchProvider } from "@elastic/react-search-ui";
import theme from "@/chakra/chakra-theme";
import { SearchDriverOptions } from "@elastic/search-ui";
-import Head from "next/head";
-import Script from 'next/script';
+import { UIContextProvider } from "@/context/UIContext";
+import { ThemeProvider } from "@/context/Theme";
+import Metadata from "@/layout/Metadata";
+import ErrorBoundary from "@/components/errorBoundary/ErrorBoundary";
const queryClient = new QueryClient();
@@ -44,26 +54,25 @@ const config: SearchDriverOptions = {
export default function App({ Component, pageProps }) {
return (
-
-
-
-
-
-
-
-
- Bitcoin Search
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
);
}
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
index 78357586..ffc3f3cc 100644
--- a/src/pages/_document.tsx
+++ b/src/pages/_document.tsx
@@ -1,13 +1,13 @@
-import { Html, Head, Main, NextScript } from 'next/document'
+import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
-
+
- {isLoading &&
}
-
-
-
Technical Bitcoin Search
-
-
}
- sideContent={
}
- bodyContent={
}
- bodyHeader={
}
- bodyFooter={
}
- />
- {noResult &&
}
-
+
+
+ {isLoading && }
+
+
+
+ }
+ sideContent={}
+ bodyContent={}
+ bodyHeader={}
+ bodyFooter={}
+ />
+ {noResult && }
+
+
+
+ {NoResults && isHomePage &&
}
+
);
-}
+};
+
+export default App;
diff --git a/src/service/URLManager/helper.ts b/src/service/URLManager/helper.ts
index c93d73c0..8cae19b3 100644
--- a/src/service/URLManager/helper.ts
+++ b/src/service/URLManager/helper.ts
@@ -1,35 +1,45 @@
-import { urlParamsPrefix } from "@/config/config"
-import { getFacetFields, getSortFields } from "@/config/config-helper"
-import { Facet } from "@/types"
+import { URLSearchParamsKeyword } from "@/config/config";
+import { getFacetFields, getSortFields } from "@/config/config-helper";
+import { Facet } from "@/types";
export const appendFilterName = (filterType: string) => {
- return `${urlParamsPrefix.FILTER}_${filterType}`
-}
+ return `${URLSearchParamsKeyword.FILTER}_${filterType}`;
+};
export const appendSortName = (sortField: string) => {
- return `${urlParamsPrefix.SORT}_${sortField}`
-}
+ return `${URLSearchParamsKeyword.SORT}_${sortField}`;
+};
export function generateFilterQuery(searchParams: string) {
const filterList: Facet[] = [];
- const urlParams = new URLSearchParams(searchParams)
- getFacetFields().map(field => {
- const name = appendFilterName(field)
- urlParams.getAll(name).forEach(value => {
- filterList.push({field, value})
- })
- })
- return filterList
+ const urlParams = new URLSearchParams(searchParams);
+ getFacetFields().map((field) => {
+ const name = appendFilterName(field);
+ urlParams.getAll(name).forEach((value) => {
+ filterList.push({ field, value });
+ });
+ });
+ return filterList;
}
export function generateSortFields(searchParams: string) {
- const sortList = []
- const urlParams = new URLSearchParams(searchParams)
- getSortFields().map(field => {
- const name = appendSortName(field)
- urlParams.getAll(name).forEach(value => {
- sortList.push({field, value})
- })
- })
+ const sortList = [];
+ const urlParams = new URLSearchParams(searchParams);
+ getSortFields().map((field) => {
+ const name = appendSortName(field);
+ urlParams.getAll(name).forEach((value) => {
+ const pair = getSortPairFromValue(value);
+ pair && sortList.push(pair);
+ });
+ });
return sortList;
-}
\ No newline at end of file
+}
+
+function getSortPairFromValue(sort: string) {
+ const [field, value] = sort.split(":");
+ if (!field || !value) return null;
+ return {
+ field,
+ value,
+ };
+}
diff --git a/src/service/URLManager/useURLManager.ts b/src/service/URLManager/useURLManager.ts
index ddc296fd..b936dae8 100644
--- a/src/service/URLManager/useURLManager.ts
+++ b/src/service/URLManager/useURLManager.ts
@@ -1,68 +1,207 @@
-import { FacetKeys } from '@/types'
-import { useRouter } from 'next/router'
-import { appendFilterName, appendSortName } from './helper'
-import { URLSearchParamsKeyword } from '@/config/config'
+import { FacetKeys } from "@/types";
+import { useRouter } from "next/router";
+import { appendFilterName, appendSortName } from "./helper";
+import { URLSearchParamsKeyword } from "@/config/config";
+
+type FilterProp = {
+ filterType: FacetKeys;
+ filterValue: string;
+ multiSelect?: boolean;
+};
const useURLManager = () => {
- const router = useRouter()
- const urlParams = new URLSearchParams(router.asPath.slice(1))
+ const router = useRouter();
+ const urlParams = new URLSearchParams(router.asPath.slice(1));
+
+ const getSearchTerm = () => {
+ return urlParams.get("search");
+ };
const getFilter = (filterType: FacetKeys) => {
- return urlParams.getAll(appendFilterName(filterType))
- }
+ return urlParams.getAll(appendFilterName(filterType));
+ };
const getSort = (sortField: string) => {
- return urlParams.get(appendSortName(sortField))
- }
+ return urlParams.get(appendSortName(sortField));
+ };
- const addSort = (sortField: string, value: string) => {
- urlParams.set(appendSortName(sortField), value)
- router.push(router.pathname + "?" + urlParams.toString())
- }
+ const removePageQueryParams = () => {
+ urlParams.delete(URLSearchParamsKeyword["PAGE"]);
+ };
- const removeSort = (sortField: string) => {
- urlParams.delete(appendSortName(sortField))
- const newUrl = urlParams.toString() ? `${router.pathname}?${urlParams.toString()}` : router.pathname
- router.push(newUrl)
- }
+ const addSortParams = (sortField: string, value: string) => {
+ urlParams.set(appendSortName(sortField), value);
+ return urlParams.toString();
+ };
+ const removeSortParams = (sortField: string) => {
+ urlParams.delete(appendSortName(sortField));
+ return urlParams.toString();
+ };
- const getSearchTerm = () => {
- return urlParams.get("search")
- }
-
- const addFilter = ({filterType, filterValue}: {filterType: FacetKeys, filterValue: string}) => {
- const currentFilterForType = urlParams.getAll(appendFilterName(filterType))
- if (currentFilterForType.includes(filterValue)) return;
- removePageQuery()
- urlParams.append(appendFilterName(filterType), filterValue)
- router.push(router.pathname + "?" + urlParams.toString())
- }
-
- const removeFilter = ({filterType, filterValue}) => {
- const appendedFilterName = appendFilterName(filterType)
- const currentFilterForType = urlParams.getAll(appendedFilterName)
- if (currentFilterForType.length) {
- removePageQuery()
- const filterValueIndex = currentFilterForType.findIndex(value => value === filterValue)
- if (filterValueIndex !== -1) {
- currentFilterForType.splice(filterValueIndex, 1)
-
- urlParams.delete(appendedFilterName)
+ const addFilterFromParams = ({
+ filterType,
+ filterValue,
+ multiSelect = true,
+ }: FilterProp) => {
+ const currentFilterForType = urlParams.getAll(appendFilterName(filterType));
+ if (currentFilterForType.includes(filterValue)) return null;
+ removePageQueryParams();
+ if (multiSelect) {
+ urlParams.append(appendFilterName(filterType), filterValue);
+ } else {
+ urlParams.set(appendFilterName(filterType), filterValue);
+ }
+ return urlParams.toString();
+ };
+
+ const removeFilterFromParams = ({
+ filterType,
+ filterValue,
+ multiSelect = true,
+ }: FilterProp) => {
+ const appendedFilterName = appendFilterName(filterType);
+ const currentFilterForType = urlParams.getAll(appendedFilterName);
+ if (!currentFilterForType.length) return null;
+
+ const filterValueIndex = currentFilterForType.findIndex(
+ (value) => value === filterValue
+ );
+ if (filterValueIndex !== -1) {
+ removePageQueryParams();
+ currentFilterForType.splice(filterValueIndex, 1);
+ urlParams.delete(appendedFilterName);
+ if (multiSelect) {
for (let i = 0; i < currentFilterForType.length; i++) {
- urlParams.append(appendedFilterName, currentFilterForType[i])
+ urlParams.append(appendedFilterName, currentFilterForType[i]);
}
- router.push(router.pathname + "?" + urlParams.toString())
+ }
+ return urlParams.toString();
+ }
+ };
+
+ const addSort = (sortField: string, value: string) => {
+ const params = addSortParams(sortField, value);
+ router.push(
+ router.pathname + `${params ? "?" + params : params}`,
+ undefined,
+ { shallow: true, scroll: true }
+ );
+ };
+
+ const removeSort = (sortField: string) => {
+ const params = removeSortParams(sortField);
+ router.push(
+ router.pathname + `${params ? "?" + params : params}`,
+ undefined,
+ { shallow: true, scroll: true }
+ );
+ };
+
+ const addFilter = ({
+ filterType,
+ filterValue,
+ multiSelect = true,
+ }: FilterProp) => {
+ const params = addFilterFromParams({
+ filterType,
+ filterValue,
+ multiSelect,
+ });
+ if (params !== null) {
+ router.push(
+ router.pathname + `${params ? "?" + params : params}`,
+ undefined,
+ { shallow: true, scroll: true }
+ );
+ }
+ };
+
+ const removeFilter = ({
+ filterType,
+ filterValue,
+ multiSelect = true,
+ }: FilterProp) => {
+ const params = removeFilterFromParams({
+ filterType,
+ filterValue,
+ multiSelect,
+ });
+ if (params !== null) {
+ router.push(
+ router.pathname + `${params ? "?" + params : params}`,
+ undefined,
+ { shallow: true, scroll: true }
+ );
+ }
+ };
+
+ const toggleFilter = ({
+ filterType,
+ filterValue,
+ multiSelect = true,
+ }: FilterProp) => {
+ const currentFilterForType = urlParams.getAll(appendFilterName(filterType));
+ if (currentFilterForType.includes(filterValue)) {
+ removeFilter({ filterType, filterValue, multiSelect });
+ } else {
+ addFilter({ filterType, filterValue, multiSelect });
+ }
+ };
+
+ const clearAllFilters = () => {
+ const paramKeys = Array.from(urlParams.keys());
+ for (const key of paramKeys) {
+ if (key.startsWith("filter")) {
+ urlParams.delete(key);
}
}
- }
+ };
+
+ const removeFilterTypes = ({
+ filterTypes,
+ sortField,
+ }: {
+ filterTypes: FacetKeys[];
+ sortField: string;
+ }) => {
+ filterTypes.forEach((filterType) => {
+ const appendedFilterName = appendFilterName(filterType);
+ const currentFilterForType = urlParams.getAll(appendedFilterName);
+ if (!currentFilterForType.length) return;
+ urlParams.delete(appendedFilterName);
+ });
+
+ removeSortParams(sortField);
+ removePageQueryParams();
+ const params = urlParams.toString();
+ router.push(
+ router.pathname + `${params ? "?" + params : params}`,
+ undefined,
+ { shallow: true, scroll: true }
+ );
+ };
- const removePageQuery = () => {
- urlParams.delete(URLSearchParamsKeyword["PAGE"])
- }
+ const setResultsSize = (size: number) => {
+ urlParams.set(URLSearchParamsKeyword.SIZE, size.toString());
+ router.push(router.pathname + "?" + urlParams.toString(), undefined, {
+ shallow: true,
+ scroll: true,
+ });
+ };
- return (
- { addFilter, removeFilter, getFilter, getSearchTerm, getSort, addSort, removeSort }
- )
-}
+ return {
+ addFilter,
+ removeFilter,
+ getFilter,
+ clearAllFilters,
+ removeFilterTypes,
+ getSearchTerm,
+ getSort,
+ addSort,
+ removeSort,
+ setResultsSize,
+ toggleFilter,
+ };
+};
-export default useURLManager
\ No newline at end of file
+export default useURLManager;
diff --git a/src/service/api/search/searchCall.ts b/src/service/api/search/searchCall.ts
new file mode 100644
index 00000000..2b973460
--- /dev/null
+++ b/src/service/api/search/searchCall.ts
@@ -0,0 +1,41 @@
+import { EsSearchResponse, SearchQuery } from "@/types";
+
+type BuildQuery = (
+ { queryString, size, page, filterFields, sortFields }: SearchQuery,
+ url?: string
+) => Promise
;
+
+export const buildQueryCall: BuildQuery = async (
+ { queryString, size, page, filterFields, sortFields },
+ url
+) => {
+ const body = {
+ queryString,
+ size,
+ page,
+ filterFields,
+ sortFields,
+ };
+
+ const jsonBody = JSON.stringify(body);
+
+ return fetch(url ?? "/api/elasticSearchProxy/search", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: jsonBody,
+ })
+ .then(async (res) => {
+ const data = await res.json();
+ if (!data.success) {
+ const errMessage = data.message || "Error while fetching";
+ throw new Error(errMessage);
+ }
+
+ return data.data?.result;
+ })
+ .catch((err) => {
+ throw new Error(err.message ?? "Error fetching results");
+ });
+};
diff --git a/src/service/api/search/useSearch.ts b/src/service/api/search/useSearch.ts
index a5143855..316fddf8 100644
--- a/src/service/api/search/useSearch.ts
+++ b/src/service/api/search/useSearch.ts
@@ -1,55 +1,28 @@
import { SearchQuery } from "@/types";
-import { AggregationsAggregate, SearchResponse } from "@elastic/elasticsearch/lib/api/types";
import { useQuery } from "@tanstack/react-query";
+import { buildQueryCall } from "./searchCall";
-type BuildQuery = ({queryString, size, page, filterFields, sortFields}: SearchQuery) => Promise>>
-
-const buildQueryCall: BuildQuery = async ({queryString, size, page, filterFields, sortFields}) => {
- const body = {
- queryString,
- size,
- page,
- filterFields,
- sortFields,
- };
-
- const jsonBody = JSON.stringify(body);
-
- return fetch("/api/elasticSearchProxy/search", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: jsonBody,
- })
- .then(async (res) => {
- const data = await res.json();
- if (!data.success) {
- const errMessage = data.message || "Error while fetching";
- throw new Error(errMessage);
- }
- return data.data?.result;
- })
- .catch((err) => {
- throw new Error(err.message ?? "Error fetching results");
- });
-};
-
+/**
+ * Custom React hook for performing search queries against an Elasticsearch backend via a proxy endpoint.
+ * Utilizes React Query's `useQuery` to manage fetching, caching, and updating of search results.
+ */
export const useSearch = ({
- queryString, size, page, filterFields, sortFields
+ queryString,
+ size,
+ page,
+ filterFields,
+ sortFields,
}: SearchQuery) => {
- const hasFilters = Boolean(filterFields.length)
const queryResult = useQuery({
- queryKey: ["query", queryString, filterFields, page, sortFields],
- queryFn: () => buildQueryCall({queryString, size, page, filterFields, sortFields}),
+ queryKey: ["query", queryString, size, filterFields, page, sortFields],
+ queryFn: () =>
+ buildQueryCall({ queryString, size, page, filterFields, sortFields }),
cacheTime: Infinity,
staleTime: Infinity,
refetchOnWindowFocus: false,
keepPreviousData: true,
enabled: true,
- // enabled: !!queryString?.trim() || hasFilters,
});
- return queryResult
-}
-
+ return queryResult;
+};
diff --git a/src/styles/custom.scss b/src/styles/custom.scss
index e0143fbb..8de4dc17 100644
--- a/src/styles/custom.scss
+++ b/src/styles/custom.scss
@@ -1,351 +1,72 @@
@import "./reset.css";
-@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500&display=swap');
-
-* {
- font-family: 'Rubik', sans-serif;
-}
:root {
- --primary-blue: #3a86ff;
- --secondary-blue: #a2d2ff;
- --primary-orange: #FF9900;
- --number-of-lines: 4;
- --number-of-lines-mobile: 5;
-}
-
-.btc-search {
- height: 100%;
- }
-
-/* We selectedivly override base styles to create a red theme */
-.btc-search .sui-search-box__submit {
- background: none;
- background-color: #FF9900;
- margin-left: 0;
- border-radius: 0 12px 12px 0;
- padding: 13px 25px;
-}
-
-.btc-search .sui-layout-sidebar{
- width: 30%;
-}
-
-.btc-search .sui-layout-main {
- width: 100%;
-}
-.btc-search .sui-layout-main-footer {
- padding-top: 20px;
- padding-bottom: 20px;
- margin-bottom: 10px;
-}
-
-.btc-search {
- .sui-sorting {
- margin-top: 2rem;
- }
- .sui-select {
- color: #4f4f4f;
- }
- .sui-select__control {
- background-color: white;
- box-shadow: 0px 3px 4px 0px var(--chakra-colors-blackAlpha-300);
- border: none;
- &--is-focused {
- border: none;
- }
- }
- .sui-select__single-value {
- font-weight: 400;
- }
- .sui-select__option {
- &--is-selected {
- background-color: var(--primary-blue);
- color: white;
- font-weight: 500;
- }
- }
- .sui-select__dropdown-indicator {
- color: #4f4f4f;
- }
+ --primary-blue: #3a86ff;
+ --secondary-blue: #a2d2ff;
+ --primary-orange: #ff9900;
+ --number-of-lines: 4;
+ --number-of-lines-mobile: 5;
}
-.search-box-with-contribute {
- display: grid;
- grid-template-columns: 75% auto;
- width: 100%;
- place-content: center;
- -webkit-user-select: none;
- -webkit-tap-highlight-color: transparent;
- -webkit-touch-callout: none;
- user-select: none;
-
- &:has(.sui-search-box__text-input:focus) {
- .search-box__keybindings {
- transform: translateY(-50%);
- opacity: 0;
- }
- }
-
- .search-box__info {
- grid-column: 1/3;
- display: flex;
- gap: 10px;
- justify-content: space-between;
- margin: 5px;
- }
-
- .search-box__keybindings {
- display: none;
- color: var(--chakra-colors-gray-400);
- font-size: 14px;
- gap: 10px;
- align-items: center;
- line-height: 1;
- transform: translateY(0);
- opacity: 1;
- transition: all ease-in 0.2s;
-
- span:first-of-type {
- font-size: 14px;
- }
-
- @media screen and (min-width: 640px) {
- display: flex;
- }
- kbd {
- background-color: #fff;
- border-radius: 3px;
- border: 1px solid #b4b4b4;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 2px 0 0 rgba(255, 255, 255, 0.7) inset;
- display: inline-block;
- font-weight: 700;
- line-height: 1;
- padding: 2px 6px;
- white-space: nowrap;
-
- &:first-of-type {
- font-size: 1em;
- }
- }
+@media screen and (max-width: 768px) {
+ #main {
+ &:has(form[data-input-focus="true"]) .navBar {
+ display: none !important;
}
+ }
+ .popout {
+ position: absolute;
+ width: 100vw;
+ top: 0;
+ left: 0;
+ padding: 4px;
+ z-index: 99;
+ animation: "glow-in";
+ animation-duration: 200ms;
+ animation-fill-mode: forwards;
+ animation-timing-function: ease-in-out;
- .search-box__contribute-wrapper {
- margin-left: auto;
- span {
- display: block;
- font-size: 12px;
- font-weight: 500;
- color: var(--chakra-colors-gray-400);
- transition: all ease-in-out 100ms;
-
- &:hover {
- color: var(--chakra-colors-gray-600);
- }
- &:active {
- transform: translateY(1px);
- color: var(--chakra-colors-purple-500);
- }
- }
- }
- @media screen and (min-width: 640px) {
- grid-template-columns: 50% auto;
- }
-}
-
-
-@media only screen and (max-width: 1200px) {
- .btc-search {
- .sui-layout-sidebar {
- display: none;
- }
-
- .sui-layout-main {
- padding: 32px;
- }
+ .fallback-height {
+ max-height: clamp(35vh, 200px, 80vh);
}
+ }
}
-@media only screen and (max-width: 640px) {
- .btc-search {
- .header {
- font-weight: 300;
- font-size: small;
- }
- .logo {
- width: 8rem;
- }
- .url-display {
- font-size: 12px;
- overflow: hidden;
- white-space: nowrap;
- }
- .sui-layout-main {
- padding: 0px;
- }
- }
+@keyframes glow-in {
+ 0% {
+ opacity: 0;
+ transform: translateY(100px) scale(0.8);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
}
-.btc-search {
- .sui-layout-sidebar-toggle {
- display: none;
- }
- .sui-layout-body:after {
- display: none;
- }
+.btc-search {
+ .sui-layout-sidebar-toggle {
+ display: none;
+ }
+ .sui-layout-body:after {
+ display: none;
+ }
}
.btc-search .sui-facet-view-more {
color: red;
}
-.header {
- display: flex;
- flex-direction: column;
- gap: 1rem;
- text-align: center;
- margin-bottom: auto;
- color: #3a56e4;
- font-weight:400;
- padding-top: 8%;
- padding-bottom: 16px;
-}
-
-.header .description {
- font-weight: bold;
-}
-
-.logo {
- width: 12rem;
- margin: auto;
-}
-
-.btc-search {
- background: #fcfcfc;
-}
-
-
-.sui-layout-header {
- border-bottom: 0px;
- margin: auto 0;
-
- &:focus {
- border-bottom: 0px;
- }
-}
-
-.sui-search-box__text-input {
- border-radius: 35px 0px 0px 35px;
- border-right: none;
- color: #121212;
-}
-
-#profileimage {
- border-radius: 50px;
- width: 32px;
- height: 32px;
- position: absolute;
- right: 40px;
- bottom: 12px;
-}
-
-#optionsbar {
- width: 100%;
- height: 50px;
- border-width: 0px;
- border-bottom: 1px;
- border-color: lightgray;
- border-style: solid;
- display: flex;
- align-items: flex-end;
- font-family: 'Arial';
- font-size: 13px;
- color: rgb(112, 112, 112);
- padding-top: 64px;
-}
-
-#optionsmenu1 {
- list-style: none;
- display: flex;
- flex-direction: row;
- padding: 0px;
- margin: 0px;
- margin-left: 150px;
-}
-
-#optionsmenu1 li {
- padding: 0px 10px 15px 20px;
-}
-
-#optionsmenuactive {
- color: #4285F4;
- font-weight: bold;
- position: relative;
- z-index: -1;
-}
-
-.pagelist {
- list-style: none;
- color: rgb(29, 135, 255);
- display: flex;
- flex-direction: row;
- font-size: 12px;
- margin-bottom: 30px;
- margin-left: 100px;
-}
-
-.pagelistprevious::before {
- content: '<';
- display: block;
- position: absolute;
- right: 12px;
- top: -35px;
- color: rgb(29, 135, 255);
- font-size: 20px;
- transform: scale(0.7, 1.2);
-}
-
-.pagelistfirst {
- margin-left: 20px;
- font-size: 13px;
-}
-
-.pagelistnumber::before, .pagelistfirst::before {
- content: '';
- display: block;
- position: absolute;
- left: -2px;
- top: -21px;
- border-radius: 50px;
- width: 10px;
- height: 10px;
- background-color: white;
- z-index: 3;
-}
-
-.pagelistnumber::after {
- content: '';
- display: block;
- position: absolute;
- left: -6px;
- top: -25px;
- border-radius: 50px;
- width: 18px;
- height: 18px;
- background-color: rgb(255, 196, 0);
- z-index: 2;
-}
-
.paging-info {
- margin-left: auto;
- display: flex;
- flex-direction: column;
+ margin-left: auto;
+ display: flex;
+ flex-direction: column;
}
.paging-info strong {
- text-align: right;
+ text-align: right;
}
.paging-info p {
- margin: 0;
- padding: 4px 0;
+ margin: 0;
+ padding: 4px 0;
}
.home-facet-container {
@@ -363,34 +84,95 @@
}
.home-facet-tag {
- transform: scale(0.4);
- opacity: 0;
- animation-name: bubble-in;
- animation-duration: 350ms;
- animation-fill-mode: forwards;
- animation-timing-function: ease-in;
- -webkit-tap-highlight-color: transparent;
- @media (hover:none) {
- &:hover {
- background-color: auto;
- color: auto;
- }
+ transform: scale(0.4);
+ opacity: 0;
+ animation-name: bubble-in;
+ animation-duration: 350ms;
+ animation-fill-mode: forwards;
+ animation-timing-function: ease-in;
+ -webkit-tap-highlight-color: transparent;
+ @media (hover: none) {
+ &:hover {
+ background-color: auto;
+ color: auto;
}
- @media (pointer:fine) {
- &:hover {
- background-color: var(--chakra-colors-gray-600);
- color: var(--chakra-colors-gray-100);
- }
+ }
+ @media (pointer: fine) {
+ &:hover {
+ background-color: var(--chakra-colors-gray-600);
+ color: var(--chakra-colors-gray-100);
}
+ }
}
@keyframes bubble-in {
- 80% {
- transform: scale(1.1);
- opacity: 0.5;
+ 80% {
+ transform: scale(1.1);
+ opacity: 0.5;
+ }
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+.landing-gradient {
+ background-image: linear-gradient(
+ to bottom,
+ #e58525 0%,
+ #e58525 15%,
+ transparent 80%
+ );
+}
+
+// styling for non-interactive body
+// this is a hack to make the body non-interactive
+// IMPORTANT: elements oustide the element with data-freeze-body that needs interactivity must have the class "pointer-events-auto" else it will be frozen too
+
+*[data-freeze-body="true"] {
+ pointer-events: auto;
+ &::before {
+ content: "";
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ backdrop-filter: blur(5px);
+ background-color: rgba(128, 128, 128, 0.15);
+ background-color: rgba(var(--background), 0.15);
+ }
+}
+body {
+ &:has(*[data-freeze-body="true"]) {
+ overflow: hidden;
+ pointer-events: none;
+ width: 100%;
+ height: 100%;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ [data-freeze-body-mobile="true"] {
+ pointer-events: auto;
+ &::before {
+ content: "";
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ backdrop-filter: blur(5px);
+ background-color: rgba(128, 128, 128, 0.15);
}
- 100% {
- transform: scale(1);
- opacity: 1;
+ }
+
+ body {
+ &:has(*[data-freeze-body-mobile="true"]) {
+ overflow: hidden;
+ pointer-events: none;
+ width: 100%;
+ height: 100%;
}
+ }
}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index fd81e885..4b6a46d8 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -2,10 +2,54 @@
@tailwind components;
@tailwind utilities;
+@layer base {
+ @font-face {
+ font-family: "Mona Sans";
+ src: url("../../public/font/Mona-Sans.woff2")
+ format("woff2 supports variations"),
+ url("../../public/font/Mona-Sans.woff2") format("woff2-variations");
+ font-weight: 200 300 400 500 600 700 800 900;
+ }
+}
+@layer utilities {
+ /* Hide scrollbar for Chrome, Safari and Opera */
+ .no-scrollbar::-webkit-scrollbar {
+ display: none;
+ }
+ /* Hide scrollbar for IE, Edge and Firefox */
+ .no-scrollbar {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+ }
+}
+
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
+ --background: #fafafa;
+ --hover-primary: #f5f5f5;
+ --button: #333333;
+ --hover-state: #fff0e0;
+ --stroke: #bfbfbf;
+ --secondary-text: #636366;
+ /* --secondary-text: #999999; */
+ --primary-text: #292929;
+ --black: #292929;
+ /* --other-light-text: #D9D9D9; */
+ --other-light-text: #999999;
+ --accent: #f7931a;
+ --gradient: linear-gradient(92.78deg, #e8782b 0%, #f6a73f 101.1%);
+ --shadow-left: linear-gradient(
+ 90deg,
+ #fafafa 50.33%,
+ rgba(250, 250, 250, 0) 100%
+ );
+ --shadow-right: linear-gradient(
+ 270deg,
+ #fafafa 50.33%,
+ rgba(250, 250, 250, 0) 100%
+ );
}
@media (prefers-color-scheme: dark) {
@@ -17,6 +61,7 @@
}
body {
+ font-family: "Geist Sans", sans-serif !important;
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
@@ -25,3 +70,78 @@ body {
)
rgb(var(--background-start-rgb));
}
+
+html {
+ scroll-behavior: smooth !important;
+ scroll-padding-top: 24px;
+ font-family: "Geist Sans", sans-serif;
+}
+
+.scroller {
+ --scrollbar-color-thumb: #aaa;
+ --scrollbar-color-track: rgba(60, 60, 60, 0.05);
+ --scrollbar-width: auto;
+ --scrollbar-width-legacy: 10px;
+}
+
+.dark .scroller {
+ --scrollbar-color-thumb: #777;
+ --scrollbar-color-track: rgba(60, 60, 60, 0.1);
+}
+
+/* Modern browsers with `scrollbar-*` support */
+@supports (scrollbar-width: auto) {
+ .scroller {
+ scrollbar-color: var(--scrollbar-color-thumb) var(--scrollbar-color-track);
+ scrollbar-width: var(--scrollbar-width);
+ }
+}
+
+/* Legacy browsers with `::-webkit-scrollbar-*` support */
+@supports selector(::-webkit-scrollbar) {
+ .scroller::-webkit-scrollbar-thumb {
+ background: transparent;
+ }
+ .scroller::-webkit-scrollbar-track {
+ background: transparent;
+ }
+ .scroller:hover::-webkit-scrollbar-thumb {
+ background: var(--scrollbar-color-thumb);
+ }
+ .scroller:hover::-webkit-scrollbar-track {
+ background: var(--scrollbar-color-track);
+ }
+ .scroller::-webkit-scrollbar {
+ max-width: var(--scrollbar-width-legacy);
+ max-height: var(--scrollbar-width-legacy);
+ }
+}
+
+.dark {
+ background: var(--background);
+ --background: #1c1c1e;
+ --hover-state: #2c2c2e;
+ --button: #8e8e93;
+ --stroke: #3a3a3c;
+ --secondary-text: #999999;
+ /* --secondary-text: #636366; */
+ --primary-text: #a4a4a8;
+ /* --primary-text: #8e8e93; */
+ --black: #292929;
+ --other-light-text: #636366;
+ /* --other-light-text: #48484a; */
+ --accent: #f7931a;
+ --hover-primary: #2c2c2e;
+ --gradient: linear-gradient(92.78deg, #e8782b 0%, #f6a73f 101.1%);
+
+ --shadow-right: linear-gradient(
+ 270deg,
+ #1c1c1f 50.33%,
+ rgba(28, 28, 31, 0) 100%
+ );
+ --shadow-left: linear-gradient(
+ 90deg,
+ #1c1c1f 50.33%,
+ rgba(28, 28, 31, 0) 100%
+ );
+}
diff --git a/src/styles/reset.css b/src/styles/reset.css
index 08c7e421..7bd15eda 100644
--- a/src/styles/reset.css
+++ b/src/styles/reset.css
@@ -1,21 +1,32 @@
-
/*
Josh's Custom CSS Reset
https://www.joshwcomeau.com/css/custom-css-reset/
*/
-#root, html, body {
+#root,
+html,
+body {
height: 100%;
}
body {
line-height: 1.5;
}
-img, picture, video, canvas, svg {
+img,
+picture,
+video,
+canvas,
+svg {
display: block;
max-width: 100%;
}
-p, h1, h2, h3, h4, h5, h6 {
+p,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
overflow-wrap: break-word;
}
#root {
@@ -23,12 +34,22 @@ p, h1, h2, h3, h4, h5, h6 {
}
/* HTML5 display-role reset for older browsers */
-article, aside, details, figcaption, figure,
-footer, header, hgroup, menu, nav, section {
- display: block;
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block;
}
-ol, ul {
- list-style: none;
+ol,
+ul {
+ list-style: none;
}
/* Josh's Custom CSS Reset End */
diff --git a/src/types.ts b/src/types.ts
index e9135ac3..943af5fc 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,3 +1,7 @@
+import {
+ AggregationsAggregate,
+ SearchResponse,
+} from "@elastic/elasticsearch/lib/api/types";
const AUTHOR = "authors" as const;
const DOMAIN = "domain" as const;
const TAGS = "tags" as const;
@@ -10,11 +14,11 @@ export type Facet = {
};
const bodyType = {
- "markdown": "markdown",
- "raw": "raw",
- "html": "html",
+ markdown: "markdown",
+ raw: "raw",
+ html: "html",
"combined-summary": "combined-summary",
-} as const
+} as const;
export type SortOption = "asc" | "desc";
@@ -45,8 +49,14 @@ export type EsSearchResult = {
tags?: string[];
transcript_by?: string;
summary?: string;
+ thread_url?: string;
type?: "question" | "reply" | "original_post" | "newsletter";
};
};
export type BodyType = (typeof bodyType)[keyof typeof bodyType];
+
+export type EsSearchResponse = SearchResponse<
+ unknown,
+ Record
+>;
diff --git a/src/utils/documents.ts b/src/utils/documents.ts
new file mode 100644
index 00000000..a71c9d38
--- /dev/null
+++ b/src/utils/documents.ts
@@ -0,0 +1,22 @@
+const gnusha = "https://gnusha.org/pi/bitcoindev/";
+const newBitcoinDevMailingList =
+ "https://mailing-list.bitcoindevs.xyz/bitcoindev/";
+
+export const remapUrl = ({ url, domain }: { url: string; domain: string }) => {
+ switch (domain) {
+ case newBitcoinDevMailingList:
+ if (url.includes(gnusha)) {
+ return url.replaceAll(gnusha, newBitcoinDevMailingList);
+ }
+ return url;
+
+ case gnusha:
+ if (url.includes(gnusha)) {
+ return url.replaceAll(gnusha, newBitcoinDevMailingList);
+ }
+ return url;
+
+ default:
+ return url;
+ }
+};
diff --git a/src/utils/dummy.ts b/src/utils/dummy.ts
new file mode 100644
index 00000000..26f31dfc
--- /dev/null
+++ b/src/utils/dummy.ts
@@ -0,0 +1,40 @@
+export const defaultSearchTags = [
+ {
+ headline: "Search by Keywords",
+ type: "",
+ filter: false,
+ tags: [
+ "Adaptor signatures",
+ "Async payments",
+ "BIP324",
+ "Eclipse attacks",
+ "Just-in-time (JIT) routing",
+ ],
+ },
+ {
+ headline: "Search by Sources",
+ type: "domain",
+ filter: true,
+ tags: [
+ "https://delvingbitcoin.org/",
+ "https://github.com/bitcoin/bips",
+ "https://lists.linuxfoundation.org/pipermail/bitcoin-dev/",
+ "https://bitcoin.stackexchange.com",
+ "https://lists.linuxfoundation.org/pipermail/lightning-dev/",
+ "https://bitcointalk.org/",
+ "https://btctranscripts.com/",
+ ],
+ },
+ {
+ headline: "Search by Authors",
+ type: "authors",
+ filter: true,
+ tags: [
+ "Ava Chow",
+ "Anthony Towns",
+ "Gregory Maxwell",
+ "Matt Corallo",
+ "Pieter Wuille",
+ ],
+ },
+];
diff --git a/src/utils/elastic-search-ui-functions.ts b/src/utils/elastic-search-ui-functions.ts
index fd15cd4b..aa8666f1 100644
--- a/src/utils/elastic-search-ui-functions.ts
+++ b/src/utils/elastic-search-ui-functions.ts
@@ -17,3 +17,8 @@ export default function appendClassName(
if (!baseClassName) return getNewClassName(newClassName) || "";
return `${baseClassName} ${getNewClassName(newClassName)}`;
}
+
+// To remove the weird character "�" and markdowns /n
+export const removeMarkdownCharacters = (value: string) => {
+ return value.replace(/(\\n|[.]+)/gm, " ").replace(/[^\x20-\x7E]/g, "'");
+};
diff --git a/src/utils/facet.ts b/src/utils/facet.ts
new file mode 100644
index 00000000..e3c08c9a
--- /dev/null
+++ b/src/utils/facet.ts
@@ -0,0 +1,31 @@
+import { getDomainName } from "@/config/mapping-helper";
+
+type FilterValue =
+ | {
+ name: string;
+ [key: string]: string;
+ }
+ | string;
+
+export function getFilterValueDisplay(filterValue: FilterValue, label: string) {
+ if (typeof filterValue !== "string") {
+ return filterValue.name;
+ } else {
+ if (label === "domain") {
+ return getDomainName(filterValue);
+ } else return filterValue;
+ }
+}
+
+export function matchCharactersWithRegex(word: string, searchTerm: string) {
+ const escapedSearchTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+
+ const regexPattern = escapedSearchTerm
+ .split("")
+ .map((char) => `(?=.*${char})`)
+ .join("");
+
+ const regex = new RegExp(regexPattern, "i"); // 'i' flag for case-insensitive matching
+
+ return regex.test(word);
+}
diff --git a/src/utils/server/apiFunctions.ts b/src/utils/server/apiFunctions.ts
index c41cf70e..c88ebb91 100644
--- a/src/utils/server/apiFunctions.ts
+++ b/src/utils/server/apiFunctions.ts
@@ -1,47 +1,69 @@
+import { aggregatorSize } from "@/config/config";
import type { Facet, SearchQuery } from "@/types";
const FIELDS_TO_SEARCH = ["authors", "title", "body"];
+// Omitting 'page' from SearchQuery as 'from' is used for Elasticsearch pagination
type BuildQueryForElaSticClient = Omit & {
- from: number
-}
+ from: number;
+};
-export const buildQuery = ({queryString, size, from, filterFields, sortFields}: BuildQueryForElaSticClient) => {
-
+/**
+ * Constructs an Elasticsearch query object for searching documents based on provided criteria.
+ * Supports full-text search across multiple fields, filtering by facets, and sorting results.
+ */
+export const buildQuery = ({
+ queryString,
+ size,
+ from,
+ filterFields,
+ sortFields,
+}: BuildQueryForElaSticClient) => {
+ // Initialize the base structure of the Elasticsearch query
let baseQuery = {
- query:{
- bool:{
+ query: {
+ bool: {
must: [],
- should:[],
+ should: [],
filter: [],
- }
+ must_not: [
+ {
+ term: {
+ "type.keyword": "combined-summary",
+ },
+ },
+ ],
+ },
},
sort: [],
aggs: {
authors: {
terms: {
- field: 'authors.keyword',
- size: 15
- }
+ field: "authors.keyword",
+ size: aggregatorSize,
+ },
},
domains: {
terms: {
- field: 'domain.keyword',
- size: 15
- }
+ field: "domain.keyword",
+ size: aggregatorSize,
+ },
},
tags: {
terms: {
- field: 'tags.keyword',
- size: 15
- }
- }
+ field: "tags.keyword",
+ size: aggregatorSize,
+ },
+ },
},
- size,
- from,
- }
+ size, // Number of search results to return
+ from, // Offset for pagination (calculated from page number)
+ _source: {
+ excludes: ["summary_vector_embeddings"],
+ },
+ };
- //Add the clause to the should array
+ // Construct and add the full-text search clause
let shouldClause = buildShouldQueryClause(queryString);
if (!queryString) {
baseQuery.query.bool.should.push(shouldClause);
@@ -49,46 +71,51 @@ export const buildQuery = ({queryString, size, from, filterFields, sortFields}:
baseQuery.query.bool.must.push(shouldClause);
}
- if(filterFields && filterFields.length) {
+ // Add filter clauses for each specified filter field
+ if (filterFields && filterFields.length) {
for (let facet of filterFields) {
let mustClause = buildFilterQueryClause(facet);
baseQuery.query.bool.must.push(mustClause);
}
}
+ // Add sorting clauses for each specified sort field
if (sortFields && sortFields.length) {
for (let field of sortFields) {
- const sortClause = buildSortClause(field)
- baseQuery.sort.push(sortClause)
+ const sortClause = buildSortClause(field);
+ baseQuery.sort.push(sortClause);
}
}
return baseQuery;
};
+// Helper to build the should query clause for full-text search
const buildShouldQueryClause = (queryString: string) => {
let shouldQueryClause = {
- multi_match : {
- query: queryString,
- fields: FIELDS_TO_SEARCH
- }
- }
+ multi_match: {
+ query: queryString,
+ fields: FIELDS_TO_SEARCH,
+ },
+ };
return shouldQueryClause;
-}
+};
-const buildFilterQueryClause = ({field, value}: Facet) => {
+// Helper to build filter query clauses based on facets
+const buildFilterQueryClause = ({ field, value }: Facet) => {
let filterQueryClause = {
term: {
- [`${field}.keyword`]: {value}
- }
- }
+ [`${field}.keyword`]: { value },
+ },
+ };
return filterQueryClause;
-}
+};
-const buildSortClause = ({field, value}: {field: any, value: any}) => {
+// Helper to build sort clauses for sorting results
+const buildSortClause = ({ field, value }: { field: any; value: any }) => {
return {
- [field]: value
- }
-}
\ No newline at end of file
+ [field]: value,
+ };
+};
diff --git a/src/utils/tldr.ts b/src/utils/tldr.ts
index b6b4d9e0..07493c27 100644
--- a/src/utils/tldr.ts
+++ b/src/utils/tldr.ts
@@ -1,28 +1,29 @@
import { getConfig } from "@/config/config-helper";
import { getMapping } from "@/config/mapping-helper";
-const baseUrl = getConfig().tldrBaseUrl
+const baseUrl = getConfig().tldrBaseUrl;
+const mapping = getMapping();
export const getUrlForCombinedSummary = (url: string, id: string) => {
- const baseLink = getMapping().tldrMappingUrl
- const strippedLink = url.split(baseLink)[1].split("/")
+ const baseLink = mapping.tldrMappingUrl;
+ const strippedLink = url.split(baseLink)[1].split("/");
try {
// return list, year_month
- const [list, year_month] = strippedLink
-
+ const [list, year_month] = strippedLink;
+
// separate month and year from year_month string with the right tldr mapping
const [year, month] = year_month.split("-").map((i, index) => {
- if (index === 0) return i
- else return getMapping().tldrMappingMonths?.[i] ?? ""
- })
-
+ if (index === 0) return i;
+ else return mapping.tldrMappingMonths?.[i] ?? "";
+ });
+
if (!month || !year || !list || !id) {
- return url
- }
-
- const tldrUrl = `${baseUrl}/summary/${list}/${month}_${year}/${id}`
- return tldrUrl
+ return url;
+ }
+
+ const tldrUrl = `${baseUrl}/summary/${list}/${month}_${year}/${id}`;
+ return tldrUrl;
} catch {
- return url
+ return url;
}
};
diff --git a/src/utils/userOS.js b/src/utils/userOS.js
index 7cabafb4..3a7769e8 100644
--- a/src/utils/userOS.js
+++ b/src/utils/userOS.js
@@ -1,4 +1,3 @@
-
export const isMac = () => {
const OS = navigator.userAgent;
return OS.search("Mac") !== -1;
diff --git a/tailwind.config.js b/tailwind.config.js
index d53b2eaa..7384b828 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,18 +1,51 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
+ darkMode: "class",
content: [
- './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
- './src/components/**/*.{js,ts,jsx,tsx,mdx}',
- './src/app/**/*.{js,ts,jsx,tsx,mdx}',
+ "./src/layout/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
+ fontFamily: {
+ sans: ["ui-sans-serif", "system-ui"],
+ serif: ["ui-serif", "Georgia"],
+ mono: ["ui-monospace", "SFMono-Regular"],
+ geist: ['"Geist Sans"', "sans-serif"],
+ mona: ['"Mona Sans"', "sans-serif"],
+ },
extend: {
+ boxShadow: {
+ "custom-sm": "2px 3px 10px 0px rgba(255, 128, 0, 0.18);",
+ },
+ colors: {
+ custom: {
+ background: "var(--background)",
+ "hover-primary": "var(--hover-primary)",
+ "hover-state": "var(--hover-state)",
+ stroke: "var(--stroke)",
+ button: "var(--button)",
+ "secondary-text": "var(--secondary-text)",
+ "primary-text": "var(--primary-text)",
+ accent: "var(--accent)",
+ white: "#FAFAFA",
+ black: "var(--black)",
+ lightGrey: "#666666",
+ brightOrange: {
+ 100: "#EC802F",
+ 200: "#ED8936",
+ 300: "#FAA739",
+ },
+ otherLight: "var(--other-light-text)",
+ },
+ },
backgroundImage: {
- 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
- 'gradient-conic':
- 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
+ gradient: "var(--gradient)",
+ "shadow-left": "var(--shadow-left)",
+ "shadow-right": "var(--shadow-right)",
},
},
},
plugins: [],
-}
+};
diff --git a/tsconfig.json b/tsconfig.json
index 642a81a7..a2844216 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,11 +4,7 @@
"paths": {
"@/*": ["./src/*"]
},
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
+ "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@@ -22,12 +18,6 @@
"isolatedModules": true,
"jsx": "preserve"
},
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx"
- ],
- "exclude": [
- "node_modules"
- ]
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
}