Skip to content

Commit 15d366e

Browse files
committed
feat: Enhance reaction handling with new ReactionStatus component and update reaction actions
1 parent 4f778bc commit 15d366e

File tree

7 files changed

+152
-15
lines changed

7 files changed

+152
-15
lines changed

src/app/api/play/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
22
import * as reactionActions from "@/backend/services/reaction.actions";
33

44
export async function GET() {
5-
const response = await reactionActions.getReactions({
5+
const response = await reactionActions.getResourceReactions({
66
resource_id: "00e2ed46-b113-4f4f-899f-ae8477ecd5a4",
77
resource_type: "ARTICLE",
88
});

src/backend/models/domain-models.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,26 @@ export interface Bookmark {
121121
user_id: string;
122122
created_at: Date;
123123
}
124+
125+
export type REACTION_TYPE =
126+
| "LOVE"
127+
| "UNICORN"
128+
| "WOW"
129+
| "FIRE"
130+
| "CRY"
131+
| "HAHA";
132+
133+
export interface Reaction {
134+
resource_id: string;
135+
resource_type: "ARTICLE" | "COMMENT";
136+
reaction_type: REACTION_TYPE;
137+
user_id: string;
138+
created_at: Date;
139+
}
140+
141+
export interface ReactionStatus {
142+
count: number;
143+
is_reacted: boolean;
144+
reaction_type?: REACTION_TYPE;
145+
reactor_user_ids?: string[];
146+
}

src/backend/persistence/persistence-repositories.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Article,
44
ArticleTag,
55
Bookmark,
6+
Reaction,
67
Series,
78
SeriesItem,
89
Tag,
@@ -66,8 +67,8 @@ const bookmarkRepository = new Repository<Bookmark>(
6667
repositoryConfig
6768
);
6869

69-
const reactionRepository = new Repository<Bookmark>(
70-
DatabaseTableName.bookmarks,
70+
const reactionRepository = new Repository<Reaction>(
71+
DatabaseTableName.reactions,
7172
pgClient,
7273
repositoryConfig
7374
);

src/backend/services/reaction.actions.ts

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ActionException, handleActionException } from "./RepositoryException";
77
import { persistenceRepository } from "../persistence/persistence-repositories";
88
import { and, eq } from "sqlkit";
99
import { ReactionActionInput } from "./inputs/reaction.input";
10+
import { ReactionStatus } from "../models/domain-models";
1011

1112
const sql = String.raw;
1213

@@ -27,6 +28,7 @@ export async function toogleReaction(
2728
where: and(
2829
eq("resource_id", input.resource_id),
2930
eq("resource_type", input.resource_type),
31+
eq("reaction_type", input.reaction_type),
3032
eq("user_id", sessionUserId)
3133
),
3234
});
@@ -37,13 +39,14 @@ export async function toogleReaction(
3739
where: and(
3840
eq("resource_id", input.resource_id),
3941
eq("resource_type", input.resource_type),
42+
eq("reaction_type", input.reaction_type),
4043
eq("user_id", sessionUserId)
4144
),
4245
});
4346
return {
44-
reaction_type: input.resource_type,
47+
reaction_type: input.reaction_type,
4548
resource_id: input.resource_id,
46-
reacted: false,
49+
is_reacted: false,
4750
};
4851
}
4952

@@ -52,21 +55,22 @@ export async function toogleReaction(
5255
{
5356
resource_id: input.resource_id,
5457
resource_type: input.resource_type,
58+
reaction_type: input.reaction_type,
5559
user_id: sessionUserId,
5660
created_at: new Date(),
5761
},
5862
]);
5963
return {
6064
reaction_type: input.reaction_type,
6165
resource_id: input.resource_id,
62-
reacted: true,
66+
is_reacted: true,
6367
};
6468
} catch (error) {
6569
handleActionException(error);
6670
}
6771
}
6872

69-
export async function getReactions(
73+
export async function getResourceReactions(
7074
_input: z.infer<typeof ReactionActionInput.getReactionsInput>
7175
) {
7276
try {
@@ -93,13 +97,37 @@ export async function getReactions(
9397
input.resource_type,
9498
]);
9599

96-
return response?.rows?.map((row: any) => {
97-
return {
98-
reaction_type: row?.reaction_type! ?? null,
99-
count: Number(row?.count) ?? 0,
100-
is_reacted: row?.reactor_user_ids?.includes(sessionUserId) ?? false,
101-
};
102-
});
100+
const rows = response?.rows as ReactionStatus[];
101+
102+
// Create a map of results
103+
const reactionMap = new Map<
104+
string,
105+
{ count: number; is_reacted: boolean; reactor_user_ids: string[] }
106+
>();
107+
108+
for (const row of rows) {
109+
const reaction_type = row?.reaction_type;
110+
const count = Number(row?.count ?? 0);
111+
const reactor_user_ids = row?.reactor_user_ids ?? [];
112+
const is_reacted = reactor_user_ids.includes(sessionUserId!);
113+
114+
if (reaction_type) {
115+
reactionMap.set(reaction_type, { count, is_reacted, reactor_user_ids });
116+
}
117+
}
118+
119+
// Return all types, filling missing ones with count: 0
120+
return ["LOVE", "UNICORN", "WOW", "FIRE", "CRY", "HAHA"].map(
121+
(reaction_type) => {
122+
const entry = reactionMap.get(reaction_type);
123+
return {
124+
reaction_type,
125+
count: entry?.count ?? 0,
126+
is_reacted: entry?.is_reacted ?? false,
127+
reactor_user_ids: entry?.reactor_user_ids ?? [],
128+
};
129+
}
130+
);
103131
} catch (error) {
104132
handleActionException(error);
105133
}

src/components/ArticleCard.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import Link from "next/link";
77
import { useMemo } from "react";
88
import { HoverCard, HoverCardContent, HoverCardTrigger } from "./ui/hover-card";
99
import UserInformationCard from "./UserInformationCard";
10-
import BookmarkStatus from "./BookmarkStatus";
10+
import BookmarkStatus from "./render-props/BookmarkStatus";
1111
import clsx from "clsx";
12+
import ReactionStatus from "./render-props/ReactionStatus";
1213

1314
interface ArticleCardProps {
1415
id: string;
@@ -149,6 +150,18 @@ const ArticleCard = ({
149150
</button>
150151
</div>
151152

153+
<ReactionStatus
154+
resource_type="ARTICLE"
155+
resource_id={id}
156+
render={({ reactions, toggle }) => {
157+
return reactions.map((r) => (
158+
<button onClick={() => toggle(r.reaction_type!)}>
159+
{r.reaction_type} ({r.count})
160+
</button>
161+
));
162+
}}
163+
/>
164+
152165
<BookmarkStatus
153166
resource_type="ARTICLE"
154167
resource_id={id}
File renamed without changes.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from "react";
2+
import * as reactionActions from "@/backend/services/reaction.actions";
3+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4+
import {
5+
REACTION_TYPE,
6+
ReactionStatus as ReactionStatusModel,
7+
} from "@/backend/models/domain-models";
8+
9+
interface Props {
10+
resource_type: "ARTICLE" | "COMMENT";
11+
resource_id: string;
12+
render: ({
13+
toggle,
14+
reactions,
15+
getReaction,
16+
}: {
17+
toggle: (reaction_type: REACTION_TYPE) => void;
18+
getReaction: (reaction_type: REACTION_TYPE) => void;
19+
reactions: ReactionStatusModel[];
20+
}) => React.ReactNode;
21+
}
22+
23+
// All possible reactions
24+
25+
const ReactionStatus: React.FC<Props> = ({
26+
resource_id,
27+
resource_type,
28+
render,
29+
}) => {
30+
const queryClient = useQueryClient();
31+
32+
const query = useQuery({
33+
queryKey: ["reaction", resource_id, resource_type],
34+
queryFn: () =>
35+
reactionActions.getResourceReactions({ resource_id, resource_type }),
36+
});
37+
38+
const mutation = useMutation({
39+
mutationFn: (reaction_type: REACTION_TYPE) =>
40+
reactionActions.toogleReaction({
41+
resource_id,
42+
resource_type,
43+
reaction_type,
44+
}),
45+
onSuccess: () => {
46+
queryClient.invalidateQueries({
47+
queryKey: ["reaction", resource_id, resource_type],
48+
});
49+
},
50+
});
51+
52+
const toggle = (reaction_type: REACTION_TYPE) => {
53+
mutation.mutate(reaction_type);
54+
};
55+
56+
const getReaction = (reaction_type: REACTION_TYPE) => {
57+
return query?.data?.find((r) => r.reaction_type == reaction_type);
58+
};
59+
60+
return render({
61+
reactions: query?.data
62+
? query.data.map((r) => ({
63+
...r,
64+
reaction_type: r.reaction_type as REACTION_TYPE,
65+
}))
66+
: [],
67+
getReaction,
68+
toggle,
69+
});
70+
};
71+
72+
export default ReactionStatus;

0 commit comments

Comments
 (0)