Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Filter search bar implementation #273

Merged
merged 5 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/frontend/src/api/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from "./useCookieChoice";
export * from "./useIsMobile";
export * from "./useFeatureFlags";
export * from "./useFilters";
export * from "./useFilterKeywordSearch";
export * from "./useGATracker";
export * from "./useGlobalSearch";
export * from "./useGlobalHooks";
Expand Down
69 changes: 69 additions & 0 deletions packages/frontend/src/api/hooks/useFilterKeywordSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useMemo, useState } from "react";
import { useGetFilterKeywordsQuery } from "src/api/services";
import { TFilterKeyword, ESupportedFilters } from "src/api/types";

interface IFilterKeywordSearch {
searchState: string;
keywordResults: TFilterKeyword[];
isFetchingKeywordResults: boolean;
setSearchState: (s: string) => void;
}

/**
* useFilterKeywordSearch.
* Pretty similar to useKeywordSearch, but instead of consuming the keywords endpoint,
* it queries the superfeed/filter_keywords endpoint.
* The main difference is that the results are grouped in categories (concept tags, coins, projects, etc).
* This hook is used in the UserFilter page.
* It is used to search for keywords, display them and add them to the view.
*
* @returns - The search state and results.
*/
export const useFilterKeywordSearch: () => IFilterKeywordSearch = () => {
const [searchState, setSearchState] = useState("");

const { data: keywordsData, isFetching: isFetchingKeywordResults } =
useGetFilterKeywordsQuery(
{
filter_text: searchState,
},
{
skip: searchState === "",
}
);

const keywordResults = useMemo(() => {
if (!keywordsData) return [];
return [
...keywordsData.conceptTags.map((keyword) => ({
id: keyword.id,
name: keyword.name,
slug: keyword.tag.slug,
type: ESupportedFilters.ConceptTags,
})),
...keywordsData.chains.map((keyword) => ({
id: keyword.id,
name: keyword.name,
slug: keyword.tag.slug,
type: ESupportedFilters.Chains,
})),
...keywordsData.coins.map((keyword) => ({
id: keyword.id,
name: keyword.name,
slug: keyword.tag.slug,
type: ESupportedFilters.Coins,
})),
].filter(
(item, index, self) =>
self.findIndex((innerItem) => innerItem.slug === item.slug) ===
index
);
}, [keywordsData]);

return {
searchState,
setSearchState,
keywordResults,
isFetchingKeywordResults,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
TGetSuperfeedFilterDataRawResponse,
TGetSuperfeedFilterDataResponse,
TGetSuperfeedFilterDataRequest,
TGetSuperfeedFilterKeywordsResponse,
TGetSuperfeedFilterKeywordsRequest,
TGetSuperfeedFilterKeywordsRawResponse,
} from "./types";

const { SUPERFEED } = CONFIG.API.DEFAULT.ROUTES;
Expand Down Expand Up @@ -89,8 +92,31 @@ export const superfeedApi = alphadayApi.injectEndpoints({
),
}),
}),
getFilterKeywords: builder.query<
TGetSuperfeedFilterKeywordsResponse,
TGetSuperfeedFilterKeywordsRequest
>({
query: (req) => {
const { ...reqParams } = req;
const params: string = queryString.stringify(reqParams);
const path = `${SUPERFEED.BASE}${SUPERFEED.FILTER_KEYWORDS}?${params}`;
Logger.debug("getSuperfeedFilterKeywords: querying", path);
return path;
},
transformResponse: (
r: TGetSuperfeedFilterKeywordsRawResponse
): TGetSuperfeedFilterKeywordsResponse => ({
coins: r.coin_keywords,
conceptTags: r.concept_keywords,
chains: r.chain_keywords,
}),
}),
}),
overrideExisting: false,
});

export const { useGetSuperfeedListQuery, useGetFilterDataQuery } = superfeedApi;
export const {
useGetSuperfeedListQuery,
useGetFilterDataQuery,
useGetFilterKeywordsQuery,
} = superfeedApi;
24 changes: 24 additions & 0 deletions packages/frontend/src/api/services/superfeed/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ export type TTaggedFilterDatum = TBaseFilterItem & {
tags: TBaseFilterTag[];
};

export type TRemoteFilterKeyword = {
id: number;
name: string;
tag: {
id: number;
name: string;
slug: string;
};
};

/**
* Query types
*/
Expand Down Expand Up @@ -75,3 +85,17 @@ export type TGetSuperfeedFilterDataResponse = {
coins: TTaggedFilterDatum[];
chains: TTaggedFilterDatum[];
};

export type TGetSuperfeedFilterKeywordsRequest = {
filter_text: string;
};
export type TGetSuperfeedFilterKeywordsRawResponse = {
concept_keywords: TRemoteFilterKeyword[];
coin_keywords: TRemoteFilterKeyword[];
chain_keywords: TRemoteFilterKeyword[];
};
export type TGetSuperfeedFilterKeywordsResponse = {
conceptTags: TRemoteFilterKeyword[];
coins: TRemoteFilterKeyword[];
chains: TRemoteFilterKeyword[];
};
7 changes: 7 additions & 0 deletions packages/frontend/src/api/types/superfeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ export enum ETimeRange {
Last6Months = "last-6-months",
}

export type TFilterKeyword = {
id: number;
name: string;
slug: string;
type: ESupportedFilters;
};

export type TFeedMarketData = {
coin: {
name: string;
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/config/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const API_V0 = {
BASE: "superfeed",
DEFAULT: "/",
FILTER_DATA: "/filter_data/",
FILTER_KEYWORDS: "/filter_keywords/",
},
TVL: {
BASE: "tvl",
Expand Down
13 changes: 6 additions & 7 deletions packages/frontend/src/mobile-components/FilterSearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { FC } from "react";
import { SearchBar } from "@alphaday/ui-kit";
import { TBaseFilterItem } from "src/api/services";
import { Logger } from "src/api/utils/logging";

type TOption = TBaseFilterItem;

interface FilterSearchBarProps {
interface FilterSearchBarProps<T extends TBaseFilterItem = TOption> {
tags?: string;
tagsList: TOption[];
tagsList: T[];
setSearchState: (value: string) => void;
onChange: (value: readonly TOption[]) => void;
onChange: (value: readonly T[]) => void;
}

const FilterSearchBar: FC<FilterSearchBarProps> = ({
const FilterSearchBar = <T extends TBaseFilterItem>({
onChange,
tags,
setSearchState,
tagsList,
}) => {
}: FilterSearchBarProps<T>) => {
const searchValues = tags
?.split(",")
.map((tag) => {
Expand All @@ -31,7 +30,7 @@ const FilterSearchBar: FC<FilterSearchBarProps> = ({
data-testid="header-search-container"
>
<span className="w-full max-w-[524px]">
<SearchBar<TOption>
<SearchBar<T>
showBackdrop
onChange={(o) => {
Logger.debug("onChange called");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
Toggle,
themeColors,
} from "@alphaday/ui-kit";
import { ESortFeedBy, ESupportedFilters } from "src/api/types";
import { ESortFeedBy, ESupportedFilters, TFilterKeyword } from "src/api/types";
import { ReactComponent as ChevronSVG } from "src/assets/icons/chevron-down2.svg";
// import FilterSearchBar from "../FilterSearchBar";
import FilterSearchBar from "../FilterSearchBar";
import { TFilterOptions } from "./filterOptions";
import { OptionsDisclosure, OptionButton } from "./OptionsDisclosure";

Expand All @@ -17,6 +17,8 @@ interface IUserFiltersModalProps {
filterOptions: TFilterOptions;
isLoading: boolean;
onSelectFilter: (slug: string, type: ESupportedFilters) => void;
filterKeywords: TFilterKeyword[];
onSearchInputChange: (value: string) => void;
}

const UserFiltersModal: FC<IUserFiltersModalProps> = ({
Expand All @@ -25,6 +27,8 @@ const UserFiltersModal: FC<IUserFiltersModalProps> = ({
filterOptions,
isLoading,
onSelectFilter,
filterKeywords,
onSearchInputChange,
}) => {
const [isOpen, setIsOpen] = useState(true);

Expand Down Expand Up @@ -70,9 +74,20 @@ const UserFiltersModal: FC<IUserFiltersModalProps> = ({
Craft your ideal superfeed by customizing the
filters below.
</p>
{/* TODO: implement filter search when properly spec-ed */}
<div className="flex relative z-10 justify-center [&>div]:w-full">
{/* <FilterSearchBar /> */}
<FilterSearchBar<TFilterKeyword>
setSearchState={onSearchInputChange}
tagsList={filterKeywords.map((kw) => ({
...kw,
label: kw.name,
value: kw.slug,
}))}
onChange={(values) =>
values.forEach((kw) =>
onSelectFilter(kw.slug, kw.type)
)
}
/>
</div>
</div>
<div className="w-full flex justify-between py-6 border-b border-borderLine">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FC } from "react";
import { useFilters } from "src/api/hooks";
import { useFilters, useFilterKeywordSearch } from "src/api/hooks";
import {
useGetFilterDataQuery,
TFilterDatum,
Expand Down Expand Up @@ -101,6 +101,8 @@ const UserFiltersContainer: FC<{

const { toggleFilter } = useFilters();

const { setSearchState, keywordResults } = useFilterKeywordSearch();

const filterOptions: TFilterOptions = {
localFilterOptions: updateLocalFilterOptionsState(selectedLocalFilters),
syncedFilterOptions: {
Expand Down Expand Up @@ -146,6 +148,8 @@ const UserFiltersContainer: FC<{
filterOptions={filterOptions}
isLoading={isLoading}
onSelectFilter={handleSelectFilter}
filterKeywords={keywordResults}
onSearchInputChange={setSearchState}
/>
);
};
Expand Down
Loading