A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz
98
fork

Configure Feed

Select the types of activity you want to include in your feed.

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.

authored by

Tsiry Sandratraina and committed by tangled.org 44d7ef3a 4bcc5391

+30 -5
+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 }}>
+1
apps/web/src/components/SongCover/InteractionBar/InteractionBar.tsx
··· 14 14 <span 15 15 className="cursor-pointer" 16 16 onClick={(e) => { 17 + e.stopPropagation(); 17 18 e.preventDefault(); 18 19 onLike(); 19 20 }}
+24 -3
apps/web/src/components/SongCover/SongCover.tsx
··· 2 2 import styled from "@emotion/styled"; 3 3 import InteractionBar from "./InteractionBar"; 4 4 import useLike from "../../hooks/useLike"; 5 + import SignInModal from "../SignInModal"; 6 + import { useState } from "react"; 5 7 6 8 const Cover = styled.img<{ size?: number }>` 7 9 border-radius: 8px; ··· 62 64 }; 63 65 64 66 function SongCover(props: SongCoverProps) { 67 + const [isSignInOpen, setIsSignInOpen] = useState(false); 68 + const [liked, setLiked] = useState(props.liked); 65 69 const { like, unlike } = useLike(); 66 - const { title, artist, cover, size, liked, likesCount, uri, withLikeButton } = 67 - props; 70 + const [likesCount, setLikesCount] = useState(props.likesCount); 71 + const { title, artist, cover, size, uri, withLikeButton } = props; 68 72 const handleLike = async () => { 69 73 if (!uri) return; 74 + if (!localStorage.getItem("token")) { 75 + setIsSignInOpen(true); 76 + return; 77 + } 70 78 if (liked) { 79 + setLiked(false); 80 + if (likesCount !== undefined && likesCount > 0) { 81 + setLikesCount(likesCount - 1); 82 + } 71 83 await unlike(uri); 72 84 } else { 85 + setLiked(true); 86 + if (likesCount !== undefined) { 87 + setLikesCount(likesCount + 1); 88 + } 73 89 await like(uri); 74 90 } 75 91 }; 76 92 return ( 77 - <CoverWrapper> 93 + <CoverWrapper onClick={(e) => e.stopPropagation()}> 78 94 <div className={`relative h-[100%] w-[92%]`}> 79 95 {withLikeButton && ( 80 96 <InteractionBar ··· 91 107 </SongTitle> 92 108 <Artist>{artist}</Artist> 93 109 </div> 110 + <SignInModal 111 + isOpen={isSignInOpen} 112 + onClose={() => setIsSignInOpen(false)} 113 + like 114 + /> 94 115 </CoverWrapper> 95 116 ); 96 117 }