Skip to content

Commit abf0e3f

Browse files
committed
adding community page
1 parent 31852e3 commit abf0e3f

File tree

7 files changed

+459
-22
lines changed

7 files changed

+459
-22
lines changed

src/components/Buttons.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* eslint-disable */
2+
/* eslint-disable */
23
import React from 'react';
34

45
interface Props {
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { useQuery, useMutation } from '@apollo/client';
3+
import { toast } from 'react-toastify';
4+
import { useTranslation } from 'react-i18next';
5+
import {
6+
GET_ALL_QUESTIONS,
7+
CREATE_QUESTION,
8+
CREATE_ANSWER,
9+
DELETE_QUESTION,
10+
DELETE_ANSWER,
11+
} from '../queries/questions.query';
12+
13+
function CommunityQuestions() {
14+
const { loading, error, data, refetch } = useQuery(GET_ALL_QUESTIONS);
15+
const [createQuestion, { loading: isSubmitting }] =
16+
useMutation(CREATE_QUESTION);
17+
const { t } = useTranslation();
18+
const [createAnswer] = useMutation(CREATE_ANSWER);
19+
const [deleteQuestion] = useMutation(DELETE_QUESTION);
20+
const [deleteAnswer] = useMutation(DELETE_ANSWER);
21+
const [loggedUser, setLoggedUser] = useState<string | null>(null);
22+
const [questionText, setQuestionText] = useState('');
23+
const [answerText, setAnswerText] = useState('');
24+
const [selectedQuestion, setSelectedQuestion] = useState<any>(null);
25+
const [questionTitleText, setQuestionTitleText] = useState<any>(null);
26+
27+
useEffect(() => {
28+
const authData = localStorage.getItem('auth');
29+
if (authData) {
30+
const parsedAuthData = JSON.parse(authData);
31+
setLoggedUser(parsedAuthData.userId);
32+
}
33+
}, []);
34+
35+
const questions = data?.getAllQuestions || [];
36+
if (loading) return <p>Loading...</p>;
37+
if (error) return <p>Error loading questions.</p>;
38+
39+
const handleCreateQuestion = async () => {
40+
if (questionText.trim()) {
41+
await createQuestion({
42+
variables: {
43+
title: questionTitleText,
44+
content: questionText,
45+
},
46+
update: (cache, { data: { createQuestion } }) => {
47+
const existingQuestions: any = cache.readQuery({
48+
query: GET_ALL_QUESTIONS,
49+
});
50+
cache.writeQuery({
51+
query: GET_ALL_QUESTIONS,
52+
data: {
53+
getAllQuestions: [
54+
...existingQuestions.getAllQuestions,
55+
createQuestion,
56+
],
57+
},
58+
});
59+
},
60+
onError: (error) => {
61+
toast.error('Unable to create question.');
62+
},
63+
onCompleted: () => {
64+
toast.success('Question created successfully');
65+
refetch();
66+
setQuestionText('');
67+
setQuestionTitleText('');
68+
},
69+
});
70+
} else {
71+
toast.error('Question text cannot be empty.');
72+
}
73+
};
74+
75+
const handleCreateAnswer = async (questionId: string) => {
76+
if (answerText.trim()) {
77+
await createAnswer({
78+
variables: {
79+
questionId,
80+
content: answerText,
81+
},
82+
update: (cache, { data: { createAnswer } }) => {
83+
const existingQuestions: any = cache.readQuery({
84+
query: GET_ALL_QUESTIONS,
85+
});
86+
const updatedQuestions = existingQuestions.getAllQuestions.map(
87+
(question: any) => {
88+
if (question?.id === questionId) {
89+
return {
90+
...question,
91+
answers: [...(question?.answers || []), createAnswer],
92+
};
93+
}
94+
return question;
95+
},
96+
);
97+
cache.writeQuery({
98+
query: GET_ALL_QUESTIONS,
99+
data: { getAllQuestions: updatedQuestions },
100+
});
101+
},
102+
onError: (error) => {
103+
toast.error('Unable to submit your answer.');
104+
},
105+
onCompleted: () => {
106+
toast.success('Answer submitted successfully');
107+
refetch({ query: GET_ALL_QUESTIONS });
108+
setSelectedQuestion('');
109+
110+
setAnswerText('');
111+
},
112+
});
113+
} else {
114+
toast.error('Answer text cannot be empty.');
115+
}
116+
};
117+
118+
const handleDeleteQuestion = async (questionId: string) => {
119+
await deleteQuestion({
120+
variables: { id: questionId },
121+
update: (cache) => {
122+
const existingQuestions: any = cache.readQuery({
123+
query: GET_ALL_QUESTIONS,
124+
});
125+
const updatedQuestions = existingQuestions.getAllQuestions.filter(
126+
(question: any) => question?.id !== questionId,
127+
);
128+
cache.writeQuery({
129+
query: GET_ALL_QUESTIONS,
130+
data: { getAllQuestions: updatedQuestions },
131+
});
132+
},
133+
onError: (error) => {
134+
toast.error('Unable to delete the question.');
135+
},
136+
onCompleted: () => {
137+
toast.success('Question deleted successfully');
138+
setSelectedQuestion('');
139+
140+
refetch();
141+
},
142+
});
143+
};
144+
145+
const handleDeleteAnswer = async (answerId: string) => {
146+
await deleteAnswer({
147+
variables: { id: answerId },
148+
update: (cache) => {
149+
const existingQuestions: any = cache.readQuery({
150+
query: GET_ALL_QUESTIONS,
151+
});
152+
const updatedQuestions = existingQuestions.getAllQuestions.map(
153+
(question: any) => ({
154+
...question,
155+
answers: question?.answers.filter(
156+
(answer: any) => answer.id !== answerId,
157+
),
158+
}),
159+
);
160+
cache.writeQuery({
161+
query: GET_ALL_QUESTIONS,
162+
data: { getAllQuestions: updatedQuestions },
163+
});
164+
},
165+
onError: (error) => {
166+
toast.error('Unable to delete the answer.');
167+
},
168+
onCompleted: () => {
169+
toast.success('Answer deleted successfully');
170+
setSelectedQuestion('');
171+
refetch({ query: GET_ALL_QUESTIONS });
172+
},
173+
});
174+
};
175+
176+
return (
177+
<div className="flex items-start p-4 bg-indigo-100 dark:bg-transparent rounded-md shadow-lg h-full ">
178+
<div className="w-1/3 pr-4 border-r border-divider-bg dark:border-border-dark ">
179+
<h2 className="text-lg font-semibold text-center text-header-text dark:text-dark-text-fill">
180+
Questions
181+
</h2>
182+
{loggedUser && (
183+
<div className="mt-4 -4 bg-tertiary dark:bg-transparent rounded-md flex flex-col gap-2">
184+
<input
185+
type="text"
186+
value={questionTitleText}
187+
onChange={(e) => setQuestionTitleText(e.target.value)}
188+
placeholder="Write question title..."
189+
className="p-2 border dark:bg-dark-tertiary border-gray-300 dark:border-gray-600 dark:text-black rounded w-full"
190+
/>
191+
<textarea
192+
value={questionText}
193+
onChange={(e) => setQuestionText(e.target.value)}
194+
rows={3}
195+
className="w-full dark:bg-dark-tertiary p-2 border rounded-md dark:text-black"
196+
placeholder="Ask a question..."
197+
/>
198+
<button
199+
onClick={handleCreateQuestion}
200+
disabled={isSubmitting}
201+
type="button"
202+
className="mt-2 w-fit p-2 px-6 bg-primary text-white rounded-md disabled:opacity-50"
203+
>
204+
{isSubmitting ? 'Submitting...' : 'Submit Question'}
205+
</button>
206+
</div>
207+
)}
208+
<ul className="mt-4 space-y-4">
209+
{questions?.map((question: any) => (
210+
<li
211+
key={question?.id}
212+
className={`cursor-pointer p-3 rounded-md shadow-sm ${
213+
selectedQuestion?.id === question?.id
214+
? 'bg-primary text-dark-text-fill'
215+
: 'bg-tertiary text-light-text dark:bg-dark-tertiary dark:text-dark-text-fill'
216+
}`}
217+
onClick={() => setSelectedQuestion(question)}
218+
onKeyPress={(e) => {
219+
if (e.key === 'Enter' || e.key === ' ') {
220+
setSelectedQuestion(question);
221+
}
222+
}}
223+
role="presentation"
224+
>
225+
<p className="text-md font-semibold">{question?.title}</p>
226+
<p className="text-sm text-secondary dark:text-dark-text-fill">
227+
Asked by: {question?.author?.email}
228+
</p>
229+
</li>
230+
))}
231+
</ul>
232+
</div>
233+
234+
<div className="w-2/3 pl-4">
235+
{selectedQuestion ? (
236+
<div>
237+
<h2 className="text-lg font-bold text-primary">
238+
{selectedQuestion?.title}
239+
</h2>
240+
<span>{selectedQuestion?.content}</span>
241+
<p className="text-sm text-secondary dark:text-dark-text-fill">
242+
Asked by {selectedQuestion?.author?.email}
243+
</p>
244+
{loggedUser && loggedUser === selectedQuestion?.author?.id && (
245+
<button
246+
type="button"
247+
onClick={() => handleDeleteQuestion(selectedQuestion?.id)}
248+
className=" text-red-500 text-sm"
249+
>
250+
{t('Delete')}
251+
</button>
252+
)}
253+
254+
<div className="mt-4 dark:bg-dark-tertiary">
255+
<h3 className="font-semibold text-secondary dark:text-dark-text-fill border-b border-black py-2">
256+
Answers thread
257+
</h3>
258+
259+
{/* Display answers */}
260+
{selectedQuestion?.answers?.length > 0 ? (
261+
selectedQuestion?.answers.map((answer: any) => (
262+
<div
263+
key={answer.id}
264+
className={`mt-2 p-4 rounded-md flex flex-col ${
265+
loggedUser && loggedUser === answer?.author?.id
266+
? 'items-end'
267+
: 'items-start'
268+
}`}
269+
>
270+
{/* Answer content */}
271+
<div
272+
className={` bg-gray-100 text-black dark:bg-dark-secondary p-2 rounded-md ${
273+
loggedUser && loggedUser === answer?.author?.id
274+
? 'text-right'
275+
: 'text-left'
276+
}`}
277+
>
278+
<p>{answer.content}</p>
279+
<p className="text-sm text-secondary dark:text-black ">
280+
Answered by {answer.author.email}
281+
</p>
282+
</div>
283+
284+
{/* Delete button for the user's own answers */}
285+
{loggedUser && loggedUser === answer?.author?.id && (
286+
<button
287+
type="button"
288+
onClick={() => handleDeleteAnswer(answer.id)}
289+
className="mt-2 text-red-500 text-sm"
290+
>
291+
{t('Delete')}
292+
</button>
293+
)}
294+
</div>
295+
))
296+
) : (
297+
<p className="text-sm text-gray-500 dark:text-dark-45">
298+
No answers yet. Be the first to answer!
299+
</p>
300+
)}
301+
302+
{/* Answer submission form for the logged-in user */}
303+
{loggedUser && (
304+
<div className="mt-4 flex items-start flex-col w-full">
305+
<textarea
306+
value={answerText}
307+
onChange={(e) => setAnswerText(e.target.value)}
308+
placeholder="Write an answer..."
309+
className="p-2 border outline-none bg-transparent dark:text-white dark:bg-dark-tertiary min-w-full min-h-24 border-gray-300 dark:border-gray-600 dark:text-black rounded"
310+
/>
311+
<button
312+
type="button"
313+
onClick={() => handleCreateAnswer(selectedQuestion?.id)}
314+
className="mt-2 bg-primary text-white rounded p-2 px-6"
315+
>
316+
Submit Answer
317+
</button>
318+
</div>
319+
)}
320+
</div>
321+
</div>
322+
) : (
323+
<p className="text-lg text-gray-500 dark:text-dark-45">
324+
Select a question to view details.
325+
</p>
326+
)}
327+
</div>
328+
</div>
329+
);
330+
}
331+
332+
export default CommunityQuestions;

src/components/Footer.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
FaPlayCircle,
1010
} from 'react-icons/fa';
1111
import i18next from 'i18next';
12+
import { Link } from 'react-router-dom';
1213
import LogoFooter from '../assets/logo.svg';
1314
import getLanguage from '../utils/getLanguage';
1415
import LogoIcon from './logoIcon';
@@ -76,7 +77,9 @@ function Footer({ styles }: any) {
7677
<div className="w-full sm:w-1/2 md:w-1/4 p-4 text-center lg:text-left">
7778
<h3 className="font-bold mb-2">{t('Resources')}</h3>
7879
<ul>
79-
<li className="mb-1">{t('Community')}</li>
80+
<li className="mb-1">
81+
<Link to="/community">{t('Community')} </Link>
82+
</li>
8083
<li className="mb-1">{t('Help Center')}</li>
8184
</ul>
8285
</div>

0 commit comments

Comments
 (0)