• ᴥ •つ TOP

ogimage

Project Idea

This project was inspired by Receiptify, an app that allows you to visualize your top tracks from the Spotify app based on your previous listening history. I've been using this every month to see my monthly top tracks.

receiptify

One day, I was thinking about a side project to do using React. I came across Spotify's web API and decided to create my own version of this application but much simpler.

The initial name for the project was Trendify. However, I changed it to YourTrack because the domain with Trendify in Vercel was already taken :(

Technical Stack

I used React and Vite for the frontend, and SCSS for styling. While state management wasn't necessary for this small application, I might consider Jotai or Recoil in the future if I find the need for further optimization due to their simplicity.

I chose to use JavaScript instead of TypeScript because I didn't think this application was complex enough to benefit significantly from TypeScript. Nevertheless, I did use prop-types to ensure components receive proper data.

The application allows users to download the top 10 tracks as an image. For this feature, html2canvas was utilized.

Features

Spotify Integration

I implemented the Authorization Code with PKCE flow to authenticate users and obtain the access token. This access token is then utilized to make API calls to Spotify.

yourtrack-spotiy-auth

To provide a brief overview of PKCE, it stands for Proof Key for Code Exchange—an authorization code extension designed to prevent CSRF and authorization code injection attacks. While it was originally employed for mobile devices, it has become the recommended approach for applications unable to securely store client secrets within their codebase. For more in-depth information on the PKCE extension and implementation details, please refer to my blog post: PKCE Authorization Code Flow

30-Second Track Preview

yourtrack-track-prievew

Upon reviewing the documentation, I discovered that the returned JSON object includes a preview_url field containing a link to a 30-second audio preview file. Motivated by this, I decided to implement a feature enabling users to click on a track and listen to its preview.

The following API was employed to fetch the preview link of a selected track using its id.

export async function fetchAudioPreview(id, token) { const req = request(token); const AUDIO_PREVIEW_URL = `https://api.spotify.com/v1/tracks/${id}`; const result = await fetch(AUDIO_PREVIEW_URL, req); return await result.json(); }

I implemented a logic which allows users to click on a different track while one is playing or click on the same track to stop the audio preview.

Customizable Themes

The application offers users a variety of themes to choose from. Currently, there are six predefined themes; however, I am contemplating the addition of a color picker to allow for a more extensive range of customization.

As users select different themes, the entire color scheme of the app undergoes a transformation. This includes the color of the radio selection, download button, and the background color on the track's ranking when the preview audio for that track is playing.

yourtrack-theme

Download as Image

Last but not least, users have the option to download the top 10 tracks as an image. To facilitate this process, html2canvas was used to convert the component into a canvas, which can then be downloaded as an image.

Initially, there were issues with images not loading correctly upon download, primarily due to CORS problems. However, this was resolved by including a specific flag when calling html2canvas.

export const exportAsImage = async (el, imageFileName) => { const canvas = await html2canvas(el, { scale: 2, useCORS: true, }); const image = canvas.toDataURL('image/png', 1.0); downloadImage(image, imageFileName); };

Below is an example of the downloaded image.

yourtrack-download

There was a minor setback with html2canvas when downloading the component as an image, particularly when dealing with overflowed text that was hidden with ellipsis in the design. Unfortunately, the ellipsis were omitted upon download, resulting in a cut-off of the text.

Despite the ongoing issues reported on GitHub (#324 - closed, #2262 - open), a solution was manually crafted. The approach involves calculating the precise number of characters in a track title that fit within the width. During the download, an ellipsis '...' is appended. Once the image is downloaded, any truncated texts are replaced back with their original titles.

While this is not an ideal solution, simplicity guided the choice of this workaround.

export const exportAsImage = async (tracksList, el, imageFileName) => { const MAX_CHAR = 26; const originalTitle = []; // checks for the overflowed title [...tracksList.children].forEach((list) => { const songTitle = list.querySelector('.song-title'); if (songTitle.scrollWidth > songTitle.clientWidth) { originalTitle.push({ idx: songTitle.textContent }); songTitle.textContent = songTitle.textContent.substr(0, MAX_CHAR) + '...'; } }); const canvas = await html2canvas(el, { scale: 2, useCORS: true, }); const image = canvas.toDataURL('image/png', 1.0); downloadImage(tracksList, image, imageFileName); // replace back with their original title originalTitle.map((idx, text) => { tracksList.children[idx].querySelector('.song-title').textContent = text; }); };

Retrospect

Good Job 👍🏼

  • Successfully implementing the PKCE authorization code flow.
  • Completed all the planned features.
  • Used localStorage for preview audio, reducing the number of API calls.
  • Refactored the code, potentially enhancing maintainability and readability (even if it's challenging to quantify as a solo project).

But 🤔

  • Acknowledged the application's minimalistic nature.
  • Consider additional features, such as extending the time range (half a year, all time) and utilizing more data from the API.
  • The possibility of refining the design further. The plain white background could have benefited from more initial design consideration.
← prev postnext post →