-
Notifications
You must be signed in to change notification settings - Fork 95
feature-itunes-ticket-NA #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,101 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React, { useState, useRef } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import PropTypes from 'prop-types'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Card, CardContent, CardMedia, Typography, IconButton, Box } from '@mui/material'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { PlayArrow, Pause } from '@mui/icons-material'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import styled from '@emotion/styled'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const StyledCard = styled(Card)` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position: relative; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: 100%; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition: transform 0.2s ease-in-out; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cursor: pointer; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &:hover { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transform: translateY(-4px); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const PlayButton = styled(IconButton)` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position: absolute; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| right: 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bottom: 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background-color: rgba(147, 51, 234, 0.9); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &:hover { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background-color: rgba(147, 51, 234, 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ArtistText = styled(Typography)` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color: ${({ theme }) => theme.palette.text.secondary}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-size: 0.875rem; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function SongCard({ track }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isPlaying, setIsPlaying] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const audioRef = useRef(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handlePlayPause = (e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e.stopPropagation(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!audioRef.current) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| audioRef.current = new Audio(track.previewUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| audioRef.current.addEventListener('ended', () => setIsPlaying(false)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isPlaying) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| audioRef.current.pause(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| audioRef.current.play(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsPlaying(!isPlaying); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| React.useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (audioRef.current) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| audioRef.current.pause(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| audioRef.current.removeEventListener('ended', () => setIsPlaying(false)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The event listener for 'ended' added in Because these are different function instances, Could you define the event handler function in a stable way (e.g., using // Define this handler in a stable way, e.g., using useCallback
// const handleAudioEnded = useCallback(() => setIsPlaying(false), []);
// audioRef.current.removeEventListener('ended', handleAudioEnded);
// For a direct fix here, assuming you'll refactor handlePlayPause too:
// This line needs to reference the *exact same function instance* as addEventListener
// A proper fix involves refactoring how the listener is defined and passed.
// Example (conceptual, full change involves handlePlayPause):
// const onAudioEnd = audioRef.current._onAudioEndHandler; // Assuming you store it
// if (onAudioEnd) audioRef.current.removeEventListener('ended', onAudioEnd);
audioRef.current.removeEventListener('ended', () => setIsPlaying(false)); // This line is problematic |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| audioRef.current = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix audio cleanup in useEffect. The current cleanup function has a bug - the event listener removal won't work because it creates a new function reference instead of removing the original listener. function SongCard({ track }) {
const [isPlaying, setIsPlaying] = useState(false);
const audioRef = useRef(null);
+ const endedListenerRef = useRef(null);
const handlePlayPause = (e) => {
e.stopPropagation();
if (!audioRef.current) {
audioRef.current = new Audio(track.previewUrl);
- audioRef.current.addEventListener('ended', () => setIsPlaying(false));
+ endedListenerRef.current = () => setIsPlaying(false);
+ audioRef.current.addEventListener('ended', endedListenerRef.current);
}
// ... rest of function
};
React.useEffect(() => {
return () => {
if (audioRef.current) {
audioRef.current.pause();
- audioRef.current.removeEventListener('ended', () => setIsPlaying(false));
+ if (endedListenerRef.current) {
+ audioRef.current.removeEventListener('ended', endedListenerRef.current);
+ }
audioRef.current = null;
}
};
}, []);📝 Committable suggestion
Suggested change
🧰 Tools🪛 ESLint[error] 58-58: No object mutation allowed. (immutable/no-mutation) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <StyledCard> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <CardMedia | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| component="img" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height="200" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| image={track.artworkUrl100.replace('100x100bb', '400x400bb')} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alt={track.trackName} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <CardContent> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Typography variant="h6" noWrap> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {track.trackName} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Typography> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ArtistText variant="body2" noWrap> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {track.artistName} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </ArtistText> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box sx={{ mt: 1 }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Typography variant="caption" color="text.secondary"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {track.primaryGenreName} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Typography> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </CardContent> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PlayButton aria-label={isPlaying ? 'pause' : 'play'} onClick={handlePlayPause} size="medium"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isPlaying ? <Pause /> : <PlayArrow />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </PlayButton> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </StyledCard> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SongCard.propTypes = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| track: PropTypes.shape({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trackName: PropTypes.string.isRequired, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| artistName: PropTypes.string.isRequired, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| artworkUrl100: PropTypes.string.isRequired, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| previewUrl: PropTypes.string.isRequired, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| primaryGenreName: PropTypes.string.isRequired | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).isRequired | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default SongCard; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { SEARCH_TRACKS, SEARCH_TRACKS_SUCCESS, SEARCH_TRACKS_ERROR, CLEAR_TRACKS } from './constants'; | ||
|
|
||
| /** | ||
| * Search for tracks action | ||
| * @param {string} query - The search query | ||
| * @returns {object} An action object with a type of SEARCH_TRACKS | ||
| */ | ||
| export const searchTracks = (query) => ({ | ||
| type: SEARCH_TRACKS, | ||
| query | ||
| }); | ||
|
|
||
| /** | ||
| * Dispatched when the search is successful | ||
| * @param {array} tracks - The track results | ||
| * @returns {object} An action object with a type of SEARCH_TRACKS_SUCCESS | ||
| */ | ||
| export const searchTracksSuccess = (tracks) => ({ | ||
| type: SEARCH_TRACKS_SUCCESS, | ||
| tracks | ||
| }); | ||
|
|
||
| /** | ||
| * Dispatched when the search fails | ||
| * @param {object} error - The error object | ||
| * @returns {object} An action object with a type of SEARCH_TRACKS_ERROR | ||
| */ | ||
| export const searchTracksError = (error) => ({ | ||
| type: SEARCH_TRACKS_ERROR, | ||
| error | ||
| }); | ||
|
|
||
| /** | ||
| * Clear the tracks from store | ||
| * @returns {object} An action object with a type of CLEAR_TRACKS | ||
| */ | ||
| export const clearTracks = () => ({ | ||
| type: CLEAR_TRACKS | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export const SEARCH_TRACKS = 'app/ITunesSearch/SEARCH_TRACKS'; | ||
| export const SEARCH_TRACKS_SUCCESS = 'app/ITunesSearch/SEARCH_TRACKS_SUCCESS'; | ||
| export const SEARCH_TRACKS_ERROR = 'app/ITunesSearch/SEARCH_TRACKS_ERROR'; | ||
| export const CLEAR_TRACKS = 'app/ITunesSearch/CLEAR_TRACKS'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for audio playback.
The
audio.play()method returns a Promise that can be rejected if playback fails. This should be handled to prevent unhandled promise rejections.if (isPlaying) { audioRef.current.pause(); } else { - audioRef.current.play(); + audioRef.current.play().catch((error) => { + console.error('Audio playback failed:', error); + setIsPlaying(false); + }); } setIsPlaying(!isPlaying);📝 Committable suggestion
🤖 Prompt for AI Agents