From 832066a8b84328211b9c974203ef2ee9a0de2956 Mon Sep 17 00:00:00 2001 From: Hein Thant Date: Fri, 20 Oct 2023 19:41:00 +0700 Subject: [PATCH] :sparkles: feat(profile): add filtering with tag feature (#54) Co-authored-by: Hein Thant Maung Maung --- src/app/profile/page.tsx | 25 ++- .../ProfileCardList/ProfileCardList.tsx | 206 ++++++++++++------ 2 files changed, 154 insertions(+), 77 deletions(-) diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index 1a4b624..adeddf7 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -1,10 +1,10 @@ -import Container from "@/components/Common/Container/Container"; -import APP_CONFIG from "@/config/config"; -import { Metadata } from "next"; +import Container from '@/components/Common/Container/Container'; +import APP_CONFIG from '@/config/config'; +import { Metadata } from 'next'; -import ProfileCardList from "@/components/Profile/ProfileCardList/ProfileCardList"; -import { allProfiles } from "contentlayer/generated"; -import SpacingDivider from "@/components/Common/SpacingDivider/SpacingDivider"; +import ProfileCardList from '@/components/Profile/ProfileCardList/ProfileCardList'; +import { allProfiles } from 'contentlayer/generated'; +import SpacingDivider from '@/components/Common/SpacingDivider/SpacingDivider'; export const metadata: Metadata = { title: `Profile List | ${APP_CONFIG.title}`, @@ -16,12 +16,17 @@ const getAllProfileList = async () => { }; const ProfileListPage = async () => { - const profiles = await getAllProfileList(); + const profiles = (await getAllProfileList()).map((profile) => { + // just clean some invisible space like \r \n \t in the data + return { + ...profile, + tags: (profile.tags ?? []).map((tag) => tag.trim()), + }; + }); + return ( -
- -
+
); diff --git a/src/components/Profile/ProfileCardList/ProfileCardList.tsx b/src/components/Profile/ProfileCardList/ProfileCardList.tsx index 653ac8c..a89fed1 100644 --- a/src/components/Profile/ProfileCardList/ProfileCardList.tsx +++ b/src/components/Profile/ProfileCardList/ProfileCardList.tsx @@ -1,84 +1,156 @@ -"use client"; +'use client'; -import TitleText from "@/components/Common/TitleText/TitleText"; -import SquareBox from "@/components/Ui/SquareBox/SquareBox"; -import { cn, generateColor } from "@/utils"; -import { Profile } from "contentlayer/generated"; -import Image from "next/image"; -import Link from "next/link"; +import SpacingDivider from '@/components/Common/SpacingDivider/SpacingDivider'; +import TitleText from '@/components/Common/TitleText/TitleText'; +import SquareBox from '@/components/Ui/SquareBox/SquareBox'; +import { cn, generateColor } from '@/utils'; +import { Profile } from 'contentlayer/generated'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useSearchParams, useRouter } from 'next/navigation'; +import { useMemo } from 'react'; type TPropsProfileCardList = { profiles: Profile[]; }; +const Tag = ({ tag }: { tag: string }) => { + const searchParams = useSearchParams(); + const router = useRouter(); + + const tmpSearchParam = new URLSearchParams(searchParams.toString()); + tmpSearchParam.set('tag', tag); + + return ( + { + e.preventDefault(); + router.push(`/profile?${tmpSearchParam.toString()}`); + }} + > + {tag} + + ); +}; + const ProfileCardList = ({ profiles }: TPropsProfileCardList) => { + const searchParams = useSearchParams(); + const tag = searchParams.get('tag') ?? ''; + + const uniqueTags = useMemo(() => { + const tags: string[] = []; + const tmpSet = new Set(profiles.map((profile) => profile.tags ?? []).flat()); + tmpSet.forEach((tag) => void tags.push(tag)); + return tags; + }, [profiles]); + + const filteredProfiles = useMemo(() => { + if (tag.length === 0) return profiles; + + const filtered = profiles.filter((profile) => { + let found = false; + + profile.tags?.forEach((pTag) => { + const profileTag = pTag.toLowerCase(); + const searchTag = tag.toLowerCase(); + + if (profileTag === searchTag) { + // exact match + found = true; + } else if (profileTag === searchTag + 'js' || profileTag === searchTag + '.js') { + // try add js or .js extension to search tag + found = true; + } else if ( + profileTag === searchTag.replace(/\.js$/, '') || + profileTag === searchTag.replace(/js$/, '') + ) { + // try remove js or .js extension from search tag + found = true; + } else if (profileTag + 'js' === searchTag || profileTag + '.js' === searchTag) { + found = true; + } else if ( + profileTag === searchTag.replace(/\.js$/, '') + 'js' || + profileTag === searchTag.replace(/js$/, '') + '.js' + ) { + // try swap .js and js extension + found = true; + } + }); + return found; + }); + return filtered; + }, [profiles, tag]); + return ( <> - {profiles.map((profile) => { - const bgColor = generateColor(); +
+ {uniqueTags.map((tag) => ( + + ))} +
- return ( -
- - -
-
- {!!profile.image ? ( - {profile.name} - ) : ( - profile.name?.trim()?.[0] - )} -
-
- - {profile.name} - -
-
-
- {profile.tags?.slice(0, 8)?.map((tag) => ( - + +
+ {filteredProfiles.map((profile) => { + const bgColor = generateColor(); + + return ( +
+ + +
+
{ - e.preventDefault(); - console.log(tag); - }} > - {tag} - - ))} -
- - {profile.description} - - - -
- ); - })} + {!!profile.image ? ( + {profile.name} + ) : ( + profile.name?.trim()?.[0] + )} +
+
+ + {profile.name} + +
+
+
+ {profile.tags?.slice(0, 8)?.map((tag) => ( + + ))} +
+ + {profile.description} + + + +
+ ); + })} +
); };