-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #260 from ShivanshPlays/faq-FE
- Loading branch information
Showing
10 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
File renamed without changes.
File renamed without changes.
80 changes: 80 additions & 0 deletions
80
alimento-nextjs/app/(PublicRoutes)/(footerPages)/forum/components/NewQuestionForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
93 changes: 93 additions & 0 deletions
93
alimento-nextjs/app/(PublicRoutes)/(footerPages)/forum/components/unAnsweredQuestions.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
77 changes: 77 additions & 0 deletions
77
alimento-nextjs/app/(PublicRoutes)/(footerPages)/forum/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
|
||
import { getAnsweredQuestions, getUnansweredQuestions, createQuestion } from "@/actions/forum/Question"; | ||
import { Button } from "@/components/ui/button"; | ||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | ||
import { Label } from "@/components/ui/label"; | ||
import { Textarea } from "@/components/ui/textarea"; | ||
import { SyntheticEvent, useState } from "react"; // Import useState for client-side form handling | ||
import NewQuestionForm from "./components/NewQuestionForm"; | ||
import UnansweredQuestionCard from "./components/unAnsweredQuestions"; | ||
|
||
const Forum = async () => { | ||
// Fetch unanswered and answered questions using server actions | ||
const unansweredResp = await getUnansweredQuestions(); | ||
const answeredResp = await getAnsweredQuestions(); | ||
|
||
return ( | ||
<div className="container mx-auto p-6 space-y-8"> | ||
{/* Page Title */} | ||
<h1 className="text-3xl font-bold text-center text-gray-800">Forum</h1> | ||
|
||
{/* Ask a New Question Section */} | ||
<section className="space-y-4"> | ||
<h2 className="text-2xl font-semibold text-gray-700">Ask a Question</h2> | ||
<NewQuestionForm /> | ||
</section> | ||
|
||
{/* Unanswered Questions Section */} | ||
<section className="space-y-6"> | ||
<h2 className="text-2xl font-semibold text-gray-700">Unanswered Questions</h2> | ||
|
||
{unansweredResp.success ? ( | ||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> | ||
{unansweredResp.data?.map((question) => ( | ||
<UnansweredQuestionCard | ||
key={question.id} | ||
question={question} | ||
/> | ||
))} | ||
</div> | ||
) : ( | ||
<p className="text-gray-600">{unansweredResp.error || "No unanswered questions available."}</p> | ||
)} | ||
</section> | ||
|
||
{/* Answered Questions Section */} | ||
<section className="space-y-6"> | ||
<h2 className="text-2xl font-semibold text-gray-700">Answered Questions</h2> | ||
|
||
{answeredResp.success ? ( | ||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> | ||
{answeredResp.data?.map((question) => ( | ||
<Card key={question.id} className="shadow-lg"> | ||
<CardHeader> | ||
<CardTitle>{question.content}</CardTitle> | ||
</CardHeader> | ||
<CardContent> | ||
<div className="space-y-2"> | ||
<p className="text-sm font-medium text-gray-700">Answers:</p> | ||
{question.answers?.map((answer) => ( | ||
<div key={answer.id} className="p-3 bg-gray-100 rounded-md"> | ||
<p className="text-gray-800">{answer.content}</p> | ||
</div> | ||
))} | ||
</div> | ||
</CardContent> | ||
</Card> | ||
))} | ||
</div> | ||
) : ( | ||
<p className="text-gray-600">{answeredResp.error || "No answered questions available."}</p> | ||
)} | ||
</section> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Forum |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import React from 'react'; | ||
import { cn } from '@/lib/utils'; | ||
import { VariantProps, cva } from 'class-variance-authority'; | ||
import { Loader2 } from 'lucide-react'; | ||
|
||
const spinnerVariants = cva('flex-col items-center justify-center', { | ||
variants: { | ||
show: { | ||
true: 'flex', | ||
false: 'hidden', | ||
}, | ||
}, | ||
defaultVariants: { | ||
show: true, | ||
}, | ||
}); | ||
|
||
const loaderVariants = cva('animate-spin text-primary', { | ||
variants: { | ||
size: { | ||
small: 'size-6', | ||
medium: 'size-8', | ||
large: 'size-12', | ||
}, | ||
}, | ||
defaultVariants: { | ||
size: 'medium', | ||
}, | ||
}); | ||
|
||
interface SpinnerContentProps | ||
extends VariantProps<typeof spinnerVariants>, | ||
VariantProps<typeof loaderVariants> { | ||
className?: string; | ||
children?: React.ReactNode; | ||
} | ||
|
||
export function Spinner({ size, show, children, className }: SpinnerContentProps) { | ||
return ( | ||
<span className={spinnerVariants({ show })}> | ||
<Loader2 className={cn(loaderVariants({ size }),'text-customTeal dark:text-Green ', className)} /> | ||
{children} | ||
</span> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import * as React from "react" | ||
|
||
import { cn } from "@/lib/utils" | ||
|
||
const Textarea = React.forwardRef< | ||
HTMLTextAreaElement, | ||
React.ComponentProps<"textarea"> | ||
>(({ className, ...props }, ref) => { | ||
return ( | ||
<textarea | ||
className={cn( | ||
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", | ||
className | ||
)} | ||
ref={ref} | ||
{...props} | ||
/> | ||
) | ||
}) | ||
Textarea.displayName = "Textarea" | ||
|
||
export { Textarea } |