Skip to content

Commit

Permalink
✨ feat(profile): add filtering with tag feature (#54)
Browse files Browse the repository at this point in the history
Co-authored-by: Hein Thant Maung Maung <[email protected]>
  • Loading branch information
heinthanth and Hein Thant Maung Maung authored Oct 20, 2023
1 parent d4c0db2 commit 832066a
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 77 deletions.
25 changes: 15 additions & 10 deletions src/app/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -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}`,
Expand All @@ -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 (
<Container>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5">
<ProfileCardList profiles={profiles} />
</div>
<ProfileCardList profiles={profiles} />
<SpacingDivider size="lg" />
</Container>
);
Expand Down
206 changes: 139 additions & 67 deletions src/components/Profile/ProfileCardList/ProfileCardList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<TitleText
className={cn(
'inline-block cursor-pointer text-[10px] px-2 py-1 rounded-full mb-1 mr-[4px] bg-opacity-50 hover:bg-opacity-80',
generateColor()
)}
key={tag}
tag="span"
onClick={(e) => {
e.preventDefault();
router.push(`/profile?${tmpSearchParam.toString()}`);
}}
>
{tag}
</TitleText>
);
};

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();
<div>
{uniqueTags.map((tag) => (
<Tag key={tag} tag={tag} />
))}
</div>

return (
<div key={profile._id} className="self-stretch">
<Link href={`/profile/${profile.slugAsParams}`}>
<SquareBox
className={cn(
"w-full h-full transition ease-out cursor-pointer hover:opacity-80 hover:-translate-y-1 bg-opacity-30 min-h-[130px]",
bgColor
)}
>
<div className="flex flex-row items-center mb-2 space-x-2">
<div
className={cn(
"flex justify-center items-center h-10 w-10 rounded-full overflow-hidden relative",
bgColor
)}
>
{!!profile.image ? (
<Image
src={profile.image}
className="h-10 w-10 object-fit"
alt={profile.name}
fill
/>
) : (
profile.name?.trim()?.[0]
)}
</div>
<div className="flex-1">
<TitleText tag="h4" className="text-base">
{profile.name}
</TitleText>
</div>
</div>
<div className="mb-2">
{profile.tags?.slice(0, 8)?.map((tag) => (
<TitleText
<SpacingDivider size="lg" />

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5">
{filteredProfiles.map((profile) => {
const bgColor = generateColor();

return (
<div key={profile._id} className="self-stretch">
<Link href={`/profile/${profile.slugAsParams}`}>
<SquareBox
className={cn(
'w-full h-full transition ease-out cursor-pointer hover:opacity-80 hover:-translate-y-1 bg-opacity-30 min-h-[130px]',
bgColor
)}
>
<div className="flex flex-row items-center mb-2 space-x-2">
<div
className={cn(
"inline-block text-[10px] px-2 py-1 rounded-full mb-1 mr-[4px] bg-opacity-50 hover:bg-opacity-80",
'flex justify-center items-center h-10 w-10 rounded-full overflow-hidden relative',
bgColor
)}
key={tag}
tag="span"
onClick={(e) => {
e.preventDefault();
console.log(tag);
}}
>
{tag}
</TitleText>
))}
</div>
<TitleText
tag="h4"
className="text-sm md:overflow-hidden md:text-ellipsis md:line-clamp-3"
>
{profile.description}
</TitleText>
</SquareBox>
</Link>
</div>
);
})}
{!!profile.image ? (
<Image
src={profile.image}
className="h-10 w-10 object-fit"
alt={profile.name}
fill
/>
) : (
profile.name?.trim()?.[0]
)}
</div>
<div className="flex-1">
<TitleText tag="h4" className="text-base">
{profile.name}
</TitleText>
</div>
</div>
<div className="mb-2">
{profile.tags?.slice(0, 8)?.map((tag) => (
<Tag key={tag} tag={tag} />
))}
</div>
<TitleText
tag="h4"
className="text-sm md:overflow-hidden md:text-ellipsis md:line-clamp-3"
>
{profile.description}
</TitleText>
</SquareBox>
</Link>
</div>
);
})}
</div>
</>
);
};
Expand Down

0 comments on commit 832066a

Please sign in to comment.