Merge branch 'noah-dev'

This commit is contained in:
Noah Knapp 2025-08-02 11:13:11 +02:00
commit f3cacb2a69
7 changed files with 48 additions and 101 deletions

2
frontend/.gitignore vendored
View file

@ -21,3 +21,5 @@
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
music/*

View file

@ -0,0 +1,35 @@
import React, { useRef, useState } from "react";
const SingleAudioPlayer = ({ src }) => {
const audioRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const togglePlay = () => {
const audio = audioRef.current;
if (!audio) return;
if (isPlaying) {
audio.pause();
} else {
audio.play().catch((e) => {
console.warn("Playback error:", e);
});
}
setIsPlaying(!isPlaying);
};
const handleEnded = () => {
setIsPlaying(false);
};
return (
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
<button onClick={togglePlay}>{isPlaying ? "Pause" : "Play"}</button>
<span>{src.split("/").pop()}</span>
<audio ref={audioRef} src={src} onEnded={handleEnded} />
</div>
);
};
export default SingleAudioPlayer;

View file

@ -1,30 +0,0 @@
import React from 'react';
import { getSpotifyLoginUrl, isLoggedIn, removeAccessToken } from '../utils/spotifyAuth.js';
const LoginButton = () => {
const loggedIn = isLoggedIn();
const handleLogout = () => {
removeAccessToken();
window.location.reload(); // Refresh to update UI
};
if (loggedIn) {
return (
<div className="login-status">
<span> Connected to Spotify</span>
<button onClick={handleLogout} className="logout-btn">
Logout
</button>
</div>
);
}
return (
<a href={getSpotifyLoginUrl()}>
<button className="spotify-login-btn">Login with Spotify</button>
</a>
);
};
export default LoginButton;

View file

@ -3,6 +3,3 @@ export const RADIOSTATION_URL = "http://localhost:8080/api";
export const CREATE_RADIOSTATION_ENDPOINT = "/radio-stations"; export const CREATE_RADIOSTATION_ENDPOINT = "/radio-stations";
export const LIST_RADIOSTATIONS_ENDPOINT = "/radio-stations"; export const LIST_RADIOSTATIONS_ENDPOINT = "/radio-stations";
export const ADD_CLIENT_ENDPOINT = "/clients/connect"; export const ADD_CLIENT_ENDPOINT = "/clients/connect";
export const SPOTIFY_PREMIUM_TOKEN =
"BQCEnWrYVdmta4Eekdkewazun99IuocRAyPEbwPSrHuGYgZYg7JGlXG2BLmRL6fwjzPJkrmHiqlTLSn1yqR36awA9Rv4n9dwvTBv1DjAitsuzaEVg7PtYdbUHXqP2HJJ4dDDvTtvUfWIBDY_Afa7WgY1cyRbl-p4VobNHXUR9N3Ye1qBTgH3RZ5ziIbIoNWe_JrxYvcedkvr23zXUVabOyahTgt_YdmnCWt2Iu8XT8sjhSyc8tOCqbs_KqE-Qe1WSPUCrGS8";

View file

@ -1,13 +1,12 @@
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import AddSongModal from "./AddSongModal";
import LoginButton from "../components/LoginButton";
import { addClientToStation } from "../utils/AddClientToStation"; import { addClientToStation } from "../utils/AddClientToStation";
import { getAudioFiles } from "../utils/AudioFiles";
import SingleAudioPlayer from "../components/AudioPlayer";
function StationPage() { function StationPage() {
const { stationID } = useParams(); const { stationID } = useParams();
const [clientToken, setClientToken] = useState(""); const [clientToken, setClientToken] = useState("");
const [isModalOpen, setIsModalOpen] = useState(false);
const [accessDenied, setAccessDenied] = useState(true); const [accessDenied, setAccessDenied] = useState(true);
const [userName, setUserName] = useState(""); const [userName, setUserName] = useState("");
@ -29,20 +28,12 @@ function StationPage() {
<div> <div>
<textarea onChange={(e) => setUserName(e.target.value)} /> <textarea onChange={(e) => setUserName(e.target.value)} />
<textarea onChange={(e) => setJoinCode(e.target.value)} /> <textarea onChange={(e) => setJoinCode(e.target.value)} />
<button onClick={auth}>Access RadioTower</button> <button onClick={auth}>Access this RadioTower</button>
</div> </div>
); );
} }
const recommendedSongs = []; const recommendedSongs = getAudioFiles() ?? [];
const openModal = () => {
setIsModalOpen(true);
};
const closeModal = () => {
setIsModalOpen(false);
};
return ( return (
<div className="station-page"> <div className="station-page">
@ -56,15 +47,12 @@ function StationPage() {
<header className="station-header"> <header className="station-header">
<h1>Serena Station</h1> <h1>Serena Station</h1>
<p className="station-subtitle">Collaborative Music Experience</p> <p className="station-subtitle">Collaborative Music Experience</p>
<LoginButton />
</header> </header>
<SingleAudioPlayer src={recommendedSongs[0]}></SingleAudioPlayer>
<div className="songs-section"> <div className="songs-section">
<div className="section-header"> <div className="section-header">
<h2>Song Queue</h2> <h2>Song Queue</h2>
<button className="add-song-btn" onClick={openModal}>
Add Song
</button>
</div> </div>
<div className="songs-list"> <div className="songs-list">
@ -73,21 +61,11 @@ function StationPage() {
<p>No songs in queue yet. Add some songs to get started!</p> <p>No songs in queue yet. Add some songs to get started!</p>
</div> </div>
) : ( ) : (
recommendedSongs.map((song) => ( recommendedSongs.map((song) => <p>{song}</p>)
<div key={song.id} className="song-item">
<div className="song-info">
<h4>{song.title}</h4>
<p>{song.artist}</p>
</div>
<span className="song-duration">{song.duration}</span>
</div>
))
)} )}
</div> </div>
</div> </div>
</div> </div>
{isModalOpen && <AddSongModal onClose={closeModal} />}
</div> </div>
); );
} }

View file

@ -0,0 +1,5 @@
const importAll = (r) => r.keys().map(r);
export const getAudioFiles = () => {
return importAll(require.context("../../music", false, /\.(mp3|wav)$/));
};

View file

@ -1,40 +0,0 @@
const CLIENT_ID = 'e1274b6593674771bea12d8366c7978b';
const REDIRECT_URI = 'http://localhost:3000/callback';
const SCOPES = [
'user-read-private',
'user-read-email',
'playlist-read-private',
'user-library-read',
];
export const getSpotifyLoginUrl = () => {
const scope = SCOPES.join('%20');
return `https://accounts.spotify.com/authorize?client_id=${CLIENT_ID}&response_type=token&redirect_uri=${encodeURIComponent(
REDIRECT_URI
)}&scope=${scope}`;
};
export const getAccessTokenFromUrl = () => {
const hash = window.location.hash;
if (hash) {
const params = new URLSearchParams(hash.substring(1));
return params.get('access_token');
}
return null;
};
export const storeAccessToken = (token) => {
localStorage.setItem('spotify_access_token', token);
};
export const getAccessToken = () => {
return localStorage.getItem('spotify_access_token');
};
export const removeAccessToken = () => {
localStorage.removeItem('spotify_access_token');
};
export const isLoggedIn = () => {
return !!getAccessToken();
};