Skip to content

Commit ee53019

Browse files
committed
feat(artist-list) : 아티스트 좋아요 기능 구현
1 parent 2fb7cc9 commit ee53019

File tree

2 files changed

+74
-9
lines changed

2 files changed

+74
-9
lines changed

src/components/artist/list/ArtistListItems.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
1+
"use client";
2+
13
import Link from "next/link";
24
import { ArtistListItem } from "@/types/artists";
35
import Image from "next/image";
46
import { Heart } from "lucide-react";
57
import { Button } from "@/components/ui/button";
6-
7-
{
8-
/*TODO: 나중에 바로 아래 div에서 api로 불러온 콘서트 목록 map으로 돌리기*/
9-
}
8+
import { twMerge } from "tailwind-merge";
9+
import { likeArtist } from "@/lib/artists/artists";
10+
import { toast } from "sonner";
1011

1112
export default function ArtistListItems({ artists }: { artists: ArtistListItem[] }) {
13+
const handleLike = async (id: number) => {
14+
try {
15+
await likeArtist(id);
16+
toast.success("아티스트를 좋아요 했어요!");
17+
} catch (err) {
18+
if (err instanceof Error) {
19+
toast.error(err.message);
20+
} else {
21+
toast.error("알 수 없는 오류가 발생했습니다.");
22+
}
23+
}
24+
};
1225
return (
13-
<div className="grid grid-cols-5 gap-x-8 gap-y-12">
26+
<div className="grid gap-x-8 gap-y-12 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5">
1427
{artists.map((artist) => (
1528
<Link
1629
key={artist.id}
1730
href={`/concerts/${artist.id}`}
18-
className="flex cursor-pointer flex-col gap-5"
31+
className="group flex flex-col gap-5 transition hover:opacity-90"
1932
>
20-
<div className="relative aspect-square w-full overflow-hidden rounded-lg border">
33+
<div className="border-border/60 relative aspect-square overflow-hidden rounded-lg border">
2134
<Image
2235
src={artist.imageUrl || "/images/artist-placeholder.png"}
2336
alt="Concert Poster"
@@ -26,11 +39,22 @@ export default function ArtistListItems({ artists }: { artists: ArtistListItem[]
2639
className="object-cover"
2740
/>
2841
<Button
42+
onClick={async (e) => {
43+
e.preventDefault();
44+
e.stopPropagation();
45+
await handleLike(artist.id);
46+
}}
2947
type="button"
3048
aria-label="아티스트 좋아요"
31-
className="absolute top-2 right-2 z-10 h-10 w-10 cursor-pointer rounded-full bg-black/50 opacity-80 backdrop-blur-sm"
49+
className="absolute top-2 right-2 h-9 w-9 scale-90 rounded-full bg-black/20 opacity-0 backdrop-blur-sm transition-all duration-200 group-hover:scale-100 group-hover:opacity-100"
3250
>
33-
<Heart />
51+
<Heart
52+
className={twMerge(
53+
"h-5 w-5 fill-white text-white transition",
54+
artist.id % 2 === 0 && "fill-red-500 text-red-500"
55+
)}
56+
strokeWidth={0}
57+
/>
3458
</Button>
3559
</div>
3660
<strong className="line-clamp-1 text-2xl">{artist.artistName}</strong>

src/lib/artists/artists.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,44 @@ export async function getArtists(): Promise<ArtistListItem[]> {
3939
throw new Error("알 수 없는 오류가 발생했습니다.");
4040
}
4141
}
42+
43+
export async function likeArtist(id: number): Promise<void> {
44+
try {
45+
// fetch (네트워크 단계)
46+
const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/artists/likes/${id}`, {
47+
method: "POST",
48+
cache: "no-store",
49+
credentials: "include",
50+
});
51+
52+
// JSON 파싱 단계 (여기서도 에러 날 수 있음)
53+
let json: ArtistListResponse;
54+
55+
try {
56+
json = (await res.json()) as ArtistListResponse;
57+
} catch {
58+
// JSON 파싱 실패 (빈 응답, HTML 응답 등)
59+
throw new Error("서버 응답을 처리할 수 없습니다.");
60+
}
61+
62+
// HTTP / 비즈니스 에러 분기
63+
if (!res.ok || json.resultCode !== "OK") {
64+
throw new Error(json.msg ?? "아티스트 좋아요 요청에 실패했습니다.");
65+
}
66+
67+
// 성공
68+
} catch (err) {
69+
// 네트워크 에러 (Failed to fetch)
70+
if (err instanceof TypeError) {
71+
throw new Error("서버에 연결할 수 없습니다. 네트워크 상태를 확인해주세요.");
72+
}
73+
74+
// 내가 던진 Error는 그대로 전달
75+
if (err instanceof Error) {
76+
throw err;
77+
}
78+
79+
// 정말 예외적인 케이스
80+
throw new Error("알 수 없는 오류가 발생했습니다.");
81+
}
82+
}

0 commit comments

Comments
 (0)