can add images to forum posts and comments

This commit is contained in:
jackiettran
2025-11-11 23:32:03 -05:00
parent b045fbeb01
commit 105f257c5f
13 changed files with 383 additions and 78 deletions

View File

@@ -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">

View File

@@ -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' && (