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

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.com and committed by tangled.org 44d7ef3a 4bcc5391

+30 -5
+5 -2
apps/web/src/components/SignInModal/SignInModal.tsx
··· 7 interface SignInModalProps { 8 isOpen: boolean; 9 onClose: () => void; 10 } 11 12 function SignInModal(props: SignInModalProps) { 13 - const { isOpen, onClose } = props; 14 const [handle, setHandle] = useState(""); 15 16 const onLogin = async () => { ··· 55 <ModalBody style={{ padding: 10 }}> 56 <h1 style={{ color: "#ff2876", textAlign: "center" }}>Rocksky</h1> 57 <p className="text-[var(--color-text)] text-[18px] mt-[40px] mb-[20px]"> 58 - Sign in or create your account to join the conversation! 59 </p> 60 <div style={{ marginBottom: 20 }}> 61 <div style={{ marginBottom: 15 }}>
··· 7 interface SignInModalProps { 8 isOpen: boolean; 9 onClose: () => void; 10 + like?: boolean; 11 } 12 13 function SignInModal(props: SignInModalProps) { 14 + const { isOpen, onClose, like } = props; 15 const [handle, setHandle] = useState(""); 16 17 const onLogin = async () => { ··· 56 <ModalBody style={{ padding: 10 }}> 57 <h1 style={{ color: "#ff2876", textAlign: "center" }}>Rocksky</h1> 58 <p className="text-[var(--color-text)] text-[18px] mt-[40px] mb-[20px]"> 59 + {!like 60 + ? "Sign in or create your account to join the conversation!" 61 + : "Sign in or create your account to like songs!"} 62 </p> 63 <div style={{ marginBottom: 20 }}> 64 <div style={{ marginBottom: 15 }}>
+1
apps/web/src/components/SongCover/InteractionBar/InteractionBar.tsx
··· 14 <span 15 className="cursor-pointer" 16 onClick={(e) => { 17 e.preventDefault(); 18 onLike(); 19 }}
··· 14 <span 15 className="cursor-pointer" 16 onClick={(e) => { 17 + e.stopPropagation(); 18 e.preventDefault(); 19 onLike(); 20 }}
+24 -3
apps/web/src/components/SongCover/SongCover.tsx
··· 2 import styled from "@emotion/styled"; 3 import InteractionBar from "./InteractionBar"; 4 import useLike from "../../hooks/useLike"; 5 6 const Cover = styled.img<{ size?: number }>` 7 border-radius: 8px; ··· 62 }; 63 64 function SongCover(props: SongCoverProps) { 65 const { like, unlike } = useLike(); 66 - const { title, artist, cover, size, liked, likesCount, uri, withLikeButton } = 67 - props; 68 const handleLike = async () => { 69 if (!uri) return; 70 if (liked) { 71 await unlike(uri); 72 } else { 73 await like(uri); 74 } 75 }; 76 return ( 77 - <CoverWrapper> 78 <div className={`relative h-[100%] w-[92%]`}> 79 {withLikeButton && ( 80 <InteractionBar ··· 91 </SongTitle> 92 <Artist>{artist}</Artist> 93 </div> 94 </CoverWrapper> 95 ); 96 }
··· 2 import styled from "@emotion/styled"; 3 import InteractionBar from "./InteractionBar"; 4 import useLike from "../../hooks/useLike"; 5 + import SignInModal from "../SignInModal"; 6 + import { useState } from "react"; 7 8 const Cover = styled.img<{ size?: number }>` 9 border-radius: 8px; ··· 64 }; 65 66 function SongCover(props: SongCoverProps) { 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 }; 92 return ( 93 + <CoverWrapper onClick={(e) => e.stopPropagation()}> 94 <div className={`relative h-[100%] w-[92%]`}> 95 {withLikeButton && ( 96 <InteractionBar ··· 107 </SongTitle> 108 <Artist>{artist}</Artist> 109 </div> 110 + <SignInModal 111 + isOpen={isSignInOpen} 112 + onClose={() => setIsSignInOpen(false)} 113 + like 114 + /> 115 </CoverWrapper> 116 ); 117 }