Skip to content

Commit

Permalink
Merge pull request #24 from kudos-ink/feat/serach_params_filters
Browse files Browse the repository at this point in the history
[Feat] Search and filters
  • Loading branch information
leapalazzolo authored Jan 10, 2024
2 parents 22a1f03 + fb00cff commit 63bbe59
Show file tree
Hide file tree
Showing 22 changed files with 1,242 additions and 397 deletions.
70 changes: 35 additions & 35 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
import Filter from "@/components/filter";
import { title, subtitle } from "@/components/primitives";
import Search from "@/components/search";
import { SEARCH_OPTIONS, LANGUAGES_OPTIONS } from "@/data/filters";
import ContributionsTable from "@/components/contributions-table/table";
import Toolbar from "@/components/filters/toolbar";
import { title } from "@/components/primitives";
import { queryDatabase } from "@/lib/notion";
import { SearchParams } from "@/types/filters";
import {
processNotionFilters,
transformNotionDataToContributions,
} from "@/utils/notion";

interface IHomeProps {
searchParams: SearchParams;
}

export default async function Home({ searchParams }: IHomeProps) {
const filter = processNotionFilters(searchParams);
const data = await queryDatabase({
page_size: 10,
filter,
});
const contributions = transformNotionDataToContributions(data);
const items = {
data: contributions,
hasMore: data.has_more,
nextCursor: data.next_cursor ?? undefined,
};

export default function Home() {
return (
<div>
<section className="flex flex-col items-center py-8 md:py-10">
<h1 className={title()}>Find Collaborations,</h1>
<h1 className={title()}>Collect Kudos</h1>
</section>
<div className="flex flex-col items-center gap-4 py-8 md:py-10">
<div className="inline-block max-w-lg text-center justify-center">
<Search placeholder="Search" emoji="🔍" items={SEARCH_OPTIONS} />
<h2 className={subtitle({ class: "mt-4" })}>Banner</h2>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex justify-around items-stretch gap-4">
<Filter
placeholder="Languages"
emoji={"💪"}
items={LANGUAGES_OPTIONS}
/>
<Filter
placeholder="Interests"
emoji={"🪄"}
items={LANGUAGES_OPTIONS}
<div className="flex flex-col gap-4">
<Toolbar searchParams={searchParams} />
<div>
<ContributionsTable
items={items}
queries={{
page_size: 10,
filter,
}}
/>
{/* <div>
<Bounties
currency="USD"
defaultValue={100}
maxValue={10000}
emoji="💰"
/>
</div> */}
</div>
<div className="flex justify-end">
<div>
<h2 className={subtitle({ class: "mt-4" })}>Sort</h2>
</div>
</div>
</div>
</div>
Expand Down
19 changes: 0 additions & 19 deletions app/table/page.tsx

This file was deleted.

12 changes: 11 additions & 1 deletion assets/icons.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import * as React from "react";
import { IconSvgProps } from "@/types/index";

export const CircledCross = ({ size = 24,
width,
height,
...props }: IconSvgProps) => (
<svg
height={size || height}
viewBox="0 0 24 24"
width={size || width}
{...props}>
<path d="M12 2a10 10 0 1010 10A10.016 10.016 0 0012 2zm3.36 12.3a.754.754 0 010 1.06.748.748 0 01-1.06 0l-2.3-2.3-2.3 2.3a.748.748 0 01-1.06 0 .754.754 0 010-1.06l2.3-2.3-2.3-2.3A.75.75 0 019.7 8.64l2.3 2.3 2.3-2.3a.75.75 0 011.06 1.06l-2.3 2.3z" fill="currentColor"></path> </svg>
)
export const HeartFilledIcon = ({
size = 24,
width,
Expand Down
14 changes: 9 additions & 5 deletions components/contributions-table/row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,27 @@ interface IContentProps {
title: string;
project: string;
repository: string;
language: string; //TODO: make it an enum type with available languages from filters
languages: string[]; //TODO: make it an enum type with available languages from filters
}
export const Content = ({
title,
project,
repository,
language,
languages,
}: IContentProps) => {
return (
<div className="flex flex-col space-y-unit-1 md:space-y-0">
<h3 className="font-semibold max-w-64 sm:truncate sm:max-w-80 lg:max-w-64 xl:max-w-96">
{title}
</h3>
<span className="text-small text-default-500 truncate md:hidden">{`${project} / ${repository}`}</span>
<Chip className="mt-[1px]" variant="bordered" size="sm">
{language}
</Chip>
<div className="flex gap-1 mt-[1px]">
{languages.map((language, idx) => (
<Chip key={idx} className="mx-1" variant="bordered" size="sm">
{language}
</Chip>
))}
</div>
</div>
);
};
Expand Down
40 changes: 27 additions & 13 deletions components/contributions-table/table.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import React from "react";
import React, { useEffect, useState } from "react";
import { useMediaQuery } from "react-responsive";
import {
Table as NuiTable,
Expand All @@ -15,13 +15,23 @@ import { Contribution, PaginatedContributions } from "@/types/contribution";
import { ExternalLink, Content, Labels, Time, Project } from "./row";
import { useContributions } from "@/hooks/useContributions";
import { KudosQueryParameters } from "@/lib/notion/types";
import { isNotNull } from "@/utils/type-guard";

interface IColumn {
name: string;
uid: string;
}

interface ITableProps {
items: PaginatedContributions;
queries?: Partial<KudosQueryParameters>;
}
export const Table = ({ items, queries = {} }: ITableProps) => {
const [columns, setColumns] = useState<IColumn[]>([
{ name: "PROJECT", uid: "project" },
{ name: "CONTENT", uid: "content" },
{ name: "LABELS", uid: "labels" },
{ name: "DATE", uid: "date" },]);

const {
data: results,
fetchNextPage,
Expand All @@ -32,19 +42,23 @@ export const Table = ({ items, queries = {} }: ITableProps) => {
onLoadMore: fetchNextPage,
});

const isMobile = useMediaQuery({ maxWidth: 639 }); // tailwind lg default: 640px
const isLaptop = useMediaQuery({ minWidth: 1024 }); // tailwind lg default: 1024px
useEffect(() => {
setColumns([
{ name: "PROJECT", uid: "project" },
{ name: "CONTENT", uid: "content" },
...(isLaptop ? [{ name: "LABELS", uid: "labels" }] : []),
{ name: "DATE", uid: "date" },
...(isMobile ? [] : [{ name: "ACTIONS", uid: "actions" }]),
]);
}, [isMobile, isLaptop]);


const contributions = React.useMemo(() => {
return results?.pages.flatMap((page) => page.data) || [];
}, [results]);

const isMobile = useMediaQuery({ maxWidth: 639 }); // tailwind lg default: 640px
const isLaptop = useMediaQuery({ minWidth: 1024 }); // tailwind lg default: 1024px
const COLUMNS = [
{ name: "PROJECT", uid: "project" },
{ name: "CONTENT", uid: "content" },
isLaptop ? { name: "LABELS", uid: "labels" } : null,
{ name: "DATE", uid: "date" },
isMobile ? null : { name: "ACTIONS", uid: "actions" },
].filter(isNotNull);

const renderCell = React.useCallback(
(item: Contribution, columnKey: React.Key) => {
Expand All @@ -65,7 +79,7 @@ export const Table = ({ items, queries = {} }: ITableProps) => {
title={item.title}
project={item.project}
repository={item.repository}
language={item.language}
languages={item.languages}
/>
);
case "labels":
Expand Down Expand Up @@ -120,7 +134,7 @@ export const Table = ({ items, queries = {} }: ITableProps) => {
td: "px-2 sm:px-inherit",
}}
>
<TableHeader columns={COLUMNS}>
<TableHeader columns={columns}>
{(column) => (
<TableColumn
key={column.uid}
Expand Down
34 changes: 0 additions & 34 deletions components/filter.tsx

This file was deleted.

39 changes: 39 additions & 0 deletions components/filters/clear-filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";
import React from "react";
import { usePathname, useSearchParams } from "next/navigation";
import { useRouter } from "next/navigation";
import { Chip } from "@nextui-org/chip";
import { createUrl } from "@/utils/url";

interface IClearFilters {
param?: string;
value: string;
onClear: () => void;
}

export const ClearFilters = ({ param, value, onClear }: IClearFilters) => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const clearSearchParams = () => {
if (!!param) {
const optionNameLowerCase = param.toLowerCase();
const optionSearchParams = new URLSearchParams(searchParams.toString());
optionSearchParams.delete(optionNameLowerCase);
const optionUrl = createUrl(pathname, optionSearchParams);
router.replace(optionUrl);
} else {
router.replace(pathname);
}
onClear();
};
return (
<div className="flex gap-4">
<Chip variant="solid" onClose={clearSearchParams}>
{value}
</Chip>
</div>
);
};

export default ClearFilters;
75 changes: 75 additions & 0 deletions components/filters/select-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use client";
import React from "react";
import { usePathname, useSearchParams } from "next/navigation";
import { useRouter } from "next/navigation";
import { Select, SelectItem } from "@nextui-org/select";
import { FilterOption } from "@/types/filters";
import { createUrl } from "@/utils/url";
import Emoji from "../emoji";
import { CircledCross } from "@/assets/icons";

interface ISelectFilterProps {
placeholder: string;
mainEmoji: string;
options: FilterOption[];
selectedKey?: string;
onSelect: (value: string) => void;
}
export const SelectFilter = ({
placeholder,
mainEmoji,
options,
selectedKey,
onSelect,
}: ISelectFilterProps) => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();

const handleSelectionChange = (selection: unknown) => {
const selectedValue = Array.from(selection as Iterable<string>)[0];
onSelect(selectedValue);
const optionNameLowerCase = placeholder.toLowerCase();
const optionSearchParams = new URLSearchParams(searchParams.toString());
if (selectedValue) {
optionSearchParams.set(optionNameLowerCase, selectedValue);
} else {
optionSearchParams.delete(optionNameLowerCase)
}
const optionUrl = createUrl(pathname, optionSearchParams);
router.replace(optionUrl, { scroll: false });

};
const resetFilter = () => {
handleSelectionChange("")
}
return (
<Select
aria-label={`Select Filter ${placeholder}`}
color="default"
variant="faded"
size="sm"
placeholder={placeholder}
selectionMode="single"
startContent={<Emoji emoji={mainEmoji} className="text-sm" />}
endContent={selectedKey && <CircledCross className="z-10 appearance-none outline-none select-none transition-opacity opacity-70 hover:opacity-100 cursor-pointer active:opacity-disabled tap-highlight-transparent text-large" onClick={resetFilter} />}
selectedKeys={selectedKey ? new Set([selectedKey]) : new Set()}
onSelectionChange={handleSelectionChange}
>
{options.map(({ emoji, label, value }) => {

return (
<SelectItem
key={value}
value={value}
startContent={<Emoji emoji={emoji} />}
>
{label}
</SelectItem>
);
})}
</Select>
);
};

export default SelectFilter;
Loading

0 comments on commit 63bbe59

Please sign in to comment.