Skip to content

Commit 4e1afad

Browse files
authored
adding community page (#624)
1 parent 0f56ecd commit 4e1afad

File tree

7 files changed

+467
-22
lines changed

7 files changed

+467
-22
lines changed

src/components/Buttons.tsx

+1
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 {

src/components/CommunityQuestions.tsx

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

src/components/Footer.tsx

+4-1
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)