From 927165602a24dc2cc3fbafc0448202d574d34944 Mon Sep 17 00:00:00 2001 From: "vishalkondle.dev" Date: Wed, 10 Jul 2024 18:21:55 +0530 Subject: [PATCH] feature: tags in forum --- app/(private)/forum/[forum]/page.tsx | 11 ++-- app/(private)/forum/page.tsx | 13 +++-- app/(private)/forum/tags/page.tsx | 76 ++++++++++++++++++++++++++++ app/api/forums/tags/route.ts | 36 +++++++++++++ components/Forum/Tag.tsx | 27 ++++++++++ components/Forum/index.ts | 1 + components/Skelton/Skelton.tsx | 4 +- hooks/useFetchData.tsx | 6 +-- lib/constants.tsx | 12 ++++- 9 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 app/(private)/forum/tags/page.tsx create mode 100644 app/api/forums/tags/route.ts create mode 100644 components/Forum/Tag.tsx diff --git a/app/(private)/forum/[forum]/page.tsx b/app/(private)/forum/[forum]/page.tsx index 43de757..9db006b 100644 --- a/app/(private)/forum/[forum]/page.tsx +++ b/app/(private)/forum/[forum]/page.tsx @@ -10,7 +10,7 @@ import { ForumType } from '@/components/Forum/Forum.types'; import { FORUM_ANSWERS_SORT_OPTIONS } from '@/lib/constants'; const ForumPage = ({ params }: { params: { forum: string } }) => { - const { data, refetch } = useFetchData(`/api/forums?_id=${params.forum}`); + const { data, refetch, loading } = useFetchData(`/api/forums?_id=${params.forum}`); const [opened, { open, close }] = useDisclosure(false); const [value, setValue] = useState('highest'); const [answers, setAnswers] = useState(data?.[1]); @@ -37,7 +37,7 @@ const ForumPage = ({ params }: { params: { forum: string } }) => { setAnswers(sortIt); }, [value, data]); - if (!data?.length) { + if (loading) { return <>; } @@ -80,9 +80,10 @@ const ForumPage = ({ params }: { params: { forum: string } }) => { placeholder="Sort by" /> - {answers?.map((forum: ForumType) => ( - - ))} + {answers && + answers?.map((forum: ForumType) => ( + + ))} diff --git a/app/(private)/forum/page.tsx b/app/(private)/forum/page.tsx index 87e7477..ad4aabb 100644 --- a/app/(private)/forum/page.tsx +++ b/app/(private)/forum/page.tsx @@ -5,9 +5,10 @@ import { useDisclosure, useMediaQuery } from '@mantine/hooks'; import { ForumItem, AskQuestion } from '@/components/Forum'; import useFetchData from '@/hooks/useFetchData'; import { ForumType } from '@/components/Forum/Forum.types'; +import Skelton from '@/components/Skelton/Skelton'; const ForumPage = () => { - const { data: forums } = useFetchData('/api/forums'); + const { data: forums, loading } = useFetchData('/api/forums'); const [opened, { open, close }] = useDisclosure(false); const isMobile = useMediaQuery('(max-width: 768px)'); @@ -17,9 +18,13 @@ const ForumPage = () => { - {forums?.map((forum: ForumType) => ( - - ))} + {loading ? ( + + ) : ( + forums?.map((forum: ForumType) => ( + + )) + )} diff --git a/app/(private)/forum/tags/page.tsx b/app/(private)/forum/tags/page.tsx new file mode 100644 index 0000000..f870c7a --- /dev/null +++ b/app/(private)/forum/tags/page.tsx @@ -0,0 +1,76 @@ +'use client'; + +import React, { useState } from 'react'; +import { + Group, + rem, + SegmentedControl, + SimpleGrid, + Stack, + Text, + TextInput, + Title, +} from '@mantine/core'; +import { IconSearch } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { Tag } from '@/components/Forum'; +import useFetchData from '@/hooks/useFetchData'; +import Skelton from '@/components/Skelton/Skelton'; + +const TagsPage = () => { + const [sort, setSort] = useState('popular'); + const [search, setSearch] = useState(''); + const { data, loading } = useFetchData(`/api/forums/tags?sort=${sort}`); + const router = useRouter(); + + return ( + <> + + Tags + + A tag is a keyword or label that categorizes your question with other, similar questions. + Using the right tags makes it easier for others to find and answer your question. + + + } + placeholder="Filter by tag name" + w="min-width" + onChange={(e) => setSearch(e.currentTarget.value)} + value={search} + /> + + + + {loading ? ( + + ) : ( + data + ?.filter((tag) => String(tag._id).includes(search)) + ?.map((tag) => ( + router.push(`/forum/tagged/${tag._id}`)} + /> + )) + )} + + + + ); +}; + +export default TagsPage; diff --git a/app/api/forums/tags/route.ts b/app/api/forums/tags/route.ts new file mode 100644 index 0000000..158a329 --- /dev/null +++ b/app/api/forums/tags/route.ts @@ -0,0 +1,36 @@ +import dayjs from 'dayjs'; +import { getServerSession } from 'next-auth'; +import { NextRequest, NextResponse } from 'next/server'; +import startDb from '@/lib/db'; +import Forum from '@/models/Forum'; +import { authOptions } from '../../auth/[...nextauth]/authOptions'; +import { UserDataTypes } from '../../auth/[...nextauth]/next-auth.interfaces'; + +export const maxDuration = 60; +export const dynamic = 'force-dynamic'; + +export async function GET(req: NextRequest) { + const today = dayjs().startOf('day'); + try { + const session: UserDataTypes | null = await getServerSession(authOptions); + if (!session?.user) { + return NextResponse.json({ error: 'You are not authorized' }, { status: 401 }); + } + const sort = req.nextUrl.searchParams.get('sort')?.toString() ?? 'name'; + await startDb(); + const tags = await Forum.aggregate([ + { $unwind: '$tags' }, + { + $group: { + _id: '$tags', + total: { $sum: 1 }, + today: { $sum: { $cond: [{ $gte: ['$createdAt', today.toDate()] }, 1, 0] } }, + }, + }, + { $sort: sort === 'name' ? { _id: 1 } : sort === 'new' ? { createdAt: -1 } : { total: -1 } }, + ]); + return NextResponse.json(tags, { status: 200 }); + } catch (error: any) { + return NextResponse.json({ error: error?.message }, { status: 500 }); + } +} diff --git a/components/Forum/Tag.tsx b/components/Forum/Tag.tsx new file mode 100644 index 0000000..d743f1a --- /dev/null +++ b/components/Forum/Tag.tsx @@ -0,0 +1,27 @@ +import { Badge, Group, Paper, Stack, Text } from '@mantine/core'; +import React from 'react'; + +interface Props { + tag: string; + onClick: () => void; + count: number; + todayCount?: number; +} + +export const Tag = ({ tag, onClick, count, todayCount }: Props) => ( + + + + {tag} + + + + {count} questions + + + {todayCount} asked today + + + + +); diff --git a/components/Forum/index.ts b/components/Forum/index.ts index 48bf34c..cb2247d 100644 --- a/components/Forum/index.ts +++ b/components/Forum/index.ts @@ -2,3 +2,4 @@ export { AskQuestion } from './AskQuestion'; export { Forum } from './Forum'; export { ForumItem } from './ForumItem'; export { ReplyToQuestion } from './ReplyToQuestion'; +export { Tag } from './Tag'; diff --git a/components/Skelton/Skelton.tsx b/components/Skelton/Skelton.tsx index af6aea8..d7f80bd 100644 --- a/components/Skelton/Skelton.tsx +++ b/components/Skelton/Skelton.tsx @@ -1,7 +1,7 @@ import { Skeleton } from '@mantine/core'; import React from 'react'; -const Skelton = ({ items = 4 }: { items?: number }) => - [...Array(items)].map((_, i) => ); +const Skelton = ({ items = 4, height = 100 }: { items?: number; height?: number }) => + [...Array(items)].map((_, i) => ); export default Skelton; diff --git a/hooks/useFetchData.tsx b/hooks/useFetchData.tsx index b8f56b7..6660ffa 100644 --- a/hooks/useFetchData.tsx +++ b/hooks/useFetchData.tsx @@ -20,9 +20,9 @@ export default function useFetchData(url: string, cb?: () => void) { const res: any[] | any = await apiCall(url, null, 'GET', cb); dispatch(set(res?.data)); dispatch(stop()); - } catch (error) { - failure(String(error) || 'Something went wrong'); - router.back(); + } catch (err: any) { + failure(String(err) || 'Something went wrong'); + router.push('/'); } }; diff --git a/lib/constants.tsx b/lib/constants.tsx index 4272f0c..ad14d9a 100644 --- a/lib/constants.tsx +++ b/lib/constants.tsx @@ -41,6 +41,7 @@ import { IconPrompt, IconRobot, IconStar, + IconTagsFilled, IconTrash, IconWallet, } from '@tabler/icons-react'; @@ -130,7 +131,16 @@ export const APPS: AppType[] = [ color: 'green', sidebar: [{ label: 'Calendar', path: '/calendar', icon: }], }, - { label: 'Forum', path: '/forum', icon: , color: 'indigo', sidebar: [] }, + { + label: 'Forum', + path: '/forum', + icon: , + color: 'indigo', + sidebar: [ + { label: 'Home', path: '/forum', icon: }, + { label: 'Tags', path: '/forum/tags', icon: }, + ], + }, { label: 'Passwords', path: '/passwords', icon: , color: 'red', sidebar: [] }, { label: 'Chat', path: '/chat', icon: , color: 'pink', sidebar: [] }, { label: 'Wallet', path: '/wallet', icon: , color: 'grape', sidebar: [] },