diff --git a/package.json b/package.json
index b93934d9c..75ace9966 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
"bcrypt": "^5.1.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
+ "cms": "file:",
"date-fns": "^3.6.0",
"dayjs": "^1.11.10",
"discord-oauth2": "^2.11.0",
diff --git a/src/app/(main)/(pages)/question/page.tsx b/src/app/(main)/(pages)/question/page.tsx
index f23e2bded..24f35e3e9 100644
--- a/src/app/(main)/(pages)/question/page.tsx
+++ b/src/app/(main)/(pages)/question/page.tsx
@@ -21,9 +21,9 @@ import { getDisabledFeature, getUpdatedUrl, paginationData } from '@/lib/utils';
import db from '@/db';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
-import PostCard from '@/components/posts/PostCard';
import Pagination from '@/components/Pagination';
import { redirect } from 'next/navigation';
+import QuestionHeader from '@/components/questions/QuestionPost';
type QuestionsResponse = {
data: ExtendedQuestion[] | null;
@@ -48,6 +48,7 @@ const getQuestionsWithQuery = async (
upvotes: true,
downvotes: true,
totalanswers: true,
+ content: true,
tags: true,
slug: true,
createdAt: true,
@@ -141,28 +142,37 @@ export default async function QuestionsPage({
<>
{/* Header */}
-
+
Questions
-
-
+
+ New Question
+
+ {/* Next question button */}
+
{/* Content Area */}
- {/* Next question button */}
-
-
-
-
+
+
+
@@ -209,24 +219,13 @@ export default async function QuestionsPage({
-
- New Question
-
{/* Chat */}
{response?.data?.map((post) => (
-
-
-
-
-
-
-
-
-
- Most Voted
-
-
-
-
- Most Down Voted
-
-
-
-
- Most Recent
-
-
-
-
-
+ {answers.length > 0 && (
+
+
+
+
+
+
+
+
+ Most Voted
+
+
+
+
+ Most Down Voted
+
+
+
+
+ Most Recent
+
+
+
+
+
+ )}
-
+
{answers.map((post: any) => (
-
+
))}
-
+ {answers.length > 0 &&
}
+ {answers.length === 0 && (
+
+
No Answers
+
+ )}
);
};
diff --git a/src/app/questions/[slug]/@question/page.tsx b/src/app/questions/[slug]/@question/page.tsx
index a3c103af8..99983d0a2 100644
--- a/src/app/questions/[slug]/@question/page.tsx
+++ b/src/app/questions/[slug]/@question/page.tsx
@@ -3,8 +3,8 @@ import React from 'react';
import db from '@/db';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
-import PostCard from '@/components/posts/PostCard';
import Link from 'next/link';
+import QuestionPost from '@/components/questions/QuestionPost';
const SingleQuestionPage = async ({
params,
@@ -50,18 +50,19 @@ const SingleQuestionPage = async ({
return (
-
+
Go Back
diff --git a/src/app/questions/page.tsx b/src/app/questions/page.tsx
index fbe03aa6e..e0e10e5e3 100644
--- a/src/app/questions/page.tsx
+++ b/src/app/questions/page.tsx
@@ -21,9 +21,9 @@ import { getDisabledFeature, getUpdatedUrl, paginationData } from '@/lib/utils';
import db from '@/db';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
-import PostCard from '@/components/posts/PostCard';
import Pagination from '@/components/Pagination';
import { redirect } from 'next/navigation';
+import QuestionPost from '@/components/questions/QuestionPost';
type QuestionsResponse = {
data: ExtendedQuestion[] | null;
@@ -47,6 +47,7 @@ const getQuestionsWithQuery = async (
title: true,
upvotes: true,
downvotes: true,
+ content: true,
totalanswers: true,
tags: true,
slug: true,
@@ -218,7 +219,7 @@ export default async function Home({
{response?.data?.map((post) => (
-
= ({ questionId, answerId }) => {
onSuccess: (data) => {
toast.success(`${data.message}`);
if (questionId) {
- router.push('/questions');
+ router.push('/question');
}
},
onError: (error) => {
diff --git a/src/components/questions/AnswerPost.tsx b/src/components/questions/AnswerPost.tsx
new file mode 100644
index 000000000..a3a42df0c
--- /dev/null
+++ b/src/components/questions/AnswerPost.tsx
@@ -0,0 +1,168 @@
+'use client';
+import React from 'react';
+import { useState } from 'react';
+import { useTheme } from 'next-themes';
+import { Author, ExtendedAnswer } from '@/actions/question/types';
+import { useAction } from '@/hooks/useAction';
+import { createAnswer } from '@/actions/answer';
+import { toast } from 'sonner';
+import TextSnippet from '../posts/textSnippet';
+import MDEditor from '@uiw/react-md-editor';
+import { Button } from '../ui/button';
+import VoteForm from '../posts/form/form-vote';
+import { Avatar, AvatarFallback } from '../ui/avatar';
+import { FormPostErrors } from '../posts/form/form-errors';
+import { ROLES } from '@/actions/types';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+} from '../ui/dropdown-menu';
+import { MoreHorizontal } from 'lucide-react';
+import DeleteForm from '../posts/form/form-delete';
+
+interface IProps {
+ post: ExtendedAnswer;
+ sessionUser: Author | undefined | null;
+ reply?: boolean;
+ enableLink?: boolean;
+ isAnswer?: boolean;
+ questionId: number;
+}
+
+const AnswerPost: React.FC = ({
+ post,
+ sessionUser,
+ questionId,
+ reply = false,
+ isAnswer = true,
+}) => {
+ const { theme } = useTheme();
+ const [markDownValue, setMarkDownValue] = useState('');
+ const [enableReply, setEnableReply] = useState(false);
+
+ const handleMarkdownChange = (newValue?: string) => {
+ if (typeof newValue === 'string') {
+ setMarkDownValue(newValue);
+ }
+ };
+
+ const { execute, fieldErrors } = useAction(createAnswer, {
+ onSuccess: () => {
+ toast.success(`Answer created`);
+ if (!fieldErrors?.content) {
+ setEnableReply((prev) => !prev);
+ setMarkDownValue('');
+ }
+ },
+ onError: (error) => {
+ toast.error(error);
+ },
+ });
+
+ const handleSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+ execute({
+ content: markDownValue,
+ questionId,
+ parentId: isAnswer ? post?.id : undefined,
+ });
+ };
+
+ return (
+ <>
+
+
+
+
+ {post.author.name?.substring(0, 2).toUpperCase()}
+
+
+
+
+
+
{post.author.name}
+
+
{post.content}
+
+
+
+ {reply && (
+
+ )}
+
+
+
+
+
+
+ {(sessionUser?.role === ROLES.ADMIN ||
+ post?.author?.id === sessionUser?.id) && (
+
+ )}
+
+ {/*
+ Report spam
+ */}
+
+
+
+ {enableReply && (
+
+ )}
+
+ {post.responses &&
+ post.responses.length > 0 &&
+ post.responses.map((answer: ExtendedAnswer) => (
+
+ ))}
+
+
+
+ >
+ );
+};
+export default AnswerPost;
diff --git a/src/components/questions/QuestionPost.tsx b/src/components/questions/QuestionPost.tsx
new file mode 100644
index 000000000..90162c49f
--- /dev/null
+++ b/src/components/questions/QuestionPost.tsx
@@ -0,0 +1,212 @@
+'use client';
+import dayjs from 'dayjs';
+import relativeTime from 'dayjs/plugin/relativeTime';
+dayjs.extend(relativeTime);
+import React from 'react';
+import { useState } from 'react';
+import { useTheme } from 'next-themes';
+import { Author, ExtendedQuestion } from '@/actions/question/types';
+import { useAction } from '@/hooks/useAction';
+import { createAnswer } from '@/actions/answer';
+import { toast } from 'sonner';
+import { Answer } from '@prisma/client';
+import TextSnippet from '../posts/textSnippet';
+import Link from 'next/link';
+import MDEditor from '@uiw/react-md-editor';
+import { MessageSquareReply, MoreHorizontal, User } from 'lucide-react';
+import { Button } from '../ui/button';
+import VoteForm from '../posts/form/form-vote';
+import DeleteForm from '../posts/form/form-delete';
+import { FormPostErrors } from '../posts/form/form-errors';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+} from '../ui/dropdown-menu';
+import { ROLES } from '@/actions/types';
+
+interface IProps {
+ post: ExtendedQuestion;
+ sessionUser: Author | undefined | null;
+ reply?: boolean;
+ enableLink?: boolean;
+ isAnswer?: boolean;
+ questionId: number;
+}
+
+const isExtendedQuestion = (
+ post: ExtendedQuestion | Answer,
+): post is ExtendedQuestion => {
+ return (post as ExtendedQuestion).slug !== undefined;
+};
+
+const QuestionPost: React.FC = ({
+ post,
+ sessionUser,
+ questionId,
+ reply = true,
+ isAnswer = true,
+}) => {
+ const { theme } = useTheme();
+ const [markDownValue, setMarkDownValue] = useState('');
+ const [enableReply, setEnableReply] = useState(false);
+
+ const handleMarkdownChange = (newValue?: string) => {
+ if (typeof newValue === 'string') {
+ setMarkDownValue(newValue);
+ }
+ };
+
+ const { execute, fieldErrors } = useAction(createAnswer, {
+ onSuccess: () => {
+ toast.success(`Answer created`);
+ if (!fieldErrors?.content) {
+ setEnableReply((prev) => !prev);
+ setMarkDownValue('');
+ }
+ },
+ onError: (error) => {
+ toast.error(error);
+ },
+ });
+
+ const handleSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+ execute({
+ content: markDownValue,
+ questionId,
+ parentId: isAnswer ? post?.id : undefined,
+ });
+ };
+
+ return (
+ <>
+
+
+
+
+
+ {post?.title}
+
+
+
+
+
+
+
+ {(sessionUser?.role === ROLES.ADMIN ||
+ post?.author?.id === sessionUser?.id) && (
+
+ )}
+
+ {/*
+ Report spam
+ */}
+
+
+
+
+ 200 ? '...' : '')}
+ style={{
+ whiteSpace: 'pre-wrap',
+ wordBreak: 'break-word',
+ overflowWrap: 'break-word',
+ backgroundColor: 'transparent',
+ }}
+ />
+
+
+ {isExtendedQuestion(post) &&
+ post.tags
+ .filter((v) => v !== '')
+ .map((v, index) => (
+
+ {/* */}# {v}
+
+ ))}
+
+
+
+
+ {post.author.name}
+
+
+
+ • Created{' '}
+ {dayjs(post.createdAt) && dayjs(post.createdAt)?.fromNow()}
+
+
+ • Updated{' '}
+ {dayjs(post.updatedAt) && dayjs(post.updatedAt)?.fromNow()}
+
+
+
+
+
+
+
+ {reply && (
+
+ )}
+
+ {post.totalanswers}
+
+
+
+
+
+ {enableReply && (
+
+ )}
+ >
+ );
+};
+export default QuestionPost;
diff --git a/src/components/search.tsx b/src/components/search.tsx
index 57ac89386..5e20c6977 100644
--- a/src/components/search.tsx
+++ b/src/components/search.tsx
@@ -18,8 +18,8 @@ const Search = () => {
router.push(getUpdatedUrl(path, paramsObj, { search }));
};
return (
-