+22
-4
apps/web/src/components/SongCover/InteractionBar/InteractionBar.tsx
+22
-4
apps/web/src/components/SongCover/InteractionBar/InteractionBar.tsx
···
1
1
import HeartOutline from "../../Icons/HeartOutline";
2
2
import HeartFilled from "../../Icons/Heart";
3
3
4
-
function InteractionBar() {
4
+
export interface InteractionBarProps {
5
+
likesCount: number;
6
+
liked: boolean;
7
+
onLike: () => void;
8
+
}
9
+
10
+
function InteractionBar({ likesCount, liked, onLike }: InteractionBarProps) {
5
11
return (
6
12
<div className="absolute bottom-[-1px] left-0 h-[100px] w-full bg-[linear-gradient(rgba(22,24,35,0)_2.92%,rgba(22,24,35,0.5)_98.99%)] flex justify-start items-end p-[10px] rounded-b-[8px]">
7
13
<div className="h-[40px] w-full flex items-center">
8
-
<span className="cursor-pointer" onClick={(e) => e.preventDefault()}>
9
-
{true && <HeartOutline color="#fff" />}
10
-
{false && <HeartFilled color="#fff" />}
14
+
<span
15
+
className="cursor-pointer"
16
+
onClick={(e) => {
17
+
e.stopPropagation();
18
+
e.preventDefault();
19
+
onLike();
20
+
}}
21
+
>
22
+
{!liked && <HeartOutline color="#fff" />}
23
+
{liked && <HeartFilled color="#fff" />}
11
24
</span>
25
+
{likesCount > 0 && (
26
+
<span className="ml-[5px] mt-[-4px] text-sm text-white">
27
+
{likesCount}
28
+
</span>
29
+
)}
12
30
</div>
13
31
</div>
14
32
);
apps/web/src/components/SongCover/InteractionBar/index.tsx
apps/web/src/components/SongCover/InteractionBar/index.tsx
This file has not been changed.
+44
-3
apps/web/src/components/SongCover/SongCover.tsx
+44
-3
apps/web/src/components/SongCover/SongCover.tsx
···
1
1
import { css } from "@emotion/react";
2
2
import styled from "@emotion/styled";
3
3
import InteractionBar from "./InteractionBar";
4
+
import useLike from "../../hooks/useLike";
5
+
import SignInModal from "../SignInModal";
6
+
import { useState } from "react";
4
7
5
8
const Cover = styled.img<{ size?: number }>`
6
9
border-radius: 8px;
···
51
54
52
55
export type SongCoverProps = {
53
56
cover: string;
57
+
uri?: string;
54
58
title?: string;
55
59
artist?: string;
56
60
size?: number;
61
+
liked?: boolean;
62
+
likesCount?: number;
57
63
withLikeButton?: boolean;
58
64
};
59
65
60
66
function SongCover(props: SongCoverProps) {
61
-
const { title, artist, cover, size, withLikeButton } = props;
67
+
const [isSignInOpen, setIsSignInOpen] = useState(false);
68
+
const [liked, setLiked] = useState(props.liked);
69
+
const { like, unlike } = useLike();
70
+
const [likesCount, setLikesCount] = useState(props.likesCount);
71
+
const { title, artist, cover, size, uri, withLikeButton } = props;
72
+
const handleLike = async () => {
73
+
if (!uri) return;
74
+
if (!localStorage.getItem("token")) {
75
+
setIsSignInOpen(true);
76
+
return;
77
+
}
78
+
if (liked) {
79
+
setLiked(false);
80
+
if (likesCount !== undefined && likesCount > 0) {
81
+
setLikesCount(likesCount - 1);
82
+
}
83
+
await unlike(uri);
84
+
} else {
85
+
setLiked(true);
86
+
if (likesCount !== undefined) {
87
+
setLikesCount(likesCount + 1);
88
+
}
89
+
await like(uri);
90
+
}
91
+
};
62
92
return (
63
-
<CoverWrapper>
93
+
<CoverWrapper onClick={(e) => e.stopPropagation()}>
64
94
<div className={`relative h-[100%] w-[92%]`}>
65
-
{withLikeButton && <InteractionBar />}
95
+
{withLikeButton && (
96
+
<InteractionBar
97
+
liked={!!liked}
98
+
likesCount={likesCount || 0}
99
+
onLike={handleLike}
100
+
/>
101
+
)}
66
102
<Cover src={cover} size={size} />
67
103
</div>
68
104
<div className="mb-[13px] mt-[10px]">
···
71
107
</SongTitle>
72
108
<Artist>{artist}</Artist>
73
109
</div>
110
+
<SignInModal
111
+
isOpen={isSignInOpen}
112
+
onClose={() => setIsSignInOpen(false)}
113
+
like
114
+
/>
74
115
</CoverWrapper>
75
116
);
76
117
}
+3
apps/web/src/pages/home/feed/Feed.tsx
+3
apps/web/src/pages/home/feed/Feed.tsx
···
126
126
className="no-underline text-[var(--color-text-primary)]"
127
127
>
128
128
<SongCover
129
+
uri={song.trackUri}
129
130
cover={song.cover}
130
131
artist={song.artist}
131
132
title={song.title}
133
+
liked={song.liked}
134
+
likesCount={song.likesCount}
132
135
withLikeButton
133
136
/>
134
137
</Link>
+3
apps/web/src/api/feed.ts
+3
apps/web/src/api/feed.ts
···
79
79
uri: string;
80
80
albumUri: string;
81
81
artistUri: string;
82
+
trackUri: string;
82
83
xataVersion: number;
83
84
cover: string;
84
85
date: string;
···
86
87
userDisplayName: string;
87
88
userAvatar: string;
88
89
tags: string[];
90
+
likesCount: number;
91
+
liked: boolean;
89
92
id: string;
90
93
};
91
94
}[];
+5
-2
apps/web/src/components/SignInModal/SignInModal.tsx
+5
-2
apps/web/src/components/SignInModal/SignInModal.tsx
···
7
7
interface SignInModalProps {
8
8
isOpen: boolean;
9
9
onClose: () => void;
10
+
like?: boolean;
10
11
}
11
12
12
13
function SignInModal(props: SignInModalProps) {
13
-
const { isOpen, onClose } = props;
14
+
const { isOpen, onClose, like } = props;
14
15
const [handle, setHandle] = useState("");
15
16
16
17
const onLogin = async () => {
···
55
56
<ModalBody style={{ padding: 10 }}>
56
57
<h1 style={{ color: "#ff2876", textAlign: "center" }}>Rocksky</h1>
57
58
<p className="text-[var(--color-text)] text-[18px] mt-[40px] mb-[20px]">
58
-
Sign in or create your account to join the conversation!
59
+
{!like
60
+
? "Sign in or create your account to join the conversation!"
61
+
: "Sign in or create your account to like songs!"}
59
62
</p>
60
63
<div style={{ marginBottom: 20 }}>
61
64
<div style={{ marginBottom: 15 }}>
Submissions
4 commits
expand
collapse
feat: add InteractionBar component and integrate with SongCover for like functionality
work in progress
Add likes support to feed and SongCover
Update feed API types to include trackUri, likesCount and liked Show
like button and counts in InteractionBar and expose onLike handler Use
useLike in SongCover to call like/unlike and pass uri/liked/likesCount
Forward like state from Feed to SongCover
Prompt sign-in for likes and stop click propagation
Show SignInModal (with a `like` flag) when a user attempts to like
without a token. Add optimistic local state for liked and likesCount to
update UI immediately. Stop event propagation on the like button and
cover to prevent parent click handlers.
pull request successfully merged