Skip to content

Commit

Permalink
add new filter on browse (#1049)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbeno authored Mar 6, 2025
1 parent 48e2e09 commit bf3cfea
Show file tree
Hide file tree
Showing 17 changed files with 917 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function BrowseProjectsContextProvider({ children }: BrowseProjectsContex
ecosystemIds: filters.ecosystemIds.length ? filters.ecosystemIds : undefined,
categoryIds: filters.categoryIds.length ? filters.categoryIds : undefined,
sortBy: filters.sortBy ?? undefined,
search: filters.search ?? undefined,
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,97 +1,32 @@
import { Filter } from "lucide-react";
import { useMemo } from "react";

import { ProjectCategoryReactQueryAdapter } from "@/core/application/react-query-adapter/project-category";

import { Badge } from "@/design-system/atoms/badge";
import { Button } from "@/design-system/atoms/button/variants/button-default";
import { Popover } from "@/design-system/atoms/popover";
import { Typo } from "@/design-system/atoms/typo";
import { CheckboxButton } from "@/design-system/molecules/checkbox-button";

import { EcosystemFilter } from "@/shared/features/filters/ecosystem-filter/ecosystem-filter";
import { LanguageFilter } from "@/shared/features/filters/language-filter/language-filter";
import { CategoriesFilter } from "@/shared/filters/categories-filter/categories-filter";
import { EcosystemsFilter } from "@/shared/filters/ecosystems-filter/ecosystems-filter";
import { LanguagesFilter } from "@/shared/filters/languages-filter/languages-filter";
import { ProjectTagsFilter } from "@/shared/filters/project-tags-filter/project-tags-filter";
import { Input } from "@/shared/ui/input";

import { useBrowseProjectsContext } from "./browse-projects-filters.context";

export function BrowseProjectsFilters() {
const {
filters: { values: filters, set, count, clear, isCleared },
filters: { values: filters, set },
} = useBrowseProjectsContext();

const { data } = ProjectCategoryReactQueryAdapter.client.useGetProjectCategories({});
const categories = useMemo(() => data?.categories ?? [], [data?.categories]);

return (
<Popover placement="bottom-end">
<Popover.Trigger>
{() => (
<div>
<Button
variant={count ? "primary" : "secondary"}
size="sm"
startIcon={{ component: Filter, classNames: { base: "text-components-buttons-button-secondary-fg" } }}
iconOnly={!count}
endContent={
count ? (
<Badge size="xxs" shape="rounded" color={count ? "brand" : "grey"} variant={count ? "solid" : "flat"}>
{count}
</Badge>
) : null
}
/>
</div>
)}
</Popover.Trigger>

<Popover.Content>
{() => (
<div className="flex min-w-[250px] max-w-[360px] flex-col gap-lg">
<div className="flex items-center justify-between gap-md">
<Typo>Filters</Typo>

{!isCleared ? (
<Button onClick={clear} size="sm" variant="secondary">
Clear
</Button>
) : null}
</div>

<LanguageFilter selectedLanguages={filters.languageIds} onSelect={languageIds => set({ languageIds })} />

<EcosystemFilter
selectedEcosystems={filters.ecosystemIds}
onSelect={ecosystemIds => set({ ecosystemIds })}
/>

{categories.length ? (
<div className="flex flex-col gap-lg">
<Typo size="xs" color="secondary">
Categories
</Typo>

<div className="flex flex-wrap gap-xs">
{categories.map(category => (
<CheckboxButton
key={`project-category-${category.id}`}
value={filters.categoryIds.includes(category.id)}
onChange={checked => {
set({
categoryIds: checked
? [...filters.categoryIds, category.id]
: filters.categoryIds.filter(id => id !== category.id),
});
}}
>
{category.name}
</CheckboxButton>
))}
</div>
</div>
) : null}
</div>
)}
</Popover.Content>
</Popover>
<div className="flex flex-col items-center justify-start gap-4 xl:flex-row xl:justify-between">
<div className="w-full flex-1 xl:w-fit">
<Input
placeholder="Search"
className="w-full"
value={filters.search}
onChange={e => set({ search: e.target.value })}
/>
</div>
<div className="flex w-full flex-1 flex-row flex-wrap items-center justify-start gap-2 xl:w-fit xl:flex-[3] xl:justify-end">
<LanguagesFilter languagesIds={filters.languageIds} onSelect={languageIds => set({ languageIds })} />
<EcosystemsFilter ecosystemsIds={filters.ecosystemIds} onSelect={ecosystemIds => set({ ecosystemIds })} />
<CategoriesFilter categoriesIds={filters.categoryIds} onSelect={categoryIds => set({ categoryIds })} />
<ProjectTagsFilter tags={filters.tags} onSelect={tags => set({ tags })} />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type BrowseProjectsContextFilter = {
ecosystemIds: string[];
categoryIds: string[];
sortBy: string | undefined;
search: string | undefined;
};

export const DEFAULT_FILTER: BrowseProjectsContextFilter = {
Expand All @@ -18,6 +19,7 @@ export const DEFAULT_FILTER: BrowseProjectsContextFilter = {
ecosystemIds: [],
categoryIds: [],
sortBy: undefined,
search: undefined,
};

export type BrowseProjectsContextReturn = {
Expand Down
49 changes: 3 additions & 46 deletions app/(saas)/projects/_features/browse-projects/browse-projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,24 @@ import {
CardProjectMarketplace,
CardProjectMarketplaceLoading,
} from "@/design-system/molecules/cards/card-project-marketplace";
import { Tabs } from "@/design-system/molecules/tabs/tabs";

import { BaseLink } from "@/shared/components/base-link/base-link";
import { EmptyStateLite } from "@/shared/components/empty-state-lite/empty-state-lite";
import { ErrorState } from "@/shared/components/error-state/error-state";
import { ShowMore } from "@/shared/components/show-more/show-more";
import { PROJECT_TAG, PROJECT_TAG_METADATA } from "@/shared/constants/project-tags";
import { NEXT_ROUTER } from "@/shared/constants/router";
import { TypographyH2, TypographyMuted } from "@/shared/ui/typography";

import { BrowseProjectsFilters } from "../browse-projects-filters/browse-projects-filters";

const ALL_TAB = {
id: "ALL",
children: "All",
} as const;

const TABS: { id: PROJECT_TAG | "ALL"; children: string }[] = [
ALL_TAB,
...Object.values(PROJECT_TAG).map(tag => ({
id: tag,
children: PROJECT_TAG_METADATA[tag].label,
})),
];

function Safe() {
const { filters, queryParams } = useBrowseProjectsContext();
const { queryParams } = useBrowseProjectsContext();

const { data, isLoading, isError, hasNextPage, fetchNextPage, isFetchingNextPage } =
ProjectReactQueryAdapter.client.useGetProjectsV2({
queryParams: { ...queryParams, pageSize: 16 },
});

const projects = useMemo(() => data?.pages.flatMap(({ projects }) => projects) ?? [], [data]);
const count = useMemo(() => data?.pages[0]?.totalItemNumber ?? 0, [data]);

const renderProjects = useCallback(() => {
if (isLoading) {
Expand Down Expand Up @@ -95,35 +78,9 @@ function Safe() {
}, [projects, isError, isLoading]);

return (
<section className={"flex flex-col gap-3xl"}>
<header className="flex flex-col gap-md">
<div className="flex gap-2">
<TypographyH2>Browse Projects</TypographyH2>
<TypographyH2 className="text-muted-foreground">({count})</TypographyH2>
</div>

<TypographyMuted>
Discover innovative ideas, creative solutions, and detailed work that showcases unique expertise and impactful
results.
</TypographyMuted>
</header>

<section className={"flex flex-col"}>
<div className="flex flex-col gap-3xl">
<header className="flex flex-row items-start justify-between gap-xl">
<Tabs
variant={"solid"}
tabs={TABS}
selectedId={filters.values.tags[0] ?? ALL_TAB.id}
onTabClick={id => {
filters.set({ tags: id === ALL_TAB.id ? [] : [id as PROJECT_TAG] });
}}
classNames={{
base: "flex-wrap",
}}
/>

<BrowseProjectsFilters />
</header>
<BrowseProjectsFilters />

<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{renderProjects()}
Expand Down
Loading

0 comments on commit bf3cfea

Please sign in to comment.