Skip to content

Commit

Permalink
refactor: common listing page components
Browse files Browse the repository at this point in the history
  • Loading branch information
yongenaelf committed Sep 9, 2024
1 parent a1dbfc2 commit c61bca4
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const FormSchema = z.object({
items: z.array(z.string()),
});

export function TutorialFilter({
export function Filter({
searchKey,
title,
options,
Expand Down
63 changes: 63 additions & 0 deletions components/listing-page/list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Image from "next/image";
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import Link from "next/link";
import clsx from "clsx";

interface Item {
id: string;
img: string;
title: string;
description: string;
tags: string[];
link: string;
}

export function List({ list }: { list: Item[] }) {

if (list.length === 0) {
return <p>No items for the current search / filter.</p>
}

return (
<div className="grid grid-flow-row-dense grid-cols-2 lg:grid-cols-3 gap-8">
{list.map((i) => (
<Link key={i.id} href={i.link}>
<Card className="flex flex-col h-full">
<div className="overflow-hidden">
<Image
alt="Image"
width={300}
height={300}
className="h-auto w-full object-cover transition-all hover:scale-105"
src={i.img}
/>
</div>
<CardHeader className="flex-grow">
<CardTitle>{i.title}</CardTitle>
<CardDescription>{i.description}</CardDescription>
</CardHeader>
<CardFooter>
{i.tags.map((tag, index) => (
<span
key={tag}
className={clsx(
"inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10",
{ "ml-3": index !== 0 }
)}
>
{tag}
</span>
))}
</CardFooter>
</Card>
</Link>
))}
</div>
);
}
41 changes: 41 additions & 0 deletions components/listing-page/search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"use client";

import { useSearchParams, useRouter, usePathname } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { Input } from "@/components/ui/input";

export function Search({placeholder}: {placeholder?: string}) {
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const [search, setSearch] = useState(searchParams.get("search") || undefined);

const createQueryString = useCallback(
(value?: string) => {
const params = new URLSearchParams(searchParams.toString());
params.delete("search");

if (value) params.set("search", value);

const res = params.toString();

if (res) return "?" + res;

return "";
},
[searchParams]
);

useEffect(() => {
router.push(pathname + createQueryString(search));
}, [search]);

return (
<Input
value={search}
placeholder={placeholder ?? "Search..."}
onChange={(e) => setSearch(e.target.value)}
className="w-1/2 mb-8"
/>
);
}
4 changes: 2 additions & 2 deletions components/tutorial/filter-lang.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TutorialFilter } from "./filter";
import { Filter } from "@/components/listing-page/filter";

const lang = [
{
Expand All @@ -12,5 +12,5 @@ const lang = [
];

export function TutorialLangFilter() {
return <TutorialFilter searchKey="lang" title="Language" options={lang} />;
return <Filter searchKey="lang" title="Language" options={lang} />;
}
4 changes: 2 additions & 2 deletions components/tutorial/filter-level.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TutorialFilter } from "./filter";
import { Filter } from "@/components/listing-page/filter";

const level = [
{
Expand All @@ -16,5 +16,5 @@ const level = [
];

export function TutorialLevelFilter() {
return <TutorialFilter searchKey="level" title="Level" options={level} />;
return <Filter searchKey="level" title="Level" options={level} />;
}
48 changes: 7 additions & 41 deletions components/tutorial/list.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
"use client";

import Image from "next/image";
import { useSearchParams } from "next/navigation";
import { useMemo } from "react";
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import Link from "next/link";
import { useTutorialList } from "@/data/client";
import { List } from "@/components/listing-page/list";

export function TutorialList() {
const searchParams = useSearchParams();
Expand All @@ -37,38 +29,12 @@ export function TutorialList() {
i.description.toLowerCase().includes(search)
);

return all;
return all.map((i) => ({
...i,
tags: [i.level, i.lang],
link: `/tutorials/${i.id}`,
}));
}, [data, searchParams]);

return (
<div className="grid grid-flow-row-dense grid-cols-2 lg:grid-cols-3 gap-8">
{list.map((i) => (
<Link key={i.id} href={`/tutorials/${i.id}`}>
<Card className="flex flex-col h-full">
<div className="overflow-hidden">
<Image
alt="Image"
width={300}
height={300}
className="h-auto w-full object-cover transition-all hover:scale-105"
src={i.img}
/>
</div>
<CardHeader className="flex-grow">
<CardTitle>{i.title}</CardTitle>
<CardDescription>{i.description}</CardDescription>
</CardHeader>
<CardFooter>
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 mr-3">
{i.level}
</span>
<span className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10">
{i.lang}
</span>
</CardFooter>
</Card>
</Link>
))}
</div>
);
return <List list={list} />;
}
40 changes: 2 additions & 38 deletions components/tutorial/search.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,5 @@
"use client";

import { useSearchParams, useRouter, usePathname } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { Input } from "@/components/ui/input";
import { Search } from "../listing-page/search";

export function TutorialSearch() {
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const [search, setSearch] = useState(searchParams.get("search") || undefined);

const createQueryString = useCallback(
(value?: string) => {
const params = new URLSearchParams(searchParams.toString());
params.delete("search");

if (value) params.set("search", value);

const res = params.toString();

if (res) return "?" + res;

return "";
},
[searchParams]
);

useEffect(() => {
router.push(pathname + createQueryString(search));
}, [search]);

return (
<Input
value={search}
placeholder="Search tutorials..."
onChange={(e) => setSearch(e.target.value)}
className="w-1/2 mb-8"
/>
);
return <Search placeholder="Search tutorials..." />
}

0 comments on commit c61bca4

Please sign in to comment.