Skip to content

Commit

Permalink
Merge pull request #155 from jihwooon/issue-83
Browse files Browse the repository at this point in the history
좋아요 기능 및 로그인 유저 경험 개선하기
  • Loading branch information
jihwooon authored Mar 7, 2024
2 parents 2ada589 + bbc1967 commit 8896892
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 35 deletions.
13 changes: 13 additions & 0 deletions client/src/api/books.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,16 @@ export const fetchBook = async (bookId: string) => {

return response.data;
}

export const likeBook = async (bookId: number) => {
const response = await httpClient.post(`/likes/${bookId}`)

return response.data;

}

export const unlikeBook = async (bookId: number) => {
const response = await httpClient.delete(`/likes/${bookId}`)

return response.data;
}
34 changes: 34 additions & 0 deletions client/src/components/book/LikeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FaHeart } from "react-icons/fa";
import styled from "styled-components";
import { BookDetail } from "../../models/book.model";
import Button from "../common/Button";

interface Props {
book: BookDetail;
onClick: () => void;
}

const LikeButton = ({book, onClick}: Props) => {
return (
<LikeButtonStyle>
<Button size="medium" scheme={book.liked ? "like" : "normal"} onClick={onClick} >
<FaHeart />
{book.likes}
</Button>
</LikeButtonStyle>
);
}

const LikeButtonStyle = styled.div`
display: flex;
gap: 6px;
svg {
color: inherit;
* {
color: inherit;
}
}
`;

export default LikeButton;
10 changes: 5 additions & 5 deletions client/src/components/common/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import styled from "styled-components";
import { FaSignInAlt, FaRegUser } from "react-icons/fa";
import logo from '../../assets/images/logo.png'
import { FaRegUser, FaSignInAlt } from "react-icons/fa";
import { Link } from "react-router-dom";
import styled from "styled-components";
import logo from '../../assets/images/logo.png';
import { useCategory } from "../../hooks/useCategory";
import { useAuthStore } from "../../store/authStore";

const Header = () => {
const { category } = useCategory();
const { isloggedIn, storeLogout } = useAuthStore();
const { isLoggedIn: isloggedIn, storeLogout } = useAuthStore();

return (
<HeaderStyle>
Expand Down Expand Up @@ -60,7 +60,7 @@ const HeaderStyle = styled.header`
width: 100%;
margin: 0 auto;
max-width: ${({ theme }) => theme.layout.width.large};
display: flex;
justify-content: space-between;
padding: 20px 0;
Expand Down
35 changes: 33 additions & 2 deletions client/src/hooks/useBook.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
import { useEffect, useState } from "react";
import { fetchBook } from "../api/books.api";
import { fetchBook, likeBook, unlikeBook } from "../api/books.api";
import { BookDetail } from "../models/book.model";
import { useAuthStore } from "../store/authStore";
import { useAlert } from "./useAlert";

export const useBook = (bookId: string | undefined) => {
const [book, setBook] = useState<BookDetail | null>(null);
const { isLoggedIn } = useAuthStore();
const showAlert = useAlert();

const likeToggle = () => {
if (!isLoggedIn) {
showAlert('로그인이 필요합니다.')
return;
}

if (!book) return;

if (book.liked) {
unlikeBook(book.id).then(() => {
setBook({
...book,
liked: false,
likes: book.likes - 1,
})
})
} else {
likeBook(book.id).then(() => {
setBook({
...book,
liked: true,
likes: book.likes + 1,
})
})
}
}

useEffect(() => {
if (!bookId) return;
Expand All @@ -13,5 +44,5 @@ export const useBook = (bookId: string | undefined) => {
})
}, [bookId])

return { book }
return { book, likeToggle }
}
8 changes: 5 additions & 3 deletions client/src/pages/BookDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Link, useParams } from "react-router-dom";
import styled from "styled-components";
import LikeButton from "../components/book/LikeButton";
import EllipsisBox from "../components/common/EllipisisBox";
import Title from "../components/common/Title";
import { useBook } from "../hooks/useBook";
Expand Down Expand Up @@ -47,7 +48,7 @@ const bookInfoList = [

const BookDetail = () => {
const { bookId } = useParams();
const { book } = useBook(bookId);
const { book, likeToggle } = useBook(bookId);

if (!book) return null;

Expand All @@ -73,8 +74,9 @@ const BookDetail = () => {
))}
<p className="summary">{book.summary}</p>

<div className="like">Like</div>

<div className="like">
<LikeButton book={book} onClick={likeToggle} />
</div>
<div className="add-cart">Cart</div>
</div>
</header>
Expand Down
22 changes: 11 additions & 11 deletions client/src/pages/Signin.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useForm } from 'react-hook-form'
import Title from "../components/common/Title"
import { InputText } from "../components/common/InputText"
import Button from "../components/common/Button"
import { Link, useNavigate } from "react-router-dom"
import { useAlert } from '../hooks/useAlert'
import { SingupStyle } from './Signup'
import { signin } from '../api/auth.api'
import Button from "../components/common/Button"
import { InputText } from "../components/common/InputText"
import Title from "../components/common/Title"
import { useAlert } from '../hooks/useAlert'
import { useAuthStore } from '../store/authStore'
import { SingupStyle } from './Signup'

export interface SigninProps {
email: string;
Expand All @@ -18,8 +18,8 @@ const Signin = () => {
const showAlert = useAlert();
const { register, handleSubmit, formState: { errors } } = useForm<SigninProps>();

const { isloggedIn, storeLogin } = useAuthStore();
const { isLoggedIn: isloggedIn, storeLogin } = useAuthStore();

const onSubmit = (data: SigninProps) => {
signin(data).then((res) => {
storeLogin(res.data)
Expand All @@ -36,16 +36,16 @@ const Signin = () => {
<SingupStyle>
<form onSubmit={handleSubmit(onSubmit)}>
<fieldset>
<InputText
placeholder="이메일"
<InputText
placeholder="이메일"
inputType="email"
{...register('email', { required: true })}
/>
{errors.email && <p className='error-text'>이메일을 입력해주세요.</p>}
</fieldset>
<fieldset>
<InputText
placeholder="패스워드"
<InputText
placeholder="패스워드"
inputType="password"
{...register('password', { required: true })}
/>
Expand Down
25 changes: 13 additions & 12 deletions client/src/store/authStore.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { create } from "zustand";

interface StoreState {
isloggedIn: boolean
storeLogin: (token: string) => void
storeLogout: () => void;
isLoggedIn: boolean
storeLogin: (token: string) => void
storeLogout: () => void;
}

export const getToken = () => {
Expand All @@ -20,13 +20,14 @@ export const removeToken = () => {
}

export const useAuthStore = create<StoreState>((set) => ({
isloggedIn: getToken() ? true : false,
storeLogin: (token: string) => {
set({ isloggedIn: true })
setToken(token);
},
storeLogout: () => {
set({ isloggedIn: false })
removeToken()
}
isLoggedIn: getToken() ? true : false,

storeLogin: (token: string) => {
set({ isLoggedIn: true })
setToken(token);
},
storeLogout: () => {
set({ isLoggedIn: false })
removeToken()
}
}))
6 changes: 5 additions & 1 deletion client/src/style/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type ColorKey =
| "text";
export type HeadingSize = "large" | "medium" | "small";
export type ButtonSize = "large" | "medium" | "small";
export type ButtonScheme = "primary" | "normal";
export type ButtonScheme = "primary" | "normal" | "like";
export type LayoutWith = "large" | "medium" | "small";

interface Theme {
Expand Down Expand Up @@ -85,6 +85,10 @@ export const light: Theme = {
color: "black",
backgroundColor: "lightgrey",
},
like: {
color: "while",
backgroundColor: "coral"
}
},
borderRadius: {
default: "4px",
Expand Down
9 changes: 9 additions & 0 deletions server/src/books/domain/book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export default class Book {

private pubDate: Date;

private liked: boolean;

constructor({
id = 0,
title = '',
Expand All @@ -45,6 +47,7 @@ export default class Book {
price = 0,
likes = 0,
pubDate = new Date(),
liked = false,
}: {
id?: number;
title?: string;
Expand All @@ -61,6 +64,7 @@ export default class Book {
price?: number;
likes?: number;
pubDate?: Date;
liked?: boolean;
}) {
this.id = id;
this.title = title;
Expand All @@ -77,6 +81,7 @@ export default class Book {
this.price = price;
this.likes = likes;
this.pubDate = pubDate;
this.liked = liked;
}

getId() {
Expand Down Expand Up @@ -138,4 +143,8 @@ export default class Book {
getPubDate() {
return this.pubDate;
}

getLiked() {
return this.liked;
}
}
3 changes: 2 additions & 1 deletion server/src/books/domain/books.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const findWithCategory = async (userId: number, bookId: number): Promise<
const [rows] = await doQuery((connection) =>
connection.execute<RowDataPacket[]>(
`SELECT b.id, b.title, b.category_id, c.name as category_name, b.form, b.isbn, b.summary, b.detail, b.author, b.pages, b.contents, b.price, b.pub_date, b.img_id,
(SELECT count(*) FROM likes WHERE b.id = liked_book_id) as Likes,
(SELECT count(*) FROM likes WHERE b.id = liked_book_id) as likes,
(SELECT EXISTS(SELECT * FROM likes WHERE user_id = ? AND liked_book_id)) as liked
FROM books b
LEFT JOIN category c
Expand Down Expand Up @@ -89,6 +89,7 @@ export const findWithCategory = async (userId: number, bookId: number): Promise<
likes: row.likes,
pubDate: row.pub_date,
imgId: row.img_id,
liked: row.liked,
});
};

Expand Down

0 comments on commit 8896892

Please sign in to comment.