- {loading ? (
-
Loading post...
- ) : error ? (
-
{error}
- ) : post ? (
- <>
- {/* Post Header */}
-
-
- navigate(`/thread/${threadId}`)} className="back-button">
- ← Back to Thread
-
- {post.thread && post.thread.stock_ref && (
-
- {post.thread.stock_ref.ticker} - {post.thread.stock_ref.stock_name}
-
- )}
-
-
{post.title}
-
-
- Posted by {post.author?.username} on {formatDate(post.created_at)}
-
- {post.updated_at !== post.created_at && (
-
- (edited on {formatDate(post.updated_at)})
-
- )}
-
-
+ setComments(response.data);
+ } catch (err: any) {
+ console.error("Error fetching comments:", err);
+ setError(err.response?.data?.detail || "Failed to load comments");
+ } finally {
+ setLoading(false);
+ }
+ }
- {/* Post Content */}
-
-
- {post.content}
-
-
- like.user_id === userId) ? 'liked' : ''}`}
- onClick={handleLikePost}
- >
- {post.liked_by?.length || 0} Likes
-
- setShowCommentForm(!showCommentForm)}
- >
- Add Comment
-
- myFunction()}
- >
- Delete Post
-
-
-
+ fetchComments();
+ }, [postId]);
- {/* Comment Form */}
- {showCommentForm && (
-
- )}
-
- {/* Comments Section */}
-
-
- Comments ({comments.length})
-
-
- {comments.length > 0 ? (
-
- {comments.map((comment) => (
-
-
- {comment.content}
-
-
-
-
- {comment.author?.username}
-
-
- {formatDate(comment.created_at)}
-
-
-
like.user_id === userId) ? 'liked' : ''}`}
- onClick={() => handleLikeComment(
- comment.comment_id,
- !!comment.liked_by?.some(like => like.user_id === userId)
- )}
- >
- {comment.liked_by?.length || 0} Likes
-
-
-
- ))}
-
- ) : (
-
- No comments yet. Be the first to comment!
-
- )}
-
- {!showCommentForm && (
-
setShowCommentForm(true)}
- >
- Add a Comment
-
- )}
-
- >
- ) : (
-
Post not found
+ // Tutorial trigger
+ useEffect(() => {
+ const tutorialMode = JSON.parse(localStorage.getItem("tutorialMode") || "false");
+ if (tutorialMode) {
+ setTutorialOpen(true);
+ }
+ }, []);
+
+ // Create comment
+ const handleCreateComment = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!commentContent.trim()) return;
+
+ setSubmitting(true);
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ setError("Not authenticated");
+ return;
+ }
+
+ await axios.post(
+ `http://127.0.0.1:8000/post/${postId}/comments`,
+ { content: commentContent },
+ { headers: { Authorization: `Bearer ${token}` } }
+ );
+
+ const response = await axios.get(`http://127.0.0.1:8000/post/${postId}/comments`, {
+ headers: { Authorization: `Bearer ${token}` }
+ });
+
+ setComments(response.data);
+ setCommentContent("");
+ setShowCommentForm(false);
+ } catch (err: any) {
+ console.error("Error creating comment:", err);
+ setError(err.response?.data?.detail || "Failed to create comment");
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ const handleDeletePost = async () => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ setError("Not authenticated");
+ return;
+ }
+
+ await axios.delete(
+ `http://127.0.0.1:8000/post/${postId}`,
+ { headers: { Authorization: `Bearer ${token}` } }
+ );
+
+ navigate(`/thread/${threadId}`);
+ } catch (err: any) {
+ console.error("Error deleting post:", err);
+ setError(err.response?.data?.detail || "Failed to delete post");
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ const handleLikePost = async () => {
+ if (!post || !userId) return;
+
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ setError("Not authenticated");
+ return;
+ }
+
+ const isLiked = post.liked_by?.some(like => like.user_id === userId);
+ const endpoint = `http://127.0.0.1:8000/post/${postId}/${isLiked ? 'unlike' : 'like'}`;
+
+ await axios.post(endpoint, {}, {
+ headers: { Authorization: `Bearer ${token}` }
+ });
+
+ const response = await axios.get(`http://127.0.0.1:8000/post/${postId}`, {
+ headers: { Authorization: `Bearer ${token}` }
+ });
+
+ setPost(response.data);
+ } catch (err: any) {
+ console.error("Error updating like status:", err);
+ setError(err.response?.data?.detail || "Failed to update like status");
+ }
+ };
+
+ const handleLikeComment = async (commentId: number, isLiked: boolean) => {
+ try {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ setError("Not authenticated");
+ return;
+ }
+
+ const endpoint = `http://127.0.0.1:8000/comment/${commentId}/${isLiked ? 'unlike' : 'like'}`;
+
+ await axios.post(endpoint, {}, {
+ headers: { Authorization: `Bearer ${token}` }
+ });
+
+ const response = await axios.get(`http://127.0.0.1:8000/post/${postId}/comments`, {
+ headers: { Authorization: `Bearer ${token}` }
+ });
+
+ setComments(response.data);
+ } catch (err: any) {
+ console.error("Error updating comment like status:", err);
+ setError(err.response?.data?.detail || "Failed to update comment like status");
+ }
+ };
+
+ const formatDate = (dateString: string) => {
+ return new Date(dateString).toLocaleDateString(undefined, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ };
+
+ return (
+
+
+
+
+ {loading ? (
+
Loading post...
+ ) : error ? (
+
{error}
+ ) : post ? (
+ <>
+
+
+ navigate(`/thread/${threadId}`)} className="back-button">
+ ← Back to Thread
+
+ {post.thread?.stock_ref && (
+
+ {post.thread.stock_ref.ticker} - {post.thread.stock_ref.stock_name}
+
)}
+
+
{post.title}
+
+
+ Posted by {post.author?.username} on {formatDate(post.created_at)}
+
+ {post.updated_at !== post.created_at && (
+
+ (edited on {formatDate(post.updated_at)})
+
+ )}
+
+
+
+
+
{post.content}
+
+ like.user_id === userId) ? 'liked' : ''}`}
+ onClick={handleLikePost}
+ >
+ {post.liked_by?.length || 0} Likes
+
+ setShowCommentForm(!showCommentForm)}>
+ Add Comment
+
+
+ Delete Post
+
+
+
+
+ {showCommentForm && (
+
+ )}
+
+
+
Comments ({comments.length})
+
+ {comments.length > 0 ? (
+
+ {comments.map((comment) => (
+
+
{comment.content}
+
+
+ {comment.author?.username}
+ {formatDate(comment.created_at)}
+
+
like.user_id === userId) ? 'liked' : ''}`}
+ onClick={() =>
+ handleLikeComment(comment.comment_id, !!comment.liked_by?.some(like => like.user_id === userId))
+ }
+ >
+ {comment.liked_by?.length || 0} Likes
+
+
+
+ ))}
+
+ ) : (
+
No comments yet. Be the first to comment!
+ )}
+
+ {!showCommentForm && (
+
setShowCommentForm(true)}>
+ Add a Comment
+
+ )}
-
- );
+ >
+ ) : (
+
Post not found
+ )}
+
+
+ {/* Tutorial Dialog */}
+ {tutorialOpen && (
+
+
+ {tutorialSteps[step].title}
+ {tutorialSteps[step].description}
+
+ {step < tutorialSteps.length - 1 ? "Next" : "Finish"}
+
+
+
+)}
+
+ );
}
-export default PostViewer;
\ No newline at end of file
+export default PostViewer;
diff --git a/HypeTrade307/src/pages/thread_viewer.tsx b/HypeTrade307/src/pages/thread_viewer.tsx
index 15a5a3df..c1388e7a 100644
--- a/HypeTrade307/src/pages/thread_viewer.tsx
+++ b/HypeTrade307/src/pages/thread_viewer.tsx
@@ -4,6 +4,12 @@ import axios from "axios";
import Navbar from "../components/NavbarSection/Navbar";
import CssBaseline from "@mui/material/CssBaseline";
import AppTheme from "../components/shared-theme/AppTheme";
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ Button
+} from "@mui/material";
import "./Thread.css";
// Define interfaces
@@ -43,27 +49,39 @@ function ThreadViewer() {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
+ // Tutorial state
+ const [tutorialOpen, setTutorialOpen] = useState(false);
+ const [step, setStep] = useState(0);
+
+ const tutorialSteps = [
+ { title: "Welcome to the Thread", description: "Here you can see all posts in this thread." },
+ { title: "Viewing Posts", description: "Click on any post to view or reply to it." },
+ { title: "Posting", description: "Click 'Create Post' to contribute to the discussion." },
+ { title: "You're Ready!", description: "That's it! Start participating in the conversation." }
+ ];
+
+ const nextStep = () => {
+ if (step < tutorialSteps.length - 1) {
+ setStep(step + 1);
+ } else {
+ setTutorialOpen(false);
+ }
+ };
+
// Fetch thread details
useEffect(() => {
async function fetchThread() {
try {
const token = localStorage.getItem("token");
- // if (!token) {
- // setError("Not authenticated");
- // setLoading(false);
- // return;
- // }
- console.log(`something something and my id is ${threadId}`);
const response = await axios.get(`http://127.0.0.1:8000/thread/${threadId}`, {
headers: { Authorization: `Bearer ${token}` }
});
- console.log("Data sent to API:", { response });
setThread(response.data);
} catch (err: any) {
- console.error("Full error response:", err.response?.data);
- setError(JSON.stringify(err.response?.data, null, 2)); // Pretty print error
+ console.error("Full error response:", err.response?.data);
+ setError(JSON.stringify(err.response?.data, null, 2));
}
}
@@ -77,12 +95,9 @@ function ThreadViewer() {
try {
const token = localStorage.getItem("token");
-
- console.log(`creating post`);
const response = await axios.get(`http://127.0.0.1:8000/thread/${threadId}`, {
headers: { Authorization: `Bearer ${token}` }
});
- console.log(`created post`);
setPosts(response.data);
} catch (err: any) {
@@ -96,11 +111,17 @@ function ThreadViewer() {
fetchPosts();
}, [threadId]);
+ // Show tutorial on page load if enabled
+ useEffect(() => {
+ const tutorialMode = JSON.parse(localStorage.getItem("tutorialMode") || "false");
+ if (tutorialMode) {
+ setTutorialOpen(true);
+ }
+ }, []);
+
// Create post
const handleCreatePost = async () => {
- if (!title || !content) {
- return;
- }
+ if (!title || !content) return;
try {
const token = localStorage.getItem("token");
@@ -111,19 +132,12 @@ function ThreadViewer() {
await axios.post(
`http://127.0.0.1:8000/thread/${threadId}/posts`,
- {
- title: title,
- content: content
- },
- {
- headers: { Authorization: `Bearer ${token}` }
- }
+ { title, content },
+ { headers: { Authorization: `Bearer ${token}` } }
);
- // Clear form and refresh posts
handleCloseModal();
- // Fetch updated posts
const response = await axios.get(`http://127.0.0.1:8000/thread/${threadId}`, {
headers: { Authorization: `Bearer ${token}` }
});
@@ -142,7 +156,6 @@ function ThreadViewer() {
setContent("");
};
- // Handle clicks outside the modal to close it
const handleOverlayClick = (e: React.MouseEvent
) => {
if ((e.target as HTMLElement).classList.contains("modal-overlay")) {
handleCloseModal();
@@ -153,8 +166,32 @@ function ThreadViewer() {
+
{/* Thread Header */}
+ {/* Back Button */}
+
+ navigate("/forum")}
+ style={{
+ position: "fixed",
+ top: "80px",
+ left: "40px",
+ zIndex: 1300,
+ padding: "8px 16px",
+ backgroundColor: "#1976d2",
+ color: "#fff",
+ border: "none",
+ borderRadius: "6px",
+ cursor: "pointer",
+ fontWeight: 500,
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)"
+ }}
+ >
+ ← Back to Forum
+
+
{thread && (
{thread.title}
@@ -255,9 +292,44 @@ function ThreadViewer() {
)}
+
+ {/* Tutorial Dialog */}
+ {}}
+ hideBackdrop
+ disableEscapeKeyDown
+ disableEnforceFocus
+ PaperProps={{
+ sx: {
+ position: 'absolute',
+ top: '50%',
+ left: '5%',
+ transform: 'translateY(-50%)',
+ maxWidth: 400,
+ padding: 2,
+ zIndex: 1301,
+ pointerEvents: 'auto',
+ },
+ }}
+ sx={{ pointerEvents: 'none' }}
+ >
+ {tutorialSteps[step].title}
+
+ {tutorialSteps[step].description}
+
+ {step < tutorialSteps.length - 1 ? "Next" : "Finish"}
+
+
+