Skip to content
Merged
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
15 changes: 11 additions & 4 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Run frontend tests
run: |
npm install --save-dev jest
npm test
run: npm test

- name: Run backend tests
run: |
Expand Down
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@ Once you have your Spotify developer account, you will need to ensure that you h
Before running the command, you will need to create an `.env` file at the root directory. The file needs to have the following:

```
CLIENT_ID=""
CLIENT_SECRET=""
STATE=""
CLIENT_ID="<spotify developer client id>"
CLIENT_SECRET="<spotify developer client secret>"
STATE="<any value>"
```

For `STATE`, this value is up to the user, but it is recommended to use an encrypted password.

Once you have followed the prerequisites and installed all the needed dependencies, simply run the following command: `go run main.go`. From there, your default browser should kick off the web application against `http://localhost:8080`.

## Running Program
To run the program, you have three options: GUI mode, interactive CLI, or command line arguments.
Once done, you should see the following login page accompanied by your unique home page:

### Testing
There is frontend and backend testing for the web application. For the backend changes, simply run the following command at the root directory: `go test ./... -v`.
![login-screen](assets/login.png)

For the frontend changes, once you have npm properly configured with Jest, simply run the following command: `npm test`.
![home-screen](assets/home.png)
Binary file added assets/favicon.ico
Binary file not shown.
Binary file added assets/home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
317 changes: 317 additions & 0 deletions assets/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
/* Global Styles */
body {
margin: 0;
padding: 0;
min-height: 100vh;
font-family: 'Montserrat', Arial, sans-serif;
background: radial-gradient(circle at top right, #2a7b49 0%, #191414 46%, #111 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

/* Typography */
h1 {
color: #fff;
font-size: 2rem;
margin-bottom: 16px;
font-weight: 700;
text-align: center;
}

/* Layout */
.powered-by {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
font-size: 1.5rem;
font-weight: 600;
letter-spacing: 1px;
margin-bottom: 0;
}

.legal-disclaimer {
margin: 10px 0 0;
padding: 0 18px;
text-align: center;
color: rgba(255, 255, 255, 0.82);
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.2px;
max-width: 680px;
}

footer {
width: 100%;
position: fixed;
left: 0;
bottom: 0;
display: flex;
justify-content: center;
background: transparent;
z-index: 10;
padding-bottom: 24px;
}

.spotify-logo {
display: none;
}

.spotify-logo-footer {
width: 300px;
max-width: 80vw;
margin-top: 16px;
display: block;
}

/* Navigation */
.tabs {
display: flex;
gap: 24px;
justify-content: center;
margin-bottom: 32px;
}

.tab-link {
color: #fff;
text-decoration: none;
font-size: 1.1rem;
font-weight: 600;
padding: 8px 20px;
border-radius: 30px;
transition: background 0.3s, color 0.3s, transform 0.2s;
}

.tab-link:hover {
background: #1DB954;
color: #191414;
transform: scale(1.08);
}

/* Home Page */
body.home-page {
--home-heading-color: #fff;
justify-content: center;
align-items: center;
padding: 0 18px 140px;
overflow-x: hidden;
}

.ambient-orb {
display: none;
}

.orb-one {
top: -80px;
left: -100px;
background: #1DB954;
}

.orb-two {
right: -110px;
bottom: 60px;
background: #95ffbe;
}

header,
.home-main {
position: relative;
z-index: 1;
}

.home-main {
width: min(760px, 100%);
margin: 16px auto 0;
display: grid;
gap: 16px;
color: #fff;
padding-bottom: 12px;
}

.home-hero,
.snapshot-card,
.trend-panel {
background: rgba(0, 0, 0, 0.38);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 14px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.16);
}

.home-hero {
padding: 22px;
}

.home-title {
margin: 0;
color: var(--home-heading-color);
text-align: left;
font-size: clamp(1.45rem, 2.9vw, 2rem);
line-height: 1.25;
}

.home-subtitle {
margin: 8px 0 0;
color: rgba(255, 255, 255, 0.88);
font-size: 0.98rem;
}

.snapshot-grid {
display: grid;
grid-template-columns: 1fr;
gap: 14px;
}

.snapshot-card {
padding: 16px;
}

.snapshot-card h2,
.trend-panel h2 {
margin: 0;
color: var(--home-heading-color);
font-size: 1.04rem;
}

.snapshot-value {
margin: 10px 0 2px;
color: #fff;
font-size: 1.15rem;
font-weight: 700;
}

.snapshot-meta {
margin: 0;
color: #fff;
font-size: 0.94rem;
line-height: 1.45;
}

.trend-panel {
padding: 16px;
}

.trend-note {
margin: 6px 0 0;
color: rgba(255, 255, 255, 0.8);
font-size: 0.9rem;
}

.trend-stats {
margin-top: 12px;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}

.trend-stat {
background: rgba(255, 255, 255, 0.08);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.16);
padding: 12px;
}

.trend-label {
display: block;
color: rgba(255, 255, 255, 0.86);
font-size: 0.74rem;
text-transform: uppercase;
letter-spacing: 0.8px;
}

.trend-number {
display: block;
margin-top: 5px;
color: #fff;
font-size: 1.22rem;
font-weight: 700;
}

/* Components */
.login-btn {
background: #1DB954;
color: #fff;
border: none;
border-radius: 50px;
padding: 14px 32px;
font-size: 1.1rem;
font-weight: 700;
cursor: pointer;
transition: background 0.2s, transform 0.2s;
box-shadow: 0 2px 8px rgba(30,185,84,0.2);
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8px;
}

.login-btn:hover {
background: #17a74a;
transform: scale(1.04);
}

.artist-headshot {
width: 100px;
height: 100px;
border-radius: 50%;
margin-right: 18px;
object-fit: cover;
box-shadow: 0 2px 12px rgba(0,0,0,0.18);
}

.artist-name {
color: #fff;
font-weight: 700;
font-size: 1.1rem;
}

/* Animation */
.fade-in {
opacity: 0;
animation: fadeIn 1.2s ease forwards;
}

@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}

/* Media Queries */
@media (max-width: 500px) {
.login-container {
padding: 24px 8px;
max-width: 95vw;
}

h1 {
font-size: 1.3rem;
}

.tabs {
margin-top: 18px;
margin-bottom: 18px;
gap: 10px;
}

.tab-link {
font-size: 0.92rem;
padding: 7px 12px;
}

.home-main {
margin-top: 0;
gap: 14px;
}

.home-hero,
.trend-panel {
padding: 16px;
}

.snapshot-grid,
.trend-stats {
grid-template-columns: 1fr;
}
}
9 changes: 4 additions & 5 deletions authenticate/authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import (
spotifyauth "github.com/zmb3/spotify/v2/auth"
)

var (
authenticator *spotifyauth.Authenticator
redirectURI = "http://127.0.0.1:8080/callback"
const (
redirectURI = "http://127.0.0.1:8080/callback"
)

// SpotifyAuthenticate enables login with your unique client ID and secret, loading env variables from the specified file.
Expand All @@ -27,9 +26,9 @@ func SpotifyAuthenticate(envFile string) (*spotifyauth.Authenticator, error) {
return nil, fmt.Errorf("CLIENT_ID and CLIENT_SECRET must be set")
}

authenticator = spotifyauth.New(
authenticator := spotifyauth.New(
spotifyauth.WithRedirectURL(redirectURI),
spotifyauth.WithScopes(spotifyauth.ScopeUserTopRead),
spotifyauth.WithScopes(spotifyauth.ScopeUserTopRead, spotifyauth.ScopeUserReadRecentlyPlayed),
spotifyauth.WithClientID(clientID),
spotifyauth.WithClientSecret(clientSecret),
)
Expand Down
Loading
Loading