Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forum complete: responsive, theme adaptable, fully functional #260

Merged
merged 2 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions alimento-nextjs/actions/forum/Answer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use server"

import prismadb from "@/lib/prismadb";
import { Answer } from "@prisma/client";
import { UUID } from "crypto";

type CreateAnswerResponse = {
success: boolean;
data?: Answer
error?: string;
};

export async function createAnswer(
questionId: string,
content: string
): Promise<CreateAnswerResponse> {
try {

const newAnswer = await prismadb.answer.create({
data: {
content,
questionId,
},
});

await prismadb.question.update({
where: { id: questionId },
data: { answered: true },
});

return {
success: true,
data: newAnswer
};
} catch (error) {
console.error("Error creating answer:", error);
return {
success: false,
error: "Failed to create answer",
};
}
}




type DeleteAnswerResponse = {
success: boolean;
data?: Answer;
error?: string;
};

export async function deleteSpecificAnswer(answerId: string): Promise<DeleteAnswerResponse> {
try {
const deletedAnswer = await prismadb.answer.delete({
where: {
id: answerId,
},
});

if (!deletedAnswer) {
return {
success: false,
error: "Error in deleting the answer",
};
}

return {
success: true,
data: deletedAnswer,
};
} catch (error) {
console.error("Error deleting specific answer:", error);
return {
success: false,
error: "Failed to delete specific answer",
};
}
}
141 changes: 141 additions & 0 deletions alimento-nextjs/actions/forum/Question.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'use server';

import prismadb from '@/lib/prismadb';
import { Answer, Question } from '@prisma/client';
import { UUID } from 'crypto';

interface QuestionWithAnswer extends Question{
answers?:Answer[]
}

type QuestionResponse = {
success: boolean;
data?: Question;
error?: string;
};

type getQuestionsResponse = {
success: boolean;
data?: QuestionWithAnswer[];
error?: string;
};


export async function createQuestion(
content: string
): Promise<QuestionResponse> {
try {
const newQuestion = await prismadb.question.create({
data: {
content,
},
});

return {
success: true,
data: newQuestion,
};
} catch (error) {
console.error('Error creating question:', error);
return {
success: false,
error: 'Failed to create question',
};
}
}

export async function getUnansweredQuestions(
): Promise<getQuestionsResponse> {
try {
const unAnsweredQuestions = await prismadb.question.findMany({
where: {
answered: false,
},
});

if (!unAnsweredQuestions.length) {
return {
success: false,
error: 'No unanswered questions!',
};
}

return {
success: true,
data: unAnsweredQuestions,
};
} catch (error) {
console.error('Error fetching unanswered questions', error);
return {
success: false,
error: 'Failed to fetch unanswered questions',
};
}
}


export async function getAnsweredQuestions(
): Promise<getQuestionsResponse> {
try {
const AnsweredQuestions = await prismadb.question.findMany({
where: {
answered: true,
},
include:{
answers:true
}
});

if (!AnsweredQuestions.length) {
return {
success: false,
error: 'No unanswered questions!',
};
}

return {
success: true,
data: AnsweredQuestions,
};
} catch (error) {
console.error('Error fetching answered questions', error);
return {
success: false,
error: 'Failed to fetch answered questions',
};
}
}

// This will be only accessible by the admin

// deletes a question and all its related answers if present

export async function deleteQuestion(
questionId: string,
): Promise<QuestionResponse> {
try {
const deletedQuestion = await prismadb.question.delete({
where: {
id:questionId
},
});

if (!deletedQuestion) {
return {
success: false,
error: 'error in deleting the question',
};
}

return {
success: true,
data: deletedQuestion,
};
} catch (error) {
console.error('Error deleting specific question', error);
return {
success: false,
error: 'Failed to delete specific question',
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use client';

import { createQuestion } from '@/actions/forum/Question';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Spinner } from '@/components/ui/spinner';
import { Textarea } from '@/components/ui/textarea';
import { useSession } from 'next-auth/react';
import { SyntheticEvent, useEffect, useState } from 'react';

const NewQuestionForm = () => {
const [questionContent, setQuestionContent] = useState('');
const [submissionError, setSubmissionError] = useState('');

const [isMounted, setIsMounted] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);

const session = useSession();

useEffect(() => {
setIsMounted(true);
if (!(session.status === 'loading')) {
if (session.status === 'authenticated') {
setIsAuthenticated(true);
// console.log("hereeeeeeeeee"+isAuthenticated+session.status)
}
}
}, [session.status]);

if (!isMounted) {
return <Spinner />;
}

const handleSubmit = async (e: SyntheticEvent) => {
// e.preventDefault();
try {
const response = await createQuestion(questionContent);
if (!response.success && response.error) {
setSubmissionError(response.error);
} else {
setQuestionContent('');
setSubmissionError('');
}
} catch (error) {
setSubmissionError(
'An error occurred while submitting your question. Please try again.'
);
}
};

return (
<>
{isAuthenticated ? (
<form onSubmit={handleSubmit} className="space-y-4">
<Label htmlFor="new-question" className="text-gray-700 font-medium">
Your Question
</Label>
<Textarea
id="new-question"
value={questionContent}
onChange={e => setQuestionContent(e.target.value)}
placeholder="Type your question here..."
className="resize-none w-full rounded-md border-gray-300 focus:border-indigo-500"
required
/>
{submissionError && <p className="text-red-600">{submissionError}</p>}
<Button type="submit" className="w-full bg-green-500 text-white">
Submit Question
</Button>
</form>
) : (
<p className="text-red-500 text-sm">
Please authenticate yourself to post a question.
</p>
)}
</>
);
};

export default NewQuestionForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use client";

import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Spinner } from "@/components/ui/spinner";
import { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import toast from "react-hot-toast"; // Assuming you're using React Hot Toast for notifications
import { createAnswer } from "@/actions/forum/Answer"; // Import the server action

interface UnansweredQuestionCardProps {
question: {
id: string;
content: string;
};
}

const UnansweredQuestionCard: React.FC<UnansweredQuestionCardProps> = ({ question }) => {
const [answer, setAnswer] = useState("");
const [isMounted, setIsMounted] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);

const { status: sessionStatus } = useSession();

useEffect(() => {
setIsMounted(true);
if (sessionStatus === "authenticated") {
setIsAuthenticated(true);
}
}, [sessionStatus]);

if (!isMounted) {
return <Spinner />;
}

const handleSubmit = async (e: React.FormEvent) => {
// e.preventDefault();
setIsSubmitting(true);

try {
const response = await createAnswer(question.id, answer);

if (response.success) {
toast.success("Answer submitted successfully!");
setAnswer(""); // Clear the textarea on success
} else {
toast.error(response.error || "An error occurred while submitting your answer.");
}
} catch (error) {
toast.error("An error occurred while submitting your answer. Please try again.");
} finally {
setIsSubmitting(false);
}
};

return (
<Card key={question.id} className="shadow-lg">
<CardHeader>
<CardTitle>{question.content}</CardTitle>
</CardHeader>
<CardContent>
{isAuthenticated ? (
<form onSubmit={handleSubmit}>
<div className="space-y-4">
<Label htmlFor={`answer-${question.id}`} className="block text-sm font-medium text-gray-600">
Your Answer
</Label>
<Textarea
id={`answer-${question.id}`}
value={answer}
onChange={(e) => setAnswer(e.target.value)}
placeholder="Type your answer here..."
className="resize-none w-full rounded-md border-gray-300 focus:border-indigo-500"
required
disabled={isSubmitting}
/>
</div>
<Button type="submit" className="mt-4 w-full bg-blue-500 text-white" disabled={isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit Answer"}
</Button>
</form>
) : (
<p className="text-red-500 text-sm">Please authenticate yourself to answer the question.</p>
)}
</CardContent>
</Card>
);
};

export default UnansweredQuestionCard;
Loading