From 044924409473f3a5c76f5e26c6d564e1bccfc59a Mon Sep 17 00:00:00 2001 From: "user.name.like : \"barnalixd" Date: Mon, 17 Nov 2025 11:41:05 +0530 Subject: [PATCH] feat: add multiple tech stack selection in filters - Replace radio buttons with checkboxes for tech stack filter when allowMultiple is true - Add updateMultipleFilters method to store to handle array of selected values - Update Filter component to support both single and multiple selection modes - Modify UserInputFilterProps type to accept string or string[] for Tech stack - Update converter logic to handle multiple languages by joining them with space - Enable allowMultiple prop for Tech stack filter in FiltersContainer This allows users to select multiple programming languages simultaneously when searching for projects, providing more flexible filtering options. --- apps/web/src/components/ui/Filter.tsx | 98 ++++++++++++++----- .../src/components/ui/FiltersContainer.tsx | 1 + apps/web/src/store/useFilterInputStore.ts | 5 + apps/web/src/types/filter.ts | 2 +- apps/web/src/utils/converter.ts | 8 +- 5 files changed, 89 insertions(+), 25 deletions(-) diff --git a/apps/web/src/components/ui/Filter.tsx b/apps/web/src/components/ui/Filter.tsx index b091b813..1a506036 100644 --- a/apps/web/src/components/ui/Filter.tsx +++ b/apps/web/src/components/ui/Filter.tsx @@ -2,6 +2,7 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "./accordion"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { useFilterInputStore } from "@/store/useFilterInputStore"; import clsx from "clsx"; @@ -10,16 +11,45 @@ export default function Filter({ filterName, filters, onClick, + allowMultiple = false, }: { filterName: string; filters: string[]; onClick?: () => void; + allowMultiple?: boolean; }) { - const { updateFilters } = useFilterInputStore(); - const inputData: { [key: string]: string } = {}; + const { updateFilters, updateMultipleFilters, filters: currentFilters } = useFilterInputStore(); + const recordFilterInput = (filter: string) => { - inputData[filterName] = filter; - updateFilters(inputData); + if (allowMultiple) { + // Handle multiple selection for Tech stack + const currentSelected = (currentFilters[filterName] as string[]) || []; + const isSelected = currentSelected.includes(filter); + + let newSelected: string[]; + if (isSelected) { + // Remove from selection + newSelected = currentSelected.filter((item) => item !== filter); + } else { + // Add to selection + newSelected = [...currentSelected, filter]; + } + + updateMultipleFilters(filterName, newSelected); + } else { + // Handle single selection (original behavior) + const inputData: { [key: string]: string } = {}; + inputData[filterName] = filter; + updateFilters(inputData); + } + }; + + const isChecked = (filter: string): boolean => { + if (allowMultiple) { + const currentSelected = (currentFilters[filterName] as string[]) || []; + return currentSelected.includes(filter); + } + return false; }; const triggerClasses = clsx("text-sm font-medium", { @@ -35,25 +65,47 @@ export default function Filter({ {filterName} - - {filters.map((filter) => ( -
- recordFilterInput(filter)} - className="border-[#28282c] bg-[#141418] text-ox-purple transition data-[state=checked]:border-ox-purple data-[state=checked]:bg-ox-purple/20 data-[state=checked]:ring-2 data-[state=checked]:ring-ox-purple/50" - /> - -
- ))} -
+ {allowMultiple ? ( +
+ {filters.map((filter) => ( +
+ recordFilterInput(filter)} + className="border-[#28282c] bg-[#141418] text-ox-purple transition data-[state=checked]:border-ox-purple data-[state=checked]:bg-ox-purple/20 data-[state=checked]:ring-2 data-[state=checked]:ring-ox-purple/50" + /> + +
+ ))} +
+ ) : ( + + {filters.map((filter) => ( +
+ recordFilterInput(filter)} + className="border-[#28282c] bg-[#141418] text-ox-purple transition data-[state=checked]:border-ox-purple data-[state=checked]:bg-ox-purple/20 data-[state=checked]:ring-2 data-[state=checked]:ring-ox-purple/50" + /> + +
+ ))} +
+ )}
diff --git a/apps/web/src/components/ui/FiltersContainer.tsx b/apps/web/src/components/ui/FiltersContainer.tsx index 84c92feb..75f5d489 100644 --- a/apps/web/src/components/ui/FiltersContainer.tsx +++ b/apps/web/src/components/ui/FiltersContainer.tsx @@ -100,6 +100,7 @@ export default function FiltersContainer() { "Html", "Elixir", ]} + allowMultiple={true} /> ) => void; + updateMultipleFilters: (filterName: string, values: string[]) => void; resetFilters: () => void; } @@ -12,5 +13,9 @@ export const useFilterInputStore = create((set) => ({ set((state) => ({ filters: { ...state.filters, ...newFilter }, })), + updateMultipleFilters: (filterName, values) => + set((state) => ({ + filters: { ...state.filters, [filterName]: values }, + })), resetFilters: () => set({ filters: {} }), })); diff --git a/apps/web/src/types/filter.ts b/apps/web/src/types/filter.ts index db245af3..8b2f4f0a 100644 --- a/apps/web/src/types/filter.ts +++ b/apps/web/src/types/filter.ts @@ -48,7 +48,7 @@ export type ProjectProps = { }; export type UserInputFilterProps = { - "Tech stack"?: string; + "Tech stack"?: string | string[]; Popularity?: string; Competition?: string; Stage?: string; diff --git a/apps/web/src/utils/converter.ts b/apps/web/src/utils/converter.ts index 53a5c793..494dbbf3 100644 --- a/apps/web/src/utils/converter.ts +++ b/apps/web/src/utils/converter.ts @@ -112,7 +112,13 @@ export const convertUserInputToApiInput = ( const data: Partial = {}; if (filter["Tech stack"]) { - data.language = filter["Tech stack"]; + // Handle both single string and array of strings + if (Array.isArray(filter["Tech stack"])) { + // Join multiple languages with space (GitHub search syntax) + data.language = filter["Tech stack"].join(" "); + } else { + data.language = filter["Tech stack"]; + } } if (filter.Popularity) {