Skip to content

Commit b193031

Browse files
committed
feat(artist-list) : 아티스트 카드 컴포넌트 생성 및 컴포넌트 분리
1 parent 1c94d29 commit b193031

File tree

3 files changed

+88
-79
lines changed

3 files changed

+88
-79
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"use client";
2+
3+
import Image from "next/image";
4+
import { Button } from "@/components/ui/button";
5+
import { Heart, Loader2 } from "lucide-react";
6+
import { twMerge } from "tailwind-merge";
7+
import Link from "next/link";
8+
import React, { useState } from "react";
9+
import { ArtistListItem } from "@/types/artists";
10+
import { likeArtist } from "@/lib/artists/artists";
11+
import { toast } from "sonner";
12+
13+
// TODO:
14+
// - 아티스트 좋아요(팔로우) 상태를 서버에서 함께 내려받아 초기 상태로 관리해야 함
15+
// - 좋아요 상태에 따라 하트 아이콘을 빨간색(fill-red-500)으로 표시
16+
// - 이미 좋아요된 상태에서 버튼 클릭 시 좋아요 취소(unlike) API 호출하도록 분기 처리
17+
// - 좋아요/취소 시 optimistic update 적용 검토
18+
// - 실제 서버에서 받은 초기 like 상태를 props나 state로 관리하기
19+
20+
export default function ArtistListCard({ artist }: { artist: ArtistListItem }) {
21+
const [isLoading, setIsLoading] = useState(false);
22+
const [isLiked, setIsLiked] = useState(false);
23+
24+
const handleLikeClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
25+
e.preventDefault();
26+
e.stopPropagation();
27+
28+
if (isLoading) return;
29+
30+
setIsLoading(true);
31+
try {
32+
// TODO: isLiked 상태에 따라 API 호출 분기 (likeArtist / unlikeArtist)
33+
await likeArtist(artist.id);
34+
35+
setIsLiked(!isLiked);
36+
toast.success(isLiked ? "좋아요를 취소했습니다." : "아티스트를 좋아요 했습니다!");
37+
} catch (err) {
38+
toast.error(err instanceof Error ? err.message : "오류가 발생했습니다.");
39+
} finally {
40+
setIsLoading(false);
41+
}
42+
};
43+
44+
return (
45+
<Link
46+
key={artist.id}
47+
href={`/artists/${artist.id}`}
48+
className="group flex flex-col gap-5 transition hover:opacity-90"
49+
>
50+
<div className="border-border/60 relative aspect-square overflow-hidden rounded-lg border">
51+
<Image
52+
src={artist.imageUrl || "/images/artist-placeholder.png"}
53+
alt={artist.artistName}
54+
fill
55+
sizes="(min-width: 1024px) 20vw, (min-width: 768px) 25vw, 50vw"
56+
className="object-cover"
57+
/>
58+
<Button
59+
onClick={handleLikeClick}
60+
disabled={isLoading}
61+
type="button"
62+
aria-label="아티스트 좋아요"
63+
className={twMerge(
64+
"absolute top-2 right-2 h-9 w-9 scale-90 rounded-full bg-black/20 backdrop-blur-sm transition-all duration-200 group-hover:scale-100 group-hover:opacity-100",
65+
isLoading ? "opacity-100" : "opacity-0"
66+
)}
67+
>
68+
{isLoading ? (
69+
<Loader2 className="h-5 w-5 animate-spin text-white" />
70+
) : (
71+
<Heart
72+
className={twMerge(
73+
"h-5 w-5 transition",
74+
isLiked ? "fill-red-500 text-red-500" : "fill-white text-white"
75+
)}
76+
strokeWidth={0}
77+
/>
78+
)}
79+
</Button>
80+
</div>
81+
<strong className="line-clamp-1 text-2xl">{artist.artistName}</strong>
82+
</Link>
83+
);
84+
}

src/components/artist/list/ArtistListItems.tsx

Lines changed: 2 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,12 @@
1-
"use client";
2-
3-
import Link from "next/link";
41
import { ArtistListItem } from "@/types/artists";
5-
import Image from "next/image";
6-
import { Heart } from "lucide-react";
7-
import { Button } from "@/components/ui/button";
8-
import { twMerge } from "tailwind-merge";
9-
import { likeArtist } from "@/lib/artists/artists";
10-
import { toast } from "sonner";
112
import React from "react";
12-
13-
// TODO:
14-
// - 아티스트 좋아요(팔로우) 상태를 서버에서 함께 내려받아 초기 상태로 관리해야 함
15-
// - 좋아요 상태에 따라 하트 아이콘을 빨간색(fill-red-500)으로 표시
16-
// - 이미 좋아요된 상태에서 버튼 클릭 시 좋아요 취소(unlike) API 호출하도록 분기 처리
17-
// - 좋아요/취소 시 optimistic update 적용 검토
3+
import ArtistListCard from "@/components/artist/list/ArtistListCard";
184

195
export default function ArtistListItems({ artists }: { artists: ArtistListItem[] }) {
20-
// TODO:
21-
// - 현재는 likeArtist만 호출 중
22-
// - 추후 isLiked 상태에 따라
23-
// - true -> unlikeArtist(id) 호출
24-
// - false -> likeArtist(id) 호출
25-
// - 서버에서 내려주는 msg에 따라 toast 메시지 분기 처리
26-
const likeArtistAction = async (id: number) => {
27-
try {
28-
// TODO: isLiked 상태에 따라 like / unlike 분기 필요
29-
await likeArtist(id);
30-
toast.success("아티스트를 좋아요 했어요!");
31-
} catch (err) {
32-
if (err instanceof Error) {
33-
toast.error(err.message);
34-
} else {
35-
toast.error("알 수 없는 오류가 발생했습니다.");
36-
}
37-
}
38-
};
39-
40-
const handleLikeClick = async (e: React.MouseEvent<HTMLButtonElement>, id: number) => {
41-
e.preventDefault();
42-
e.stopPropagation();
43-
44-
// TODO:
45-
// - 중복 클릭 방지를 위한 loading 상태 추가
46-
// - 요청 중에는 버튼 disabled 처리
47-
await likeArtistAction(id);
48-
};
49-
506
return (
517
<div className="grid gap-x-8 gap-y-12 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5">
528
{artists.map((artist) => (
53-
<Link
54-
key={artist.id}
55-
href={`/artists/${artist.id}`}
56-
className="group flex flex-col gap-5 transition hover:opacity-90"
57-
>
58-
<div className="border-border/60 relative aspect-square overflow-hidden rounded-lg border">
59-
<Image
60-
src={artist.imageUrl || "/images/artist-placeholder.png"}
61-
alt="Concert Poster"
62-
fill
63-
sizes="(min-width: 1024px) 20vw, (min-width: 768px) 25vw, 50vw"
64-
className="object-cover"
65-
/>
66-
<Button
67-
onClick={(e) => handleLikeClick(e, artist.id)}
68-
type="button"
69-
aria-label="아티스트 좋아요"
70-
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"
71-
>
72-
<Heart
73-
className={twMerge(
74-
"h-5 w-5 transition",
75-
// TODO: isLiked === true 일 때 fill-red-500 text-red-500 적용
76-
// TODO: isLiked === false 일 때 fill-white text-white 적용
77-
"fill-white text-white"
78-
)}
79-
strokeWidth={0}
80-
/>
81-
</Button>
82-
</div>
83-
<strong className="line-clamp-1 text-2xl">{artist.artistName}</strong>
84-
</Link>
9+
<ArtistListCard key={artist.id} artist={artist} />
8510
))}
8611
</div>
8712
);

src/types/artists/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export type ArtistListResponse = {
88
export type ArtistListItem = {
99
id: number;
1010
artistName: string;
11-
genreName: string;
11+
genreName: string; // 필요 x
1212
imageUrl: string;
13-
likeCount: number;
13+
likeCount: number; // 필요 x
1414
};

0 commit comments

Comments
 (0)