Skip to content

Commit 9748a7a

Browse files
committed
blog
1 parent 8e428d3 commit 9748a7a

File tree

3 files changed

+423
-1
lines changed

3 files changed

+423
-1
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import * as React from "react";
2+
import { toast } from 'react-toastify';
3+
import {
4+
Box,
5+
Typography,
6+
TextField,
7+
Button,
8+
Avatar,
9+
Paper,
10+
Divider
11+
} from '@mui/material';
12+
import UserContext, { type UserContextType } from "../contexts/UserContext";
13+
14+
interface Comment {
15+
id: number;
16+
content: string;
17+
author: {
18+
id: number;
19+
username: string;
20+
profile_picture?: string;
21+
};
22+
created_at: string;
23+
pinned: boolean;
24+
hidden: boolean;
25+
}
26+
27+
interface BlogPostCommentsProps {
28+
postId: number;
29+
}
30+
31+
export default function BlogPostComments({ postId }: BlogPostCommentsProps) {
32+
const { isAuthenticated } = React.useContext<UserContextType>(UserContext);
33+
const [comments, setComments] = React.useState<Comment[]>([]);
34+
const [newComment, setNewComment] = React.useState("");
35+
const [isLoading, setIsLoading] = React.useState(false);
36+
37+
const fetchComments = React.useCallback(async () => {
38+
try {
39+
const response = await fetch(`/api/blog/posts/id/${postId}/comments/`, {
40+
credentials: 'include'
41+
});
42+
if (!response.ok) throw new Error('Failed to fetch comments');
43+
const data = await response.json();
44+
setComments(data);
45+
} catch (error) {
46+
console.error('Error fetching comments:', error);
47+
toast.error('Failed to load comments');
48+
}
49+
}, [postId]);
50+
51+
React.useEffect(() => {
52+
fetchComments();
53+
}, [fetchComments]);
54+
55+
const handleSubmitComment = async (e: React.FormEvent) => {
56+
e.preventDefault();
57+
if (!isAuthenticated) {
58+
toast.error('Please log in to comment');
59+
return;
60+
}
61+
62+
if (!newComment.trim()) {
63+
toast.error('Comment cannot be empty');
64+
return;
65+
}
66+
67+
setIsLoading(true);
68+
try {
69+
const response = await fetch(`/api/blog/posts/id/${postId}/comments/`, {
70+
method: 'POST',
71+
headers: {
72+
'Content-Type': 'application/json',
73+
},
74+
credentials: 'include',
75+
body: JSON.stringify({ content: newComment }),
76+
});
77+
78+
if (!response.ok) throw new Error('Failed to post comment');
79+
80+
await fetchComments();
81+
setNewComment("");
82+
toast.success('Comment posted successfully');
83+
} catch (error) {
84+
console.error('Error posting comment:', error);
85+
toast.error('Failed to post comment');
86+
} finally {
87+
setIsLoading(false);
88+
}
89+
};
90+
91+
return (
92+
<Paper elevation={0} sx={{ p: 3, my: 3 }}>
93+
<Typography variant="h5" component="h2" gutterBottom>
94+
Comments
95+
</Typography>
96+
97+
{isAuthenticated && (
98+
<Box component="form" onSubmit={handleSubmitComment} sx={{ mb: 4 }}>
99+
<TextField
100+
fullWidth
101+
multiline
102+
rows={3}
103+
value={newComment}
104+
onChange={(e) => setNewComment(e.target.value)}
105+
placeholder="Write a comment..."
106+
variant="outlined"
107+
sx={{ mb: 2 }}
108+
/>
109+
<Button
110+
type="submit"
111+
variant="contained"
112+
disabled={isLoading}
113+
sx={{ float: 'right' }}
114+
>
115+
{isLoading ? 'Posting...' : 'Post Comment'}
116+
</Button>
117+
</Box>
118+
)}
119+
120+
<Box sx={{ mt: 4 }}>
121+
{comments.length === 0 ? (
122+
<Typography color="text.secondary" align="center">
123+
No comments yet. Be the first to comment!
124+
</Typography>
125+
) : (
126+
comments.map((comment) => (
127+
!comment.hidden && (
128+
<Box key={comment.id} sx={{ mb: 3 }}>
129+
<Box sx={{ display: 'flex', gap: 2, mb: 1 }}>
130+
<Avatar
131+
src={comment.author.profile_picture}
132+
alt={comment.author.username}
133+
/>
134+
<Box>
135+
<Typography variant="subtitle2">
136+
{comment.author.username}
137+
{comment.pinned && (
138+
<Typography
139+
component="span"
140+
variant="caption"
141+
sx={{
142+
ml: 1,
143+
color: 'primary.main',
144+
fontWeight: 'medium'
145+
}}
146+
>
147+
(Pinned)
148+
</Typography>
149+
)}
150+
</Typography>
151+
<Typography variant="caption" color="text.secondary">
152+
{new Date(comment.created_at).toLocaleDateString(undefined, {
153+
year: 'numeric',
154+
month: 'long',
155+
day: 'numeric'
156+
})}
157+
</Typography>
158+
</Box>
159+
</Box>
160+
<Typography variant="body2" sx={{ ml: 7 }}>
161+
{comment.content}
162+
</Typography>
163+
{comments.length > 1 && <Divider sx={{ mt: 2 }} />}
164+
</Box>
165+
)
166+
))
167+
)}
168+
</Box>
169+
</Paper>
170+
);
171+
}

0 commit comments

Comments
 (0)