Skip to content
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

Music Release Project #105

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
<h1 align="center">
<a href="">
<img src="/src/assets/music-releases.svg" alt="Project Banner Image">
</a>
</h1>

# Music Releases
This project is a web application designed to display a collection of albums, with additional features like album details, a play button to launch albums in Spotify, and a sidebar showcasing editor’s picks. It pulls data from a Spotify API and uses that information to render album covers, names, and artist details. The app is structured using React components, each serving a specific purpose to ensure modularity and reusability.

Replace this readme with your own information about your project.
# Key Features
Album List:
The main section of the app displays a list of albums fetched from the Spotify API. Each album card contains:

Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
Album Cover: Displays the album’s artwork.

## Getting Started with the Project
Album Name: Links to the album's Spotify page.

### Dependency Installation & Startup Development Server
Artist Name: Shows the name of the artist(s) associated with the album.

Once cloned, navigate to the project's root directory and this project uses npm (Node Package Manager) to manage its dependencies.
Play Button: Opens the album's Spotify page when clicked, enabling users to listen to the album directly in Spotify.

The command below is a combination of installing dependencies, opening up the project on VS Code and it will run a development server on your terminal.
Sidebar with Editor’s Picks: Displays a list of curated playlists called "Editor's Picks."

```bash
npm i && code . && npm run dev
```
# Tech Stack
React: Used to create reusable components and manage application state.

### The Problem
PropTypes: For type-checking component props to ensure the correct data is passed to components.

Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next?
Spotify API: To fetch album data (album details, cover images, artist names, etc.).
CSS: Custom styling to match the given design, ensuring that the album cards, play buttons, and sidebar look cohesive and visually appealing.

### View it live
# Challenges & Solutions
Design Consistency: One of the main challenges in this project was adhering to the design specifications provided. To address this, I followed a structured approach:

I built reusable components to maintain consistency in the layout and UI elements (e.g., album card components, play button, sidebar components).
The app’s CSS was structured in a way that each component was easily styled, and custom media queries were used for responsive design.

Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
API Data Handling: Handling asynchronous data fetching from the Spotify API required ensuring that the album data was properly loaded before rendering the components. I used the map() function to iterate through the album and playlist data and render the appropriate content.

## Instructions
# Future Enhancements
Album Search: Implement a search feature that allows users to search for albums by name, artist, or genre.

<a href="instructions.md">
See instructions of this project
</a>
Album Details Page: Expand the functionality by adding an album details page that includes track listings, release date, and additional information.

User Authentication: Integrate Spotify’s OAuth to allow users to log in and personalize their album lists.

### View it live
https://gittes-music-release.netlify.app
24 changes: 13 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Music Releases</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Music Releases</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>

</html>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
12 changes: 7 additions & 5 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import data from "./data.json";

console.log(data);
import { Album } from "./components/Album";

export const App = () => {
return <div>Find me in src/app.jsx!</div>;
};
return (
<>
<Album />
</>
)
}
32 changes: 32 additions & 0 deletions src/components/Album.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { albums } from "../data.json";
import { AlbumName } from "./AlbumName";
import { ArtistName } from "./ArtistName";
import { CoverImage } from "./CoverImage";
import { Header } from "./Header";
import { Sidebar } from "./Sidebar";

// Album
export const Album = () => {
return (
<>
<Header />
<div className="main-container">
<div className="album-container">
{albums.items.map((album) => (
<div key={album.id} className="album-card">
<CoverImage
coverImg={album.images.length > 0 ? album.images[0].url : "fallback-image-url.jpg"}
albumUrl={album.external_urls.spotify}
/>
<div className="album-info">
<AlbumName name={album.name} albumUrl={album.external_urls.spotify} />
<ArtistName artists={album.artists} artistsUrl={album.external_urls.spotify} />
</div>
</div>
))}
</div>
<Sidebar />
</div>
</>
)
}
22 changes: 22 additions & 0 deletions src/components/AlbumName.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import PropTypes from "prop-types";

// Album Name
export const AlbumName = ({ name, albumUrl }) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good practise to only pass along the data that this component needs to know about.

return (
<div>
<a
href={albumUrl}
target="_blank"
rel="noopener noreferrer"
className="album-name-a">
<h2 className="album-name">{name}</h2>
</a>
</div>
)
}

// Define PropTypes for AlbumName
AlbumName.propTypes = {
name: PropTypes.string.isRequired,
albumUrl: PropTypes.string.isRequired
};
37 changes: 37 additions & 0 deletions src/components/ArtistName.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import PropTypes from "prop-types";

// Artist Name
export const ArtistName = ({ artists }) => {
return (
<div className="artists-container">
{artists.map((artist, index) => {
let separator = ", ";
if (artists.length === 2 && index === 0) {
separator = " & ";
} else if (index === artists.length - 2) {
separator = " & ";
} else if (index === artists.length - 1) {
separator = "";
}

return (
<span key={artist.id}>
<a href={artist.external_urls.spotify} target="_blank" rel="noopener noreferrer" className="artist-name-a">
<h3 className="artist-name">{artist.name + separator}</h3>
</a>
</span>
)
})}
</div>
)
}

ArtistName.propTypes = {
artists: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
})
).isRequired,
artistsUrl: PropTypes.string.isRequired
}
24 changes: 24 additions & 0 deletions src/components/CoverImage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import PropTypes from "prop-types";
import { DotsButton } from "./DotsButton";
import { HeartButton } from "./HeartButton";
import { PlayButton } from "./PlayButton";

export const CoverImage = ({ coverImg, albumUrl }) => {
return (
<div className="cover-image-container">
<div className="cover-image">
<img className="cover-image-img" alt="Album Cover" src={coverImg} />
<div className="icon-container">
<HeartButton />
<PlayButton albumUrl={albumUrl} />
<DotsButton />
</div>
</div>
</div>
)
}

CoverImage.propTypes = {
coverImg: PropTypes.string.isRequired,
albumUrl: PropTypes.string.isRequired,
}
11 changes: 11 additions & 0 deletions src/components/DotsButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import dotsIcon from "../assets/icons/dots.svg";

export const DotsButton = () => {
return (
<span>
<button type="button" aria-label="link to artist" className="dots-button">
<img src={dotsIcon} className="dots-icon" />
</button>
</span>
)
}
9 changes: 9 additions & 0 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const Header = () => {
return (
<header>
<h1>
New Vibes
</h1>
</header>
)
}
11 changes: 11 additions & 0 deletions src/components/HeartButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import heartIcon from "../assets/icons/heart.svg";

export const HeartButton = () => {
return (
<span className="heart-button-container">
<button type="button" aria-label="like" className="heart-button">
<img src={heartIcon} className="heart-icon" />
</button>
</span>
)
}
19 changes: 19 additions & 0 deletions src/components/PlayButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import playIcon from "../assets/icons/play.svg";
import PropTypes from "prop-types";

export const PlayButton = ({ albumUrl }) => {
const handlePlayClick = () => {
window.open(albumUrl, "_blank", "noopener,noreferrer")
}
return (
<span className="play-button-container">
<button type="button" aria-label="play" className="play-button" onClick={handlePlayClick}>
<img src={playIcon} className="play-icon" />
</button>
</span>
)
}

PlayButton.propTypes = {
albumUrl: PropTypes.string.isRequired,
}
18 changes: 18 additions & 0 deletions src/components/Sidebar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { playlists } from "../stretch-goal.json";
import { SidebarAlbum } from "./SidebarAlbum";

export const Sidebar = () => {
return (
<div className="sidebar">
<h2 className="sidebar-header">Editor's Picks</h2>
<div>
{playlists.items.map((item) => (
<SidebarAlbum
key={item.id}
sidebarImg={item.images.length > 0 ? item.images[0].url : "fallback-image-url.jpg"}
/>
))}
</div>
</div>
)
}
17 changes: 17 additions & 0 deletions src/components/SidebarAlbum.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import PropTypes from "prop-types";

export const SidebarAlbum = ({ sidebarImg }) => {
return (
<>
<section className="sidebar-album-container">
<div className="cover-image-sidebar">
<img className="cover-image-sidebar-img" alt="Single Cover" src={sidebarImg} />
</div>
</section>
</>
)
}

SidebarAlbum.propTypes = {
sidebarImg: PropTypes.string.isRequired,
}
Loading