can add images to forum posts and comments
This commit is contained in:
@@ -3,6 +3,7 @@ import { useNavigate, Link } from "react-router-dom";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { forumAPI } from "../services/api";
|
||||
import TagInput from "../components/TagInput";
|
||||
import ForumImageUpload from "../components/ForumImageUpload";
|
||||
|
||||
const CreateForumPost: React.FC = () => {
|
||||
const { user } = useAuth();
|
||||
@@ -21,6 +22,9 @@ const CreateForumPost: React.FC = () => {
|
||||
tags: [] as string[],
|
||||
});
|
||||
|
||||
const [imageFiles, setImageFiles] = useState<File[]>([]);
|
||||
const [imagePreviews, setImagePreviews] = useState<string[]>([]);
|
||||
|
||||
const categories = [
|
||||
{
|
||||
value: "item_request",
|
||||
@@ -57,6 +61,31 @@ const CreateForumPost: React.FC = () => {
|
||||
setFormData((prev) => ({ ...prev, tags }));
|
||||
};
|
||||
|
||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(e.target.files || []);
|
||||
const remainingSlots = 5 - imageFiles.length;
|
||||
const filesToAdd = files.slice(0, remainingSlots);
|
||||
|
||||
setImageFiles((prev) => [...prev, ...filesToAdd]);
|
||||
|
||||
// Create preview URLs
|
||||
filesToAdd.forEach((file) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setImagePreviews((prev) => [...prev, reader.result as string]);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
// Reset input
|
||||
e.target.value = "";
|
||||
};
|
||||
|
||||
const handleRemoveImage = (index: number) => {
|
||||
setImageFiles((prev) => prev.filter((_, i) => i !== index));
|
||||
setImagePreviews((prev) => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
@@ -84,7 +113,24 @@ const CreateForumPost: React.FC = () => {
|
||||
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const response = await forumAPI.createPost(formData);
|
||||
|
||||
// Create FormData
|
||||
const submitData = new FormData();
|
||||
submitData.append('title', formData.title);
|
||||
submitData.append('content', formData.content);
|
||||
submitData.append('category', formData.category);
|
||||
|
||||
// Add tags as JSON string
|
||||
if (formData.tags.length > 0) {
|
||||
submitData.append('tags', JSON.stringify(formData.tags));
|
||||
}
|
||||
|
||||
// Add images
|
||||
imageFiles.forEach((file) => {
|
||||
submitData.append('images', file);
|
||||
});
|
||||
|
||||
const response = await forumAPI.createPost(submitData);
|
||||
navigate(`/forum/${response.data.id}`);
|
||||
} catch (err: any) {
|
||||
setError(err.response?.data?.error || "Failed to create post");
|
||||
@@ -223,7 +269,7 @@ const CreateForumPost: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="mb-4">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">
|
||||
Tags <span className="text-muted">(optional)</span>
|
||||
</label>
|
||||
@@ -237,6 +283,17 @@ const CreateForumPost: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Images */}
|
||||
<div className="mb-4">
|
||||
<ForumImageUpload
|
||||
imageFiles={imageFiles}
|
||||
imagePreviews={imagePreviews}
|
||||
onImageChange={handleImageChange}
|
||||
onRemoveImage={handleRemoveImage}
|
||||
maxImages={5}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Category-specific guidelines */}
|
||||
{formData.category === "item_request" && (
|
||||
<div className="alert alert-info mb-3">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { forumAPI } from '../services/api';
|
||||
import { forumAPI, getForumImageUrl } from '../services/api';
|
||||
import { ForumPost, ForumComment } from '../types';
|
||||
import CategoryBadge from '../components/CategoryBadge';
|
||||
import PostStatusBadge from '../components/PostStatusBadge';
|
||||
@@ -36,14 +36,21 @@ const ForumPostDetail: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddComment = async (content: string) => {
|
||||
const handleAddComment = async (content: string, images: File[]) => {
|
||||
if (!user) {
|
||||
alert('Please log in to comment');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await forumAPI.createComment(id!, { content });
|
||||
const formData = new FormData();
|
||||
formData.append('content', content);
|
||||
|
||||
images.forEach((file) => {
|
||||
formData.append('images', file);
|
||||
});
|
||||
|
||||
await forumAPI.createComment(id!, formData);
|
||||
await fetchPost(); // Refresh to get new comment
|
||||
} catch (err: any) {
|
||||
throw new Error(err.response?.data?.error || 'Failed to post comment');
|
||||
@@ -57,7 +64,11 @@ const ForumPostDetail: React.FC = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
await forumAPI.createComment(id!, { content, parentCommentId });
|
||||
const formData = new FormData();
|
||||
formData.append('content', content);
|
||||
formData.append('parentCommentId', parentCommentId);
|
||||
|
||||
await forumAPI.createComment(id!, formData);
|
||||
await fetchPost(); // Refresh to get new reply
|
||||
} catch (err: any) {
|
||||
throw new Error(err.response?.data?.error || 'Failed to post reply');
|
||||
@@ -180,21 +191,19 @@ const ForumPostDetail: React.FC = () => {
|
||||
<h1 className="h3 mb-2">{post.title}</h1>
|
||||
|
||||
<div className="d-flex gap-2 mb-2 flex-wrap">
|
||||
<CategoryBadge category={post.category} />
|
||||
<PostStatusBadge status={post.status} />
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<>
|
||||
{post.tags.map((tag) => (
|
||||
<Link
|
||||
key={tag.id}
|
||||
to={`/forum?tag=${tag.tagName}`}
|
||||
className="badge bg-light text-dark text-decoration-none"
|
||||
>
|
||||
#{tag.tagName}
|
||||
</Link>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<div className="d-flex gap-2">
|
||||
<CategoryBadge category={post.category} />
|
||||
<PostStatusBadge status={post.status} />
|
||||
</div>
|
||||
{(post.tags || []).map((tag) => (
|
||||
<Link
|
||||
key={tag.id}
|
||||
to={`/forum?tag=${tag.tagName}`}
|
||||
className="badge bg-light text-dark text-decoration-none"
|
||||
>
|
||||
#{tag.tagName}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-muted small mb-3">
|
||||
@@ -210,6 +219,22 @@ const ForumPostDetail: React.FC = () => {
|
||||
{post.content}
|
||||
</div>
|
||||
|
||||
{post.images && post.images.length > 0 && (
|
||||
<div className="row g-2 mb-3">
|
||||
{post.images.map((image, index) => (
|
||||
<div key={index} className="col-6 col-md-4">
|
||||
<img
|
||||
src={getForumImageUrl(image)}
|
||||
alt={`Post image`}
|
||||
className="img-fluid rounded"
|
||||
style={{ width: '100%', height: '200px', objectFit: 'cover', cursor: 'pointer' }}
|
||||
onClick={() => window.open(getForumImageUrl(image), '_blank')}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAuthor && (
|
||||
<div className="d-flex gap-2 flex-wrap mb-3">
|
||||
{post.status !== 'closed' && (
|
||||
|
||||
Reference in New Issue
Block a user