Skip to content

Commit df7baae

Browse files
committed
feat(fe/likes): add response like
1 parent 7e7e5a3 commit df7baae

File tree

8 files changed

+113
-58
lines changed

8 files changed

+113
-58
lines changed

spez-frontend/.babelrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// .babelrc
2+
{
3+
"presets": ["next/babel"]
4+
}

spez-frontend/.gitignore

+1-2
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,4 @@ yarn-error.log*
3333

3434
# typescript
3535
*.tsbuildinfo
36-
next-env.d.ts
37-
/__tests__/
36+
next-env.d.ts

spez-frontend/jest.config.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// jest.config.js
2+
module.exports = {
3+
preset: 'ts-jest',
4+
testEnvironment: 'jest-environment-jsdom',
5+
transform: {
6+
'^.+\\.(ts|tsx)$': 'ts-jest',
7+
},
8+
moduleNameMapper: {
9+
'\\.(css|scss)$': 'identity-obj-proxy', // Mock CSS imports
10+
},
11+
transformIgnorePatterns: [
12+
'node_modules/(?!(some-es-module)/)', // Adjust if any node_modules need transforming
13+
],
14+
};

spez-frontend/package.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
"dev": "next dev",
77
"build": "next build",
88
"start": "next start",
9-
"lint": "next lint"
9+
"lint": "next lint",
10+
"test": "jest"
1011
},
1112
"dependencies": {
13+
"@testing-library/jest-dom": "^6.6.3",
14+
"@testing-library/react": "^16.0.1",
1215
"axios": "^1.7.7",
1316
"html-react-parser": "^5.1.18",
17+
"jest": "^29.7.0",
1418
"js-cookie": "^3.0.5",
1519
"next": "14.2.15",
1620
"react": "^18",
@@ -20,14 +24,17 @@
2024
"suneditor-react": "^3.6.1"
2125
},
2226
"devDependencies": {
27+
"@types/jest": "^29.5.14",
2328
"@types/js-cookie": "^3.0.6",
2429
"@types/node": "^20",
2530
"@types/react": "^18",
2631
"@types/react-dom": "^18",
2732
"eslint": "^8",
2833
"eslint-config-next": "14.2.15",
34+
"jest-environment-jsdom": "^29.7.0",
2935
"postcss": "^8",
3036
"tailwindcss": "^3.4.1",
37+
"ts-jest": "^29.2.5",
3138
"typescript": "^5"
3239
}
3340
}

spez-frontend/src/app/layout.tsx

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import type { Metadata } from "next";
2-
import localFont from "next/font/local";
2+
// import localFont from "next/font/local";
33
import "./globals.css";
44
import Header from "@/components/Header";
5-
const geistSans = localFont({
6-
src: "./fonts/GeistVF.woff",
7-
variable: "--font-geist-sans",
8-
weight: "100 900",
9-
});
10-
const geistMono = localFont({
11-
src: "./fonts/GeistMonoVF.woff",
12-
variable: "--font-geist-mono",
13-
weight: "100 900",
14-
});
5+
// const geistSans = localFont({
6+
// src: "./fonts/GeistVF.woff",
7+
// variable: "--font-geist-sans",
8+
// weight: "100 900",
9+
// });
10+
// const geistMono = localFont({
11+
// src: "./fonts/GeistMonoVF.woff",
12+
// variable: "--font-geist-mono",
13+
// weight: "100 900",
14+
// });
1515

1616
export const metadata: Metadata = {
1717
title: "Create Next App",
@@ -26,7 +26,7 @@ export default function RootLayout({
2626
return (
2727
<html lang="en">
2828
<body
29-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29+
// className={`${geistSans.variable} ${geistMono.variable} antialiased`}
3030
>
3131
<Header/>
3232
{children}

spez-frontend/src/app/post/[id]/page.tsx

+9-42
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,31 @@ import Link from "next/link";
44
import Comment from "@/components/Comment";
55
import Parser from 'html-react-parser'
66
import Image from "next/image";
7-
import EagleEmoji from '../../../../public/eagle_emoji.png'
87
import CmtForm from "@/components/CmtForm";
98
import { getRelativeTime } from "@/utils/getRelativeTime";
10-
import axios from "axios";
119
import { GoTrash } from "react-icons/go";
1210
import { IPost, IComment } from "@/types";
11+
import LikeButton from "@/components/LikeButton";
1312
type Props = {
1413
params: { id: string };
1514
};
16-
interface getILike{
17-
like: number
18-
}
15+
// interface getILike{
16+
// like: number
17+
// }
1918
export default async function Post({ params }: Props) {
2019
const id: string = params.id;
2120
const { getPost, getLike, getComment, delPost } = svPost();
2221

2322
const info: IPost = await getPost(id);
24-
const likes: getILike = await getLike(id);
23+
const likes: number = await getLike(id);
24+
console.log(likes)
2525
const comment: IComment[] = await getComment(id);
2626
const author_url = "/user/" + info.author.id;
27-
const handleLikeButton = async (e: React.FormEvent) => {
28-
e.preventDefault();
29-
try {
30-
// Send a POST request to your FastAPI backend
31-
let token: string | null = null
32-
if (typeof window !== 'undefined')
33-
token = localStorage.getItem('jwt')
34-
await axios.post(`http://localhost:8000/likes/?post_id=${id}`,
35-
{
36-
headers: {
37-
'accept': 'application/json',
38-
/* eslint-disable @typescript-eslint/no-use-before-define */
39-
'Authorization': `Bearer ${token}`
40-
}
41-
}
42-
);
43-
44-
// Redirect or give feedback upon success
45-
// alert('Post created successfully!');
46-
} catch (error) {
47-
console.error('Error posting data:', error);
48-
alert('Failed to create like.');
49-
}
50-
};
5127
const handleDelPost = async () => {
5228
let token: string | null = null
5329
if (typeof window !== 'undefined')
5430
token = localStorage.getItem('jwt')
55-
const deletePost:string = await delPost(id, token);
56-
console.log(deletePost)
31+
await delPost(id, token);
5732

5833
}
5934
return (
@@ -71,17 +46,9 @@ export default async function Post({ params }: Props) {
7146
<div className="pl-2 text-2xl font-bold">{info.title}</div>
7247
<div className="p-2">{Parser(info.content)}</div>
7348
<div className="flex flex-row">
74-
<button onClick={handleLikeButton} className="w-auto flex flex-row border-2 border-black rounded-full px-2">
75-
<p className="pt-[0.35rem]"> Ưng</p>
76-
<Image
77-
src={EagleEmoji}
78-
alt = "eagle_img"
79-
width={30}
80-
height={50} />
81-
<p className="pt-[0.35rem]">: {likes.like} </p>
82-
</button>
49+
<LikeButton initialLikes={likes} post_id={id} />
8350
<div className="w-auto mt-[0.35rem] ml-5 border-2 rounded-full px-2 border-black">Phản hồi: {comment.length}</div>
84-
</div>
51+
</div>
8552
<CmtForm postId={id} />
8653
</div>
8754
<div className="border-2 border-slate-950 p-2 text-black rounded shadow h-full w-1/2 space-y-5">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// components/LikeButton.tsx
2+
import React, { useState } from "react";
3+
import axios from "axios";
4+
import Image from "next/image";
5+
import EagleEmoji from "../../public/eagle_emoji.png";
6+
import svPost from "@/utils/svPost";
7+
interface LikeButtonProps {
8+
initialLikes: number;
9+
post_id: string
10+
}
11+
12+
const LikeButton: React.FC<LikeButtonProps> = ({ initialLikes, post_id }) => {
13+
const [likes, setLikes] = useState<number>(initialLikes);
14+
const [liked, setLiked] = useState(false);
15+
const {postLike, delLike} = svPost()
16+
17+
const handleLike = async () => {
18+
try {
19+
let token: string | null = null
20+
if(typeof window !== "undefined") token = localStorage.getItem("jwt")
21+
22+
const response = liked? await postLike(post_id, token) : await delLike(post_id, token);
23+
if (response.status == 201 || response.status == 204) {
24+
setLiked(!liked);
25+
setLikes((prev) => (liked ? prev - 1 : prev + 1));
26+
}
27+
28+
} catch (error) {
29+
console.error('Error liking post:', error);
30+
}
31+
};
32+
return (
33+
<>
34+
<button
35+
onClick={handleLike}
36+
className="w-auto flex flex-row border-2 border-black rounded-full px-2"
37+
aria-pressed={liked}
38+
>
39+
<p className="pt-[0.35rem]"> {!liked ? "Ưng" : "ko Ưng"} {likes}</p>
40+
<Image src={EagleEmoji} alt="eagle_img" width={30} height={50} />
41+
</button>
42+
</>
43+
);
44+
};
45+
46+
export default LikeButton;

spez-frontend/src/utils/svPost.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,30 @@ export default function svPost() {
5858
});
5959
return data;
6060
}
61+
async function postLike(id: string, token: string | null) {
62+
return await axios.post(API.like + "/" + id, {},{
63+
headers: {
64+
accept: "application/json",
65+
Authorization: `Bearer ${token}`
66+
}
67+
})
68+
}
69+
async function delLike(id: string, token: string | null) {
70+
return await axios.delete(API.like + "/" + id, {
71+
headers: {
72+
// accept: "application/json",
73+
Authorization: `Bearer ${token}`
74+
}
75+
})
76+
}
6177
return {
6278
getAllPost,
6379
getPost,
6480
getLike,
6581
getComment,
6682
delPost,
67-
delComment
83+
delComment,
84+
postLike,
85+
delLike
6886
};
6987
}

0 commit comments

Comments
 (0)