# Project export: Rate-ify

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 10.0
- Tagline: Everyone thinks that they have great music taste, but do we really? Rate-ify is an app to allow your playlists to fiercely compete in the marketplace of ideas, to put an end to that age old question.
- Devpost: https://devpost.com/software/rate-ify
- GitHub: https://github.com/cusianovic/SpotifyPlaylistRanker-frontend
- Demo: https://github.com/cusianovic/SpotifyPlaylistRanker
- Team: 3 GitHub contributor(s) — cusianovic (14 commits), Austin Tung (14 commits), chrisrq (11 commits)

## Devpost submission (written by the team)

### Inspiration

When we got together for the first time, we instantly gravitated towards project ideas that would appeal to a broad audience, so music as a theme for our project was a very natural choice. Originally, our ideas around a music-based project were much more abstract, incorporating some notes of music perception. We eventually realized that there were too many logistical hurdles surrounding each that they would not be feasible to do during the course of the hackathon, and we realized this as we were starting to brainstorm ideas for social media apps. We started thinking of ideas for music-based social media, and that's when we came up with the idea of making an app where people would judge other's music tastes in a lighthearted fashion.

### What it does

The concept of Rate-ify is simple; users post their Spotify playlists and write a little bit about them for context. Users can also view playlists that other people have posted, have a listen to them, and then either upvote or downvote the playlist based on their enjoyment. Finally, users can stay up to date on the most popular playlists through the website's leaderboard, which ranks all playlists that have been posted to the site.

### How we built it

and what we learned Our team learned more about tooling surrounding web dev. We had a great opportunity to practice frontend development using React and Figma, learning practices that we will likely be using in future projects. Some members were additionally introduced to tools that they had never used before this hackathon, such as databases.

### Challenges we ran into

Probably the biggest challenge of the hackathon was debugging the frontend. Our team came from a limited background, so being able to figure out how to successfully send data from the backend to the frontend could sometimes be a hassle. The quintessential example of this was when we were working on the leaderboard feature. Though the server was correctly returning ranking data, we had lots of trouble getting the frontend to successfully receive the data so that we could display it, and part of this was because of the server returning ranking data as a promise. After figuring out how to correctly return the ranking data without promises, we then had trouble storing that data as part of a React component, which was fixed by using effect hooks.

### Accomplishments we're proud of

For having done limited work on frontend for past projects, we ended up very happy with how the UI came out. It's a very simple and charming looking UI.

### What's next

There were certainly some features that we wanted to include that we didn't end up working on, such as a mode of the app where you would see two playlists and say which one you would prefer and a way of allowing users to identify their preferred genres so that we could categorize the number of upvotes and downvotes of playlists based on the favorite genres of the users who rated them. If we do continue working on Rate-ify, then there are definitely more ways than one that we could refine and expand upon the basic premise that we've developed over the course of the last two days, so that would be something that we should consider.

## README (from the GitHub repository)

# Rate-ify. 


## Detected evidence (automated analysis)

Indexed codebase: 16 recognized source files, 23 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- Express (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- PostgreSQL (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (20 of 20)

```
.gitignore
package.json
public/index.html
public/manifest.json
public/robots.txt
README.md
src/App.css
src/App.js
src/EmbedTest.js
src/index.css
src/index.js
src/PageHeader.js
src/pages/HomePage.js
src/pages/index.js
src/pages/LeaderboardViewer.js
src/pages/LoginPage.js
src/pages/PostPopup.js
src/pages/PostViewer.js
src/PageTitle.js
src/util/auth.js
```

### Dependencies

- package.json: @auth0/auth0-react@^2.2.1, @canvasjs/react-charts@^1.0.0, @testing-library/jest-dom@^5.17.0, @testing-library/react@^13.4.0, @testing-library/user-event@^13.5.0, cookies@^0.8.0, react@^18.2.0, react-dom@^18.2.0, react-router-dom@^6.17.0, react-scripts@5.0.1, react-spotify-embed@^2.0.3, web-vitals@^2.1.4

### Recent commits (newest first)

- Update README.md
- cool!
- Revert "Merge branch 'main' of https://github.com/cusianovic/SpotifyPlaylistRanker-frontend"
- Merge branch 'main' of https://github.com/cusianovic/SpotifyPlaylistRanker-frontend
- fixed login
- updates
- finished voting
- converted post popup to actual window
- converted post popup to actual window
- the low hanging fruit
- the low hanging fruit
- Merge branch 'main' of https://github.com/cusianovic/SpotifyPlaylistRanker-frontend into seb
- hkjhjg
- blah blah
- yeah
- catchup
- fixing git
- mvp
- mvp
- playlist rank screen pt2

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "spotifyplaylistranker-frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@auth0/auth0-react": "^2.2.1",
    "@canvasjs/react-charts": "^1.0.0",
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "cookies": "^0.8.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.17.0",
    "react-scripts": "5.0.1",
    "react-spotify-embed": "^2.0.3",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

```

### src/index.js

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { Auth0Provider } from '@auth0/auth0-react';
import './index.css';
import App from './App';


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <Auth0Provider
      domain="dev-43dibfgl0wzgvp8x.us.auth0.com"
      clientId="ywUJMUpwwehEWs1yO3smHGhGgehkEFhg"
      authorizationParams={{
        redirect_uri: window.location.origin
      }}
      cacheLocation="localstorage"
    >
        <App/>
      </Auth0Provider>
    </BrowserRouter>
  </React.StrictMode>
);

```

### src/App.js

```javascript
import {Routes, Route} from 'react-router-dom'
import React from 'react';
import PageHeader from "./PageHeader";
import Login from "./pages/index"
import './App.css';
import HomePage from "./pages/HomePage";
import LeaderboardViewer from "./pages/LeaderboardViewer";
import PostPopup from "./pages/PostPopup";
import PostViewer from "./pages/PostViewer";
import Auth from "./util/auth"
import LoginPage from "./pages/LoginPage"
function App() {
    return (
        <Routes>
            <Route path='/' element={<Login/>}/>
            <Route path="/leaderboard" element={<LeaderboardViewer/>}/>
            <Route path="/post" element={<Auth component={<PostPopup/>}/>}/>
            <Route path="/discover" element={<Auth component={<PostViewer/>}/>}/>
            <Route path = "/base" element={<LoginPage></LoginPage>}/>
        </Routes>
    );
}

export default App;


```

### src/pages/index.js

```javascript
import React from 'react';
import { useAuth0 } from "@auth0/auth0-react";
import LoginPage from "./LoginPage"
import HomePage from "./HomePage";

const LoginButton = () => {
  const { loginWithRedirect } = useAuth0();

  return <button onClick={() => loginWithRedirect()}>Log In</button>;
};

const LogoutButton = () => {
  const { logout } = useAuth0();

  return (
    <button onClick={() => logout({ logoutParams: { returnTo: window.location.origin } })}>
      Log Out
    </button>
  );
};

const Index = () => {

    const { isAuthenticated, isLoading } = useAuth0();

    if(isLoading) return(<></>)
    if(!isAuthenticated) return(<LoginPage/>);
    return(<HomePage/>)
}
export {LogoutButton, LoginButton}
export default Index
```

### src/PageTitle.js

```javascript
function PageTitle(props) {
    return <h1 className="page-title">{props.text}</h1>
}

export default PageTitle;
```

### src/EmbedTest.js

```javascript
function EmbedTest() {
    return (
        <iframe style={{ borderRadius: '12px' }} src="https://open.spotify.com/embed/playlist/5GmAQRdgdWIuNvpGHqmZBo?utm_source=generator" width="100%" height="352" frameBorder="0" allowFullScreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>
    );
}

export default EmbedTest;
```

### src/index.css

```css
body {
  margin: 0;
  width: 100vw;
  height: 100vh;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background-color: #c9a992;
}

div {
  width: 100vw;
  height: 100vh;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

```

### src/PageHeader.js

```javascript
import dice from './pages/icons/dice.png';
import plus from './pages/icons/plus.png';
import playlist from './pages/icons/playlist.png'
import leaderboard from './pages/icons/leaderboard.png'
import {useNavigate} from "react-router";
import exit from './pages/icons/exit.png';
import { useAuth0 } from "@auth0/auth0-react";


function PageHeader() {
    const navigate = useNavigate();
    return(
        <div className="page-header">
            <h1 id="app-name" onClick={() => navigate("/")}>RATE-IFY</h1>
            <CircularButton id="leaderboard" src={leaderboard} onClick={()=>{ navigate("/leaderboard") }}></CircularButton>
            <CircularButton id="post" src={playlist} onClick={() => { navigate("/post") }}></CircularButton>
            <CircularButton id="discover" src={dice} onClick={() => { navigate("/discover") } }></CircularButton>
            <CircularLogOut id="logout" src={exit}></CircularLogOut>
        </div>
    )
}

function CircularButton(props) {
    return (
        <button className="circular-button" id={props.id} onClick={props.onClick}>
            <img className="button-icon" src={props.src}></img>
        </button>
    );
}

function CircularLogOut(props) {

    const { logout } = useAuth0();
    return (
        <button className="circular-button" id = {props.id} 
        onClick={() => logout({ logoutParams: { returnTo: window.location.origin } })}>
            <img className="button-icon" src={props.src}></img>
            <text>{props.text}</text>
        </button>
    );
}

export default PageHeader;
```

### public/index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Rate-ify"
    />
    <style>
      @import url("https://fonts.googleapis.com/css?family=Rokkitt:500|Montserrat:400,500,700,600|Inter:400,600");
    </style>
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>Rate-ify</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

```

### src/App.css

```css
.page-title {
    color: #000000;
    font-size: 54px;
    font-weight: 500;
    left: 5vw;
    letter-spacing: 0.25px;
    line-height: normal;
    position: absolute;
    top: 5vh;
    width: 655px;
}

.bordered-elem {
    border: 3px solid;
    border-color: #000000;
    border-radius: 2vw;
}


#playlist-name {
    height: 10vh;
    left: 5vw;
    position: absolute;
    top: 25vh;
    width: 20vw;
    padding-left: 20px;
    padding-right: 20px;
}


#url {
    height: 10vh;
    left: 35vw;
    position: absolute;
    top: 25vh;
    width: 56.5vw;
    padding-left: 20px;
    padding-right: 20px;
}

#description {
    height: 30vh;
    left: 5vw;
    position: absolute;
    top: 40vh;
    width: 86vw;
    padding: 2vw;
}

#submit-button {
    background-size: 100% 100%;
    height: 10vh;
    left: 35vw;
    position: absolute;
    top: 85vh;
    width: 30vw;
    background-color: #97B69E;
    font-size: 24px;
}

#url, #description, #playlist-name {
    font-size: 20px;
    font-weight: 500;
    letter-spacing: 0.25px;
    line-height: normal;
    text-align: left;
    background-color: rgb(232, 209, 180);
}

#url, #description, #submit-button, .page-title, #playlist-name {
    font-family: "Rokkitt", Helvetica;
}

.circular-button {
    border: 1.5px solid;
    border-color: #000000;
    border-radius: 1vw;
    mix-blend-mode: multiply;
    position: absolute;
    height: 4vw;
    width: 4vw;
    display: flex;
    justify-content: center;
}

h1 {
    font-family: "Rokkitt", Helvetica;
}

#app-name {
    left: 2vw;
    letter-spacing: 0.25px;
    line-height: normal;
    position: absolute;
    top: 1vh;
    font-size: 30px;
}

#logout {
    left: 78vw;
    top: 4vh;
    position: absolute;
}

#leaderboard {
    left: 83vw;
    top: 4vh;
    position: absolute;
}

#post {
    left: 88vw;
    top: 4vh;
    position: absolute;
}

#discover {
    left: 93vw;
    top: 4vh;
    position: absolute;
}

.not_round_button_icon {
    position: absolute;
    width: 3vh;
    height: 3vh;
    bottom: 25%;
    left: 2vh;
}

.button-icon {
    position: absolute;
    width: 50%;
    height: 50%;
    bottom: 25%;
    left: 25%;
}

.rounded_button {
    border: 2px solid;
    border-color: #000000;
    border-radius: 1vw;
    mix-blend-mode: multiply;
    position: absolute;
    height: 4vw;
    width: 40vw;
    font-size: 20px;
    font-weight: 500;
    letter-spacing: 0.25px;
    line-height: 25%;
    text-align: center;
    font-family: "Rokkitt", "Helvetica";
    scale: 0.9;
}

#home_leaderboard {
    left: 30vw;
    top: 50vh;
    position: absolute
}

#home_post {
    left: 30vw;
    top: 58vh;
    position: absolute

}

#home_random {
    left: 30vw;
    top: 66vh;
    position: absolute
}

#home_logout {
    left: 30vw;
    top: 74vh;
    position: absolute
}

#BIG_HOME_APP_NAME {
    margin: auto;
    width: 100%;
    text-align: center;
    color: #000000;
    font-size: 120px;
    font-weight: 500;
    top: 24vh;
    letter-spacing: 1px;
    line-height: normal;
    position: absolute;
    font-family: "Rokkitt", "Helvetica";
}

#upvote {
    height: 3vh;
    width: 3vw;
    left: 24vw;
    position: absolute;
    top: 66.3vh;
    padding: 3vw;
    background-color:#97B69E;
    scale: 0.7;
}

#downvote {
    height: 3vh;
    left: 29.5vw;
    position: absolute;
    top: 66.3vh;
    width: 3vw;
    padding: 3vw;
    background-color: #FB7F7F;
    scale: 0.7;
}

#embed-placeholder {
    height: 60vh;
    width: 60vw;
    padding-left: 1vh;
    padding-right: 1vh;
    padding-top: 1vh;
    padding-bottom: 1vh;
    position: absolute;
    left: 19vw;
    top: 10vh;
    background-color: #c9a992;
    scale: .8;
}

#post-info {
    position: absolute;
    left: 36.5vw;
    top:  68.5vh;
    font-size: 20px;
    width: 38vw;
    height: 5vh;
    font-family: "Rokkitt", "Helvetica";
}

.results {
    height: 2vh;
    width: 4vw;
    font-family: "Rokkitt", "Helvetica";
    font-size: 16px;
    text-align: center;
}

#fire-results {
    left: 25.2vw;
    top: 78vh;
    position: absolute;
    color: #97B69E;
}

#trash-results {
    left: 30.75vw;
    top: 78vh;
    position: absolute;
    stroke: #000000;
    color: #FB7F7F;

}

#try_again {
    left: 22.5vw;
    top: 87vh;
    position: absolute;
    height: 4vw;
    width: 55vw;
}

#login_button {
    left: 30vw;
    top: 58vh;
    position: absolute
}

.leaderboard {
    overflow-x: scroll;
    position:absolute;
    bottom: 7%;
    left: 15%;
    width:70%;
}
#leaderboard-columns-start {
    background-color: #A296E8;
    border-color: #A296E8;
    letter-spacing: 0.75px;
    font-size: 10px;
    height: 30px;
}
td {
    padding-left: 1%;
    font-family: "Rokkitt", Helvetica;
}
.playlist-info-0 {
    background-color: #BA99C9;
}
.playlist-info-1 {
    background-color: #BFA1C4;
}
.playlist-info-0, .playlist-info-1 {
    height:30px;
}








```

[6 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]