Skip to content

Commit 5b87d48

Browse files
authored
Merge pull request #84 from thomassth/participation-story
participation cleanup + storybook
2 parents 6c78d2a + 9c1331d commit 5b87d48

6 files changed

Lines changed: 256 additions & 138 deletions

File tree

client/src/app/core/conversation.tsx

Lines changed: 15 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,14 @@
11
import { useParams } from "react-router";
2-
import CoreBase from "./base";
32
import {
43
createVote,
54
getConversation,
65
getNextComment,
76
} from "../../components/api/conversation";
8-
import { useEffect, useMemo, useState } from "react";
9-
import {
10-
CheckIcon,
11-
ForwardIcon,
12-
PlusIcon,
13-
XMarkIcon,
14-
} from "@heroicons/react/24/solid";
15-
import { Button } from "@headlessui/react";
16-
import { Conversation } from "./dashboard";
7+
import { useEffect } from "react";
8+
import { Conversation, ParticipationComment } from "./dashboard";
179
import { useQuery } from "@tanstack/react-query";
1810
import { getApi } from "../../components/api/base";
19-
import CommentConfig from "../../components/admin/comments/CommentConfig";
20-
21-
type Comment = { content: string; id: string; num_votes: number };
22-
23-
const VotingSection = ({
24-
comment,
25-
commentNumber,
26-
onVote,
27-
}: {
28-
comment: Comment | null;
29-
commentNumber?: number;
30-
onVote: (commentId: string, vote: "agree" | "disagree" | "skip") => void;
31-
}) => {
32-
return (
33-
comment && (
34-
<div className="flex flex-col items-start gap-4 p-4 bg-white rounded-xl">
35-
<div className="flex flex-col items-start gap-2 mb-2">
36-
<h3 className="font-semibold">Comment {commentNumber}</h3>
37-
<p className="text-gray-700 text-2xl font-bold">{comment.content}</p>
38-
<time className="text-gray-500">(time here)</time>
39-
</div>
40-
<div className="flex items-center gap-2 w-full">
41-
<Button
42-
onClick={() => onVote(comment.id, "agree")}
43-
className="border hover:bg-primary hover:text-white px-2 py-2 rounded-xl flex flex-row items-center gap-x-2"
44-
>
45-
<CheckIcon className="h-5 w-5" />
46-
Agree
47-
</Button>
48-
<Button
49-
onClick={() => onVote(comment.id, "disagree")}
50-
className="border hover:bg-primary hover:text-white px-2 py-2 rounded-xl flex flex-row items-center gap-x-2"
51-
>
52-
<XMarkIcon className="h-5 w-5" />
53-
Disagree
54-
</Button>
55-
<Button
56-
onClick={() => onVote(comment.id, "skip")}
57-
className="bg-background px-2 py-2 rounded-xl flex flex-row items-center gap-x-2 border border-gray-300 text-gray-700 hover:bg-primary hover:text-white ml-auto"
58-
>
59-
Skip
60-
<ForwardIcon className="h-5 w-5" />
61-
</Button>
62-
</div>
63-
</div>
64-
)
65-
);
66-
};
11+
import { ParticipationSpa } from "../../components/participation/ParticipationSpa";
6712

6813
const ConversationPage = () => {
6914
const { conversationId } = useParams<{ conversationId: string }>();
@@ -73,19 +18,20 @@ const ConversationPage = () => {
7318
queryFn: () => getConversation(conversationId ?? ""),
7419
});
7520

76-
const currentComment = useQuery<{ comment: Comment; num_votes: number }>({
21+
const currentComment = useQuery<{
22+
comment: ParticipationComment;
23+
num_votes: number;
24+
}>({
7725
queryKey: ["current-comment", conversationId],
7826
queryFn: () => getNextComment(conversationId ?? ""),
7927
});
8028

81-
const amountOfVotedComments = useMemo(() => {
82-
return currentComment.data?.num_votes ?? 0;
83-
}, [currentComment]);
8429
const nextComment = async () => {
8530
if (!conversationId) return;
8631

8732
await currentComment.refetch();
8833
};
34+
8935
const fetchConversation = async () => {
9036
if (!conversationId) return;
9137
await conversation.refetch();
@@ -100,22 +46,7 @@ const ConversationPage = () => {
10046
fetchData();
10147
}, [conversationId]);
10248

103-
const [dialog, setDialog] = useState<HTMLDialogElement | null>(null);
104-
105-
useEffect(() => {
106-
setDialog(document.getElementById("comment-dialog") as HTMLDialogElement);
107-
}, []);
108-
109-
const handleEditClick = (state: boolean) => {
110-
if (state) {
111-
dialog?.showModal();
112-
} else {
113-
dialog?.close();
114-
}
115-
};
116-
11749
const onFormComplete = () => {
118-
handleEditClick(false);
11950
comments.refetch();
12051
};
12152
// end of dialog logic
@@ -136,66 +67,13 @@ const ConversationPage = () => {
13667
queryFn: () => getApi(`/conversations/${conversationId}/comments`),
13768
});
13869
return (
139-
<CoreBase>
140-
<main className="w-[95%] mx-auto">
141-
<section className="p-8">
142-
<h1 className="text-3xl font-bold mb-4">{conversation.data?.name}</h1>
143-
<p className="mb-4">{conversation.data?.description}</p>
144-
</section>
145-
<section
146-
className="p-8 bg-background w-full flex flex-col"
147-
aria-labelledby="active-comment-header"
148-
>
149-
<div className="flex justify-between items-center">
150-
<h2
151-
id="active-comment-header"
152-
className="font-semibold text-secondary mb-4"
153-
>
154-
Active Comments
155-
</h2>
156-
<Button
157-
onClick={() => handleEditClick(true)}
158-
className="flex mb-4 bg-white border border-gray-300 p-2 w-min whitespace-nowrap items-center justify-center gap-x-2 rounded-xl"
159-
>
160-
<PlusIcon height={30} width={30} /> Add Comment
161-
</Button>
162-
</div>
163-
164-
{currentComment.isSuccess ? (
165-
<VotingSection
166-
comment={currentComment.data?.comment}
167-
commentNumber={amountOfVotedComments + 1}
168-
onVote={onVote}
169-
/>
170-
) : (
171-
<div className="flex flex-col items-center justify-center p-4 bg-white rounded-xl">
172-
<p className="text-gray-500">No more comments to review.</p>
173-
</div>
174-
)}
175-
<p className="text-center pt-6">
176-
{amountOfVotedComments + 1} of {comments.data?.length} comments
177-
</p>
178-
</section>
179-
</main>
180-
{!!conversationId && (
181-
<dialog
182-
id="comment-dialog"
183-
className="m-[revert] p-[revert] border-2 backdrop:bg-primary backdrop:opacity-80"
184-
>
185-
<button
186-
className="border-2 px-2 py-2 rounded-xl flex flex-row items-center gap-x-2 ml-auto"
187-
autoFocus
188-
onClick={() => handleEditClick(false)}
189-
>
190-
Close
191-
</button>
192-
<CommentConfig
193-
onComplete={onFormComplete}
194-
conversationId={conversationId}
195-
/>
196-
</dialog>
197-
)}
198-
</CoreBase>
70+
<ParticipationSpa
71+
conversation={conversation.data}
72+
currentComment={currentComment.data}
73+
comments={comments.data}
74+
onVoteComplete={onVote}
75+
onComplete={onFormComplete}
76+
/>
19977
);
20078
};
20179

client/src/app/core/dashboard.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export type Conversation = {
66
id: string;
77
name: string;
88
description: string;
9-
author: { username: string };
9+
author: { id: string; username: string };
1010
num_participants: number;
1111
date_created: string;
1212
is_active: boolean;
@@ -18,6 +18,13 @@ export type Comment = {
1818
content: string;
1919
};
2020

21+
export type ParticipationComment = Comment & {
22+
user_id: string;
23+
approved: boolean;
24+
conversation_id: string;
25+
date_created: string;
26+
};
27+
2128
// See server\convergent\routers\moderation.py
2229
export type ModerationComment = Comment & {
2330
user_id: string;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { Meta, StoryObj } from "@storybook/react-vite";
2+
3+
import {
4+
withRouter,
5+
reactRouterParameters,
6+
} from "storybook-addon-remix-react-router";
7+
8+
import { ParticipationSpa } from "./ParticipationSpa";
9+
10+
const meta = {
11+
title: "client/participation/Conversation",
12+
component: ParticipationSpa,
13+
decorators: [withRouter],
14+
argTypes: {
15+
conversation: { control: "object" },
16+
currentComment: { control: "object" },
17+
comments: { control: "object" },
18+
},
19+
parameters: {
20+
reactRouter: reactRouterParameters({}),
21+
},
22+
} satisfies Meta<typeof ParticipationSpa>;
23+
24+
export default meta;
25+
type Story = StoryObj<typeof meta>;
26+
27+
export const ActiveConversation: Story = {
28+
args: {
29+
conversation: {
30+
id: "d035d428-90c1-4ea9-99de-1d7c1f81a939",
31+
name: "Title test",
32+
description: "desc",
33+
author: {
34+
id: "162f507e-7f6e-4871-a8c8-4912c3be624c",
35+
username: "gg@gg.ca",
36+
},
37+
num_participants: 1,
38+
date_created: "2025-07-04T04:38:18.318536",
39+
is_active: true,
40+
display_unmoderated: false,
41+
},
42+
currentComment: {
43+
num_votes: 0,
44+
comment: {
45+
user_id: "162f507e-7f6e-4871-a8c8-4912c3be624c",
46+
content: "test1",
47+
approved: true,
48+
conversation_id: "d035d428-90c1-4ea9-99de-1d7c1f81a939",
49+
id: "fbeec989-d824-410d-9577-2f235e688b01",
50+
date_created: "2025-07-04T04:49:24.267970",
51+
},
52+
},
53+
comments: [
54+
{ id: "fbeec989-d824-410d-9577-2f235e688b01", content: "test1" },
55+
{ id: "32ac0523-464e-4e11-8382-d9e3a6680db3", content: "test2" },
56+
{ id: "0f55d2fd-2717-4bbb-b0e9-bff72e7c83e6", content: "" },
57+
],
58+
onVoteComplete: () => {},
59+
onComplete: () => {},
60+
},
61+
};

0 commit comments

Comments
 (0)