Files
rentall-app/frontend/src/pages/MyPosts.tsx
2025-11-11 18:23:11 -05:00

275 lines
9.6 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { forumAPI } from '../services/api';
import { ForumPost } from '../types';
import CategoryBadge from '../components/CategoryBadge';
import PostStatusBadge from '../components/PostStatusBadge';
const MyPosts: React.FC = () => {
const { user } = useAuth();
const [posts, setPosts] = useState<ForumPost[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [actionLoading, setActionLoading] = useState<string | null>(null);
useEffect(() => {
if (user) {
fetchMyPosts();
}
}, [user]);
const fetchMyPosts = async () => {
try {
setLoading(true);
const response = await forumAPI.getMyPosts();
setPosts(response.data);
} catch (err: any) {
setError(err.response?.data?.error || 'Failed to fetch your posts');
} finally {
setLoading(false);
}
};
const handleStatusChange = async (postId: string, newStatus: string) => {
try {
setActionLoading(postId);
await forumAPI.updatePostStatus(postId, newStatus);
await fetchMyPosts(); // Refresh list
} catch (err: any) {
alert(err.response?.data?.error || 'Failed to update status');
} finally {
setActionLoading(null);
}
};
const handleDelete = async (postId: string, postTitle: string) => {
if (!window.confirm(`Are you sure you want to delete "${postTitle}"? This action cannot be undone.`)) {
return;
}
try {
setActionLoading(postId);
await forumAPI.deletePost(postId);
await fetchMyPosts(); // Refresh list
} catch (err: any) {
alert(err.response?.data?.error || 'Failed to delete post');
setActionLoading(null);
}
};
const formatDate = (dateString: string) => {
const date = new Date(dateString);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffHours / 24);
if (diffHours < 1) {
return 'Just now';
} else if (diffHours < 24) {
return `${diffHours}h ago`;
} else if (diffDays < 7) {
return `${diffDays}d ago`;
} else {
return date.toLocaleDateString();
}
};
if (!user) {
return (
<div className="container mt-4">
<div className="alert alert-warning" role="alert">
<i className="bi bi-exclamation-triangle me-2"></i>
You must be logged in to view your posts.
</div>
<Link to="/forum" className="btn btn-secondary">
<i className="bi bi-arrow-left me-2"></i>
Back to Forum
</Link>
</div>
);
}
if (loading) {
return (
<div className="container mt-4">
<div className="text-center py-5">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);
}
return (
<div className="container mt-4">
<div className="d-flex justify-content-between align-items-center mb-4">
<div>
<h1>My Posts</h1>
<p className="text-muted">Manage your forum posts and discussions</p>
</div>
<Link to="/forum/create" className="btn btn-primary">
<i className="bi bi-plus-circle me-2"></i>
Create Post
</Link>
</div>
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
{posts.length === 0 ? (
<div className="text-center py-5">
<i className="bi bi-inbox display-1 text-muted"></i>
<h3 className="mt-3">No posts yet</h3>
<p className="text-muted mb-4">
Start a conversation by creating your first post!
</p>
<Link to="/forum/create" className="btn btn-primary">
<i className="bi bi-plus-circle me-2"></i>
Create Your First Post
</Link>
</div>
) : (
<>
<div className="mb-3">
<p className="text-muted">
You have {posts.length} post{posts.length !== 1 ? 's' : ''}
</p>
</div>
<div className="list-group">
{posts.map((post) => (
<div key={post.id} className="list-group-item">
<div className="row align-items-center">
<div className="col-lg-7">
<div className="d-flex align-items-start mb-2">
{post.isPinned && (
<span className="badge bg-danger me-2">
<i className="bi bi-pin-angle-fill"></i>
</span>
)}
<div className="flex-grow-1">
<h5 className="mb-1">
<Link to={`/forum/${post.id}`} className="text-decoration-none">
{post.title}
</Link>
</h5>
<div className="d-flex gap-2 mb-2">
<CategoryBadge category={post.category} />
<PostStatusBadge status={post.status} />
</div>
{post.tags && post.tags.length > 0 && (
<div className="mb-2">
{post.tags.slice(0, 3).map((tag) => (
<span key={tag.id} className="badge bg-light text-dark me-1">
#{tag.tagName}
</span>
))}
{post.tags.length > 3 && (
<span className="badge bg-light text-dark">
+{post.tags.length - 3}
</span>
)}
</div>
)}
<div className="d-flex gap-3 text-muted small">
<span>
<i className="bi bi-chat me-1"></i>
{post.commentCount || 0} comments
</span>
<span>
<i className="bi bi-eye me-1"></i>
{post.viewCount || 0} views
</span>
<span>
<i className="bi bi-clock me-1"></i>
{formatDate(post.updatedAt)}
</span>
</div>
</div>
</div>
</div>
<div className="col-lg-5">
<div className="d-flex gap-2 flex-wrap justify-content-lg-end">
<Link
to={`/forum/${post.id}`}
className="btn btn-sm btn-outline-primary"
>
<i className="bi bi-eye me-1"></i>
View
</Link>
{post.status === 'open' && (
<button
className="btn btn-sm btn-outline-success"
onClick={() => handleStatusChange(post.id, 'answered')}
disabled={actionLoading === post.id}
>
<i className="bi bi-check-circle me-1"></i>
Mark Answered
</button>
)}
{post.status !== 'closed' && (
<button
className="btn btn-sm btn-outline-secondary"
onClick={() => handleStatusChange(post.id, 'closed')}
disabled={actionLoading === post.id}
>
<i className="bi bi-x-circle me-1"></i>
Close
</button>
)}
{post.status === 'closed' && (
<button
className="btn btn-sm btn-outline-success"
onClick={() => handleStatusChange(post.id, 'open')}
disabled={actionLoading === post.id}
>
<i className="bi bi-arrow-counterclockwise me-1"></i>
Reopen
</button>
)}
<button
className="btn btn-sm btn-outline-danger"
onClick={() => handleDelete(post.id, post.title)}
disabled={actionLoading === post.id}
>
{actionLoading === post.id ? (
<span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
) : (
<>
<i className="bi bi-trash me-1"></i>
Delete
</>
)}
</button>
</div>
</div>
</div>
</div>
))}
</div>
</>
)}
<div className="mt-4">
<Link to="/forum" className="btn btn-outline-secondary">
<i className="bi bi-arrow-left me-2"></i>
Back to Forum
</Link>
</div>
</div>
);
};
export default MyPosts;