Skip to content

Commit e8eed5e

Browse files
authored
Merge pull request #138 from CodeForStartup/feat/update-tag-page
feat: refactor tag page
2 parents a0996bb + 694686b commit e8eed5e

File tree

24 files changed

+273
-238
lines changed

24 files changed

+273
-238
lines changed

apps/web/@/messages/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"category": "Category",
88
"posts": "Posts",
99
"total_post_plural": "Total {total} {total, plural, =0 {post} =1 {post} other {posts}}",
10+
"post_plural": "{total} {total, plural, =0 {post} =1 {post} other {posts}}",
1011
"post": "{total, plural, =0 {post} =1 {post} other {posts}}",
1112
"pages": "Pages",
1213
"page": "Page",

apps/web/@/molecules/follower/user-profile/index.tsx

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,23 @@
11
import Link from "next/link"
2+
import { notFound } from "next/navigation"
23

4+
import { getUser } from "database"
35
import { getTranslations } from "next-intl/server"
46
import { Avatar, AvatarFallback, AvatarImage, Card, CardContent, CardHeader, Typography } from "ui"
57

6-
import APP_APIS from "@/constants/apis"
7-
import { TUserItem } from "@/types/users"
8-
import { generatePath } from "@/utils/generatePath"
9-
108
import FollowButton from "./follow-button"
119

1210
export type UserProfileProps = {
1311
authorId: string
1412
}
1513

1614
export async function UserProfile({ authorId }: UserProfileProps) {
17-
// const rawAuthor = await fetch(
18-
// `${process.env.NEXT_PUBLIC_FRONTEND_URL}${generatePath(APP_APIS.protected.user.GET, {
19-
// userId: authorId,
20-
// })}`,
21-
// {
22-
// method: "GET",
23-
// cache: "no-cache",
24-
// headers: {
25-
// "Content-Type": "application/json",
26-
// },
27-
// }
28-
// )
29-
const author: TUserItem = await rawAuthor?.json()
3015
const t = await getTranslations()
16+
const { data: author, error } = await getUser({ userId: authorId })
17+
18+
if (error) {
19+
return notFound()
20+
}
3121

3222
return (
3323
<div className="col-span-4">
@@ -58,13 +48,13 @@ export async function UserProfile({ authorId }: UserProfileProps) {
5848
</Typography>
5949
<div className="mt-4 flex w-full flex-1 divide-x">
6050
<div className="flex flex-1 flex-col items-center justify-center">
61-
<div className="font-bold">{author?._count?.post}</div>
51+
<div className="font-bold">{author?.totalPost}</div>
6252
<div className="hover:underline">
6353
<Link href={`/author/${author?.id}`}>{t("common.posts")}</Link>
6454
</div>
6555
</div>
6656
<div className="flex flex-1 flex-col items-center justify-center">
67-
<div className="font-bold">{author?._count?.followers}</div>
57+
<div className="font-bold">{author?.totalFollower}</div>
6858
<div className="hover:underline">
6959
<Link href={`/author/${author?.id}/followers`}>{t("common.followers")}</Link>
7060
</div>

apps/web/@/molecules/posts/post-detail/index.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TPostItem } from "database"
44
import { Typography } from "ui"
55

66
import APP_ROUTES from "@/constants/routes"
7-
import TagList from "@/molecules/tag/tag-list"
7+
import TagListMeta from "@/molecules/tag/tag-list-meta"
88
import PostMeta from "@/molecules/user/posts/post-meta"
99
import { generatePath } from "@/utils/generatePath"
1010

@@ -37,8 +37,12 @@ export default function PostDetail({ post }: PostDetailProps) {
3737

3838
<PostMeta post={post} />
3939

40-
<TagList
41-
tags={post?.tagOnPost}
40+
<TagListMeta
41+
tags={post?.tagOnPost?.map((tag) => ({
42+
id: tag.tag.id,
43+
slug: tag.tag.slug,
44+
name: tag.tag.name,
45+
}))}
4246
classes={{
4347
container: "mt-4",
4448
}}

apps/web/@/molecules/posts/post-item/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useTranslations } from "next-intl"
66
import { Typography } from "ui"
77

88
import APP_ROUTES from "@/constants/routes"
9-
import TagList from "@/molecules/tag/tag-list"
9+
import TagListMeta from "@/molecules/tag/tag-list-meta"
1010
import { generatePath } from "@/utils/generatePath"
1111

1212
import CommentButton from "./comment-button"
@@ -15,6 +15,7 @@ import PostMeta from "./post-meta"
1515

1616
export default function PostItem({ post }: { post: TPostItem }) {
1717
const t = useTranslations("common")
18+
console.log(post)
1819

1920
return (
2021
<div className="mb-4 flex rounded-sm border px-8 py-4">
@@ -34,8 +35,8 @@ export default function PostItem({ post }: { post: TPostItem }) {
3435

3536
<PostMeta post={post} />
3637

37-
<TagList
38-
tags={post?.tagOnPost}
38+
<TagListMeta
39+
tags={post?.tagOnPost?.map((tag) => tag?.tag)}
3940
classes={{
4041
container: "mt-2",
4142
}}

apps/web/@/molecules/posts/post-list/index.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ import React, { useCallback, useState } from "react"
44
import { useParams } from "next/navigation"
55

66
import { getPosts, TGetPostsRequest, TPostItem } from "database"
7+
import { cn } from "ui"
78

89
import InfiniteScroll from "@/molecules/infinite-scroll"
910

1011
import PostItem from "../post-item"
1112

1213
export type TPostListProps = {
1314
getPostParams?: TGetPostsRequest
15+
containerClassName?: string
1416
}
1517

16-
export default function PostList({ getPostParams = {} }: TPostListProps) {
18+
export default function PostList({ getPostParams = {}, containerClassName }: TPostListProps) {
1719
const searchParams = useParams()
1820
const [isLoading, setIsLoading] = useState(false)
1921
const [posts, setPosts] = useState<TPostItem[]>([])
@@ -37,12 +39,12 @@ export default function PostList({ getPostParams = {} }: TPostListProps) {
3739
}, [searchParams, page])
3840

3941
return (
40-
<div className="mt-4">
42+
<div className={cn("mt-4", containerClassName)}>
4143
<InfiniteScroll
4244
loading={isLoading}
4345
nextPage={loadPosts}
4446
>
45-
{posts?.map((post) => (
47+
{posts.map((post) => (
4648
<PostItem
4749
key={post.id}
4850
post={post}

apps/web/@/molecules/tag/tag-badge/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import React from "react"
22
import Link from "next/link"
33

4+
import { TTagItem } from "database"
45
import { Badge } from "ui"
56

67
import APP_ROUTES from "@/constants/routes"
7-
import { TTagItem } from "@/types/tags"
88
import { generatePath } from "@/utils/generatePath"
99

1010
interface TagBadgeProps {
@@ -20,7 +20,7 @@ const TagBadge: React.FC<TagBadgeProps> = ({ tag }) => {
2020
<Link
2121
key={tag.id}
2222
href={generatePath(APP_ROUTES.TAG, {
23-
tagId: tag.slug,
23+
tagId: tag.slug || tag.id,
2424
})}
2525
>
2626
<Badge

apps/web/@/molecules/tag/tag-detail/index.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
import Link from "next/link"
2+
import { notFound } from "next/navigation"
23

3-
import { Tags } from "database"
4+
import { getTag } from "database"
45
import { Tag } from "lucide-react"
5-
import { Button, Card, CardContent, CardHeader } from "ui"
6+
import { getTranslations } from "next-intl/server"
7+
import { Button, Card, CardContent, CardHeader, Typography } from "ui"
68

7-
export type TagDetailProp = {
8-
tag: Tags
9-
}
9+
export default async function TagDetail({ tagIdOrSlug }: { tagIdOrSlug: string }) {
10+
const t = await getTranslations()
11+
12+
const { data: tag, error } = await getTag({
13+
tagIdOrSlug,
14+
})
15+
16+
if (error) {
17+
return notFound()
18+
}
1019

11-
const TagDetail = ({ tag }: TagDetailProp) => {
1220
return (
1321
<div className="col-span-4">
1422
<Card>
@@ -19,12 +27,12 @@ const TagDetail = ({ tag }: TagDetailProp) => {
1927
</CardHeader>
2028
<CardContent>
2129
<div className="flex flex-col items-center justify-center gap-2">
22-
<h1 className="flex-1 text-center text-4xl font-extrabold text-slate-700">
30+
<Typography variant="h1">
2331
<Link href={`/tags/${tag?.slug || tag?.id}`}>{tag.name}</Link>
24-
</h1>
32+
</Typography>
2533
<div className="mt-4 flex w-full flex-1 divide-x">
2634
<div className="flex flex-1 flex-col items-center justify-center">
27-
<div className="font-bold text-slate-800">{tag?.totalPost || 0}</div>
35+
<div className="font-bold text-slate-800">{tag?._count?.tagOnPost || 0}</div>
2836
<div className="text-gray-400 hover:underline">
2937
<Link href={`/tags/${tag.id}`}>posts</Link>
3038
</div>
@@ -40,13 +48,11 @@ const TagDetail = ({ tag }: TagDetailProp) => {
4048
className="mt-4 w-full"
4149
variant="outline"
4250
>
43-
Follow
51+
{t("common.follow")}
4452
</Button>
4553
</div>
4654
</CardContent>
4755
</Card>
4856
</div>
4957
)
5058
}
51-
52-
export default TagDetail
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Link from "next/link"
2+
3+
import { TTagItem } from "database"
4+
import { useTranslations } from "next-intl"
5+
import { Card, CardContent, CardHeader, Typography } from "ui"
6+
7+
export default function TagItem({ tag }: { tag: TTagItem }) {
8+
const t = useTranslations("common")
9+
10+
return (
11+
<Link
12+
href={`/tags/${tag?.id}`}
13+
key={tag?.id}
14+
>
15+
<Card className="px-6 py-4 sm:col-span-1">
16+
<CardHeader className="flex-row justify-between overflow-hidden p-0">
17+
<Typography
18+
variant="h3"
19+
className="mb-1 break-words text-lg hover:underline"
20+
>
21+
#{tag?.name}
22+
</Typography>
23+
</CardHeader>
24+
<CardContent className="p-0">
25+
{tag?.description && <Typography variant="span">{tag?.description}</Typography>}
26+
<div>
27+
<Typography
28+
variant="span"
29+
className="font-semibold"
30+
>
31+
{t("post_plural", { total: tag?._count?.tagOnPost || 0 })}
32+
</Typography>
33+
</div>
34+
</CardContent>
35+
</Card>
36+
</Link>
37+
)
38+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from "react"
2+
3+
import { TTagItem } from "database"
4+
import { cn } from "ui"
5+
6+
import TagBadge from "../tag-badge"
7+
8+
export type TagListProps = {
9+
tags: Pick<TTagItem, "id" | "name" | "slug">[]
10+
classes?: {
11+
container?: string
12+
}
13+
}
14+
15+
export default function TagListMeta({
16+
tags,
17+
classes = {
18+
container: "",
19+
},
20+
}: TagListProps) {
21+
return (
22+
<div className={cn(classes?.container)}>
23+
{tags?.length > 0 &&
24+
tags?.map((tag) => (
25+
<TagBadge
26+
key={tag?.id}
27+
tag={tag}
28+
/>
29+
))}
30+
</div>
31+
)
32+
}

apps/web/@/molecules/tag/tag-list/index.tsx

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,54 @@
1-
import React from "react"
2-
3-
import { cn } from "ui"
4-
5-
import TagBadge from "../tag-badge"
6-
7-
export type TagListProps = {
8-
tags: {
9-
tag: {
10-
id: string
11-
name: string
12-
slug: string
13-
}
14-
}[]
15-
classes?: {
16-
container?: string
17-
}
18-
}
1+
"use client"
2+
3+
import React, { useCallback, useState } from "react"
4+
import { useParams } from "next/navigation"
5+
6+
import { getTags, TTagItem } from "database"
7+
8+
import InfiniteScroll from "@/molecules/infinite-scroll"
9+
10+
import TagItem from "../tag-item"
11+
12+
const TagList: React.FC = () => {
13+
const searchParams = useParams()
14+
const [isLoading, setIsLoading] = useState(false)
15+
const [tags, setTags] = useState<TTagItem[]>([])
16+
const [page, setPage] = useState(1)
17+
const [hasNextPage, setHasNextPage] = useState(true)
18+
19+
const loadTags = useCallback(async () => {
20+
if (!hasNextPage) return
21+
22+
setIsLoading(true)
23+
const { data } = await getTags({
24+
...searchParams,
25+
page,
26+
})
27+
28+
setTags((prev) => [...prev, ...data?.data])
29+
setHasNextPage(data?.totalPages > page)
30+
setIsLoading(false)
31+
setPage((prev) => prev + 1)
32+
}, [searchParams, page])
1933

20-
const TagList: React.FC<TagListProps> = ({
21-
tags,
22-
classes = {
23-
container: "",
24-
},
25-
}) => {
2634
return (
27-
<div className={cn(classes?.container)}>
28-
{tags?.length > 0 &&
29-
tags?.map(({ tag }) => (
30-
<TagBadge
31-
key={tag?.id}
32-
tag={tag}
33-
/>
34-
))}
35-
</div>
35+
<InfiniteScroll
36+
loading={isLoading}
37+
nextPage={loadTags}
38+
>
39+
<div className="mt-4">
40+
{tags?.length > 0 ? (
41+
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
42+
{tags.map((tag) => (
43+
<TagItem
44+
key={tag.id}
45+
tag={tag}
46+
/>
47+
))}
48+
</div>
49+
) : null}
50+
</div>
51+
</InfiniteScroll>
3652
)
3753
}
3854

0 commit comments

Comments
 (0)