images for forum and forum comments

This commit is contained in:
jackiettran
2025-12-13 20:32:25 -05:00
parent 55e08e14b8
commit 5e01bb8cff
7 changed files with 294 additions and 92 deletions

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import ForumImageUpload from './ForumImageUpload';
import { IMAGE_LIMITS } from '../config/imageLimits';
interface CommentFormProps {
onSubmit: (content: string, images: File[]) => Promise<void>;
@@ -24,7 +25,7 @@ const CommentForm: React.FC<CommentFormProps> = ({
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
const remainingSlots = 3 - imageFiles.length;
const remainingSlots = IMAGE_LIMITS.forum - imageFiles.length;
const filesToAdd = files.slice(0, remainingSlots);
setImageFiles((prev) => [...prev, ...filesToAdd]);
@@ -81,15 +82,13 @@ const CommentForm: React.FC<CommentFormProps> = ({
/>
{error && <div className="invalid-feedback">{error}</div>}
</div>
{!isReply && (
<ForumImageUpload
imageFiles={imageFiles}
imagePreviews={imagePreviews}
onImageChange={handleImageChange}
onRemoveImage={handleRemoveImage}
compact={true}
/>
)}
<ForumImageUpload
imageFiles={imageFiles}
imagePreviews={imagePreviews}
onImageChange={handleImageChange}
onRemoveImage={handleRemoveImage}
compact={true}
/>
<div className="d-flex gap-2">
<button
type="submit"

View File

@@ -1,12 +1,14 @@
import React, { useState } from "react";
import { ForumComment } from "../types";
import CommentForm from "./CommentForm";
import ForumImageUpload from "./ForumImageUpload";
import { getPublicImageUrl } from "../services/uploadService";
import { IMAGE_LIMITS } from "../config/imageLimits";
interface CommentThreadProps {
comment: ForumComment;
onReply: (commentId: string, content: string) => Promise<void>;
onEdit?: (commentId: string, content: string) => Promise<void>;
onReply: (commentId: string, content: string, images?: File[]) => Promise<void>;
onEdit?: (commentId: string, content: string, existingImageKeys: string[], newImageFiles: File[]) => Promise<void>;
onDelete?: (commentId: string) => Promise<void>;
onMarkAsAnswer?: (commentId: string) => Promise<void>;
currentUserId?: string;
@@ -37,6 +39,11 @@ const CommentThread: React.FC<CommentThreadProps> = ({
const [editContent, setEditContent] = useState(comment.content);
const [isCollapsed, setIsCollapsed] = useState(false);
// Image editing state
const [existingImageKeys, setExistingImageKeys] = useState<string[]>([]);
const [editImageFiles, setEditImageFiles] = useState<File[]>([]);
const [editImagePreviews, setEditImagePreviews] = useState<string[]>([]);
const formatDate = (dateString: string) => {
const date = new Date(dateString);
const now = new Date();
@@ -59,17 +66,67 @@ const CommentThread: React.FC<CommentThreadProps> = ({
};
const handleReply = async (content: string, images: File[]) => {
// Replies don't support images, so we ignore the images parameter
await onReply(comment.id, content);
await onReply(comment.id, content, images);
setShowReplyForm(false);
};
const handleEdit = async () => {
if (onEdit && editContent.trim() !== comment.content) {
await onEdit(comment.id, editContent);
setIsEditing(false);
const startEditing = () => {
setIsEditing(true);
setEditContent(comment.content);
const existingKeys = comment.imageFilenames || [];
setExistingImageKeys(existingKeys);
setEditImagePreviews(existingKeys.map((key) => getPublicImageUrl(key)));
setEditImageFiles([]);
};
const cancelEditing = () => {
setIsEditing(false);
setEditContent(comment.content);
setExistingImageKeys([]);
setEditImageFiles([]);
setEditImagePreviews([]);
};
const handleEditImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
const totalImages = existingImageKeys.length + editImageFiles.length;
const remainingSlots = IMAGE_LIMITS.forum - totalImages;
const filesToAdd = files.slice(0, remainingSlots);
setEditImageFiles((prev) => [...prev, ...filesToAdd]);
filesToAdd.forEach((file) => {
const reader = new FileReader();
reader.onloadend = () => {
setEditImagePreviews((prev) => [...prev, reader.result as string]);
};
reader.readAsDataURL(file);
});
e.target.value = "";
};
const handleRemoveEditImage = (index: number) => {
if (index < existingImageKeys.length) {
// Removing an existing S3 image
setExistingImageKeys((prev) => prev.filter((_, i) => i !== index));
} else {
// Removing a new upload
const newFileIndex = index - existingImageKeys.length;
setEditImageFiles((prev) => prev.filter((_, i) => i !== newFileIndex));
}
setEditImagePreviews((prev) => prev.filter((_, i) => i !== index));
};
const handleEdit = async () => {
if (onEdit && editContent.trim()) {
await onEdit(comment.id, editContent, existingImageKeys, editImageFiles);
setIsEditing(false);
setExistingImageKeys([]);
setEditImageFiles([]);
setEditImagePreviews([]);
} else {
cancelEditing();
}
};
@@ -188,7 +245,14 @@ const CommentThread: React.FC<CommentThreadProps> = ({
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
/>
<div className="d-flex gap-2">
<ForumImageUpload
imageFiles={editImageFiles}
imagePreviews={editImagePreviews}
onImageChange={handleEditImageChange}
onRemoveImage={handleRemoveEditImage}
compact={true}
/>
<div className="d-flex gap-2 mt-2">
<button
className="btn btn-sm btn-primary"
onClick={handleEdit}
@@ -198,10 +262,7 @@ const CommentThread: React.FC<CommentThreadProps> = ({
</button>
<button
className="btn btn-sm btn-secondary"
onClick={() => {
setIsEditing(false);
setEditContent(comment.content);
}}
onClick={cancelEditing}
>
Cancel
</button>
@@ -222,8 +283,8 @@ const CommentThread: React.FC<CommentThreadProps> = ({
className="img-fluid rounded"
style={{
width: "100%",
height: "100px",
objectFit: "cover",
maxHeight: "300px",
objectFit: "contain",
cursor: "pointer",
}}
onClick={() =>
@@ -267,7 +328,7 @@ const CommentThread: React.FC<CommentThreadProps> = ({
{isAuthor && onEdit && !isEditing && (
<button
className="btn btn-sm btn-link text-decoration-none p-0"
onClick={() => setIsEditing(true)}
onClick={startEditing}
>
<i className="bi bi-pencil me-1"></i>
Edit