Skip to content

Commit f137d04

Browse files
committed
Update PostDetailClient.tsx
1 parent 6e615b5 commit f137d04

1 file changed

Lines changed: 105 additions & 22 deletions

File tree

TechStacks.Client/src/app/posts/[id]/[slug]/PostDetailClient.tsx

Lines changed: 105 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export default function PostDetailClient() {
2525
const [replyToId, setReplyToId] = useState<number | null>(null);
2626
const [editingCommentId, setEditingCommentId] = useState<number | null>(null);
2727
const [editContent, setEditContent] = useState('');
28+
const [upVotedPostIds, setUpVotedPostIds] = useState<number[]>([]);
29+
const [downVotedPostIds, setDownVotedPostIds] = useState<number[]>([]);
30+
const [localPostPoints, setLocalPostPoints] = useState<number | null>(null);
2831
const [upVotedCommentIds, setUpVotedCommentIds] = useState<number[]>([]);
2932
const [downVotedCommentIds, setDownVotedCommentIds] = useState<number[]>([]);
3033
const [localCommentPoints, setLocalCommentPoints] = useState<Record<number, number>>({});
@@ -49,14 +52,19 @@ export default function PostDetailClient() {
4952
setPost(response.post);
5053
setComments(response.comments || []);
5154

52-
// Load user's comment votes if authenticated
55+
// Load user's votes if authenticated
5356
if (isAuthenticated) {
5457
try {
55-
const votes = await gateway.getUserPostCommentVotes(postId);
58+
const [votes, activity] = await Promise.all([
59+
gateway.getUserPostCommentVotes(postId),
60+
gateway.getUserPostActivity(),
61+
]);
5662
setUpVotedCommentIds(votes.upVotedCommentIds || []);
5763
setDownVotedCommentIds(votes.downVotedCommentIds || []);
64+
setUpVotedPostIds(activity.upVotedPostIds || []);
65+
setDownVotedPostIds(activity.downVotedPostIds || []);
5866
} catch (err) {
59-
console.error('Failed to load comment votes:', err);
67+
console.error('Failed to load votes:', err);
6068
}
6169
}
6270
} catch (err) {
@@ -70,6 +78,47 @@ export default function PostDetailClient() {
7078
loadPost();
7179
}, [postId, isAuthenticated]);
7280

81+
const handlePostVote = async (weight: number, e: React.MouseEvent) => {
82+
e.stopPropagation();
83+
if (!isAuthenticated) return;
84+
85+
try {
86+
const currentUpVoted = upVotedPostIds.includes(postId);
87+
const currentDownVoted = downVotedPostIds.includes(postId);
88+
const currentPoints = localPostPoints ?? post?.points ?? 0;
89+
90+
let newWeight = weight;
91+
let pointsDelta = 0;
92+
93+
if (weight === 1) {
94+
if (currentUpVoted) {
95+
newWeight = 0;
96+
pointsDelta = -1;
97+
setUpVotedPostIds(prev => prev.filter(id => id !== postId));
98+
} else {
99+
pointsDelta = currentDownVoted ? 2 : 1;
100+
setUpVotedPostIds(prev => [...prev, postId]);
101+
setDownVotedPostIds(prev => prev.filter(id => id !== postId));
102+
}
103+
} else if (weight === -1) {
104+
if (currentDownVoted) {
105+
newWeight = 0;
106+
pointsDelta = 1;
107+
setDownVotedPostIds(prev => prev.filter(id => id !== postId));
108+
} else {
109+
pointsDelta = currentUpVoted ? -2 : -1;
110+
setDownVotedPostIds(prev => [...prev, postId]);
111+
setUpVotedPostIds(prev => prev.filter(id => id !== postId));
112+
}
113+
}
114+
115+
setLocalPostPoints(currentPoints + pointsDelta);
116+
await gateway.votePost(postId, newWeight);
117+
} catch (err) {
118+
console.error('Failed to vote on post:', err);
119+
}
120+
};
121+
73122
const handleCommentVote = async (commentId: number, weight: number, e: React.MouseEvent) => {
74123
e.stopPropagation();
75124

@@ -391,27 +440,60 @@ export default function PostDetailClient() {
391440
<div className="container mx-auto max-w-5xl px-4 py-8 space-y-6">
392441
{/* Post Card */}
393442
<div className="bg-white rounded-lg shadow-md p-6">
394-
<div className="mb-4">
395-
<div className="flex items-start justify-between mb-4">
396-
<h1 className="text-3xl font-normal flex-1">
397-
{post.url ? (
398-
<a href={post.url} className="text-blue-600 hover:underline">
399-
{post.title}
400-
</a>
401-
) : (
402-
post.title
403-
)}
404-
</h1>
405-
{canEditPost(post) && (
406-
<Link href={routes.postEdit(postId, slug)}>
407-
<PrimaryButton className="ml-4">
408-
Edit
409-
</PrimaryButton>
410-
</Link>
411-
)}
443+
<div className="flex gap-4 mb-4">
444+
{/* Post Voting */}
445+
<div className="flex flex-col items-center text-gray-500 -mt-1">
446+
<button type="button"
447+
onClick={(e) => handlePostVote(1, e)}
448+
className={`text-2xl leading-none p-1 transition-colors ${
449+
upVotedPostIds.includes(postId)
450+
? 'text-green-600'
451+
: 'hover:text-green-600'
452+
}`}
453+
title={upVotedPostIds.includes(postId) ? 'Remove upvote' : 'Upvote'}
454+
disabled={!isAuthenticated}
455+
>
456+
457+
</button>
458+
<span className="font-semibold text-base">
459+
{localPostPoints ?? post.points ?? 0}
460+
</span>
461+
<button type="button"
462+
onClick={(e) => handlePostVote(-1, e)}
463+
className={`text-2xl leading-none p-1 transition-colors ${
464+
downVotedPostIds.includes(postId)
465+
? 'text-red-600'
466+
: 'hover:text-red-600'
467+
}`}
468+
title={downVotedPostIds.includes(postId) ? 'Remove downvote' : 'Downvote'}
469+
disabled={!isAuthenticated}
470+
>
471+
472+
</button>
412473
</div>
413474

414-
<div className="flex items-center gap-3 text-sm text-gray-600 mb-4">
475+
{/* Post Header */}
476+
<div className="flex-1">
477+
<div className="flex items-start justify-between mb-4">
478+
<h1 className="text-3xl font-normal flex-1">
479+
{post.url ? (
480+
<a href={post.url} className="text-blue-600 hover:underline">
481+
{post.title}
482+
</a>
483+
) : (
484+
post.title
485+
)}
486+
</h1>
487+
{canEditPost(post) && (
488+
<Link href={routes.postEdit(postId, slug)}>
489+
<PrimaryButton className="ml-4">
490+
Edit
491+
</PrimaryButton>
492+
</Link>
493+
)}
494+
</div>
495+
496+
<div className="flex items-center gap-3 text-sm text-gray-600 mb-4">
415497
{post.userProfileUrl && (
416498
<img
417499
src={post.userProfileUrl}
@@ -448,6 +530,7 @@ export default function PostDetailClient() {
448530
)}
449531
</div>
450532
)}
533+
</div>
451534
</div>
452535
</div>
453536

0 commit comments

Comments
 (0)