Skip to content

Commit

Permalink
added fully responsive faq frontend page
Browse files Browse the repository at this point in the history
  • Loading branch information
ShivanshPlays committed Nov 9, 2024
1 parent 2780452 commit ccdbf6c
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 0 deletions.
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;
77 changes: 77 additions & 0 deletions alimento-nextjs/app/(PublicRoutes)/(footerPages)/forum/page.tsx
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
1 change: 1 addition & 0 deletions alimento-nextjs/components/common/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const Helpdesk = [
{ name: 'FAQ', id: 2, href: '/FAQ' },
{ name: 'Contact Us', id: 3, href: '/contact-us' },
{ name: 'Support', id: 4, href: '/Support' },
{ name: 'Forum', id: 5, href: '/forum' },
];

const Footer = () => {
Expand Down
45 changes: 45 additions & 0 deletions alimento-nextjs/components/ui/spinner.tsx
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>
);
}
22 changes: 22 additions & 0 deletions alimento-nextjs/components/ui/textarea.tsx
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 }

0 comments on commit ccdbf6c

Please sign in to comment.