Merge remote-tracking branch 'origin/main' into tobi-dev
This commit is contained in:
commit
e80cb2469a
18 changed files with 764 additions and 205 deletions
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import Home from './screens/Home';
|
||||
import CreateStation from './screens/CreateStation';
|
||||
import JoinStation from './screens/JoinStation';
|
||||
import StationPage from './screens/StationPage';
|
||||
import './App.css';
|
||||
import React from "react";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import Home from "./screens/Home";
|
||||
import CreateStation from "./screens/CreateStation";
|
||||
import JoinStation from "./screens/JoinStation";
|
||||
import StationPage from "./screens/StationPage";
|
||||
import "./App.css";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
|
@ -14,7 +14,7 @@ function App() {
|
|||
<Route path="/" element={<Home />} />
|
||||
<Route path="/create-station" element={<CreateStation />} />
|
||||
<Route path="/join-station" element={<JoinStation />} />
|
||||
<Route path="/station" element={<StationPage />} />
|
||||
<Route path="/station/:id" element={<StationPage />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
|
|
|
@ -2,3 +2,7 @@ export const RADIOSTATION_URL = "http://localhost:8080/api";
|
|||
|
||||
export const CREATE_RADIOSTATION_ENDPOINT = "/radio-stations";
|
||||
export const LIST_RADIOSTATIONS_ENDPOINT = "/radio-stations";
|
||||
export const ADD_CLIENT_ENDPOINT = "/clients/connect";
|
||||
|
||||
export const SPOTIFY_PREMIUM_TOKEN =
|
||||
"BQCEnWrYVdmta4Eekdkewazun99IuocRAyPEbwPSrHuGYgZYg7JGlXG2BLmRL6fwjzPJkrmHiqlTLSn1yqR36awA9Rv4n9dwvTBv1DjAitsuzaEVg7PtYdbUHXqP2HJJ4dDDvTtvUfWIBDY_Afa7WgY1cyRbl-p4VobNHXUR9N3Ye1qBTgH3RZ5ziIbIoNWe_JrxYvcedkvr23zXUVabOyahTgt_YdmnCWt2Iu8XT8sjhSyc8tOCqbs_KqE-Qe1WSPUCrGS8";
|
||||
|
|
|
@ -6,9 +6,10 @@ import { createStation } from "../utils/StationsCreate";
|
|||
function CreateStation() {
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [joinCode, setJoinCode] = useState("");
|
||||
|
||||
const handleCreateStation = () => {
|
||||
createStation(name, description);
|
||||
setJoinCode(createStation(name, description));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -49,6 +50,8 @@ function CreateStation() {
|
|||
>
|
||||
Create Radio Station
|
||||
</button>
|
||||
|
||||
<p>your joinCode: {joinCode}</p>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,65 +1,39 @@
|
|||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
function Home() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleCreateStation = () => {
|
||||
navigate('/create-station');
|
||||
navigate("/create-station");
|
||||
};
|
||||
|
||||
const handleJoinStation = () => {
|
||||
navigate('/join-station');
|
||||
navigate("/join-station");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="home-container">
|
||||
<div className="home-main">
|
||||
{/* Left Section - Logo/Animation */}
|
||||
<div className="home-left-section">
|
||||
<div className="home-vinyl-container">
|
||||
<div className="home-vinyl-record">
|
||||
<div className="vinyl-center"></div>
|
||||
<div className="vinyl-grooves"></div>
|
||||
</div>
|
||||
|
||||
{/* Animated Music Notes */}
|
||||
<div className="home-music-notes">
|
||||
<div className="music-note note-1">♪</div>
|
||||
<div className="music-note note-2">♫</div>
|
||||
<div className="music-note note-3">♪</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content">
|
||||
<h1 className="title">Radio Station Hub</h1>
|
||||
<p className="subtitle">
|
||||
Create or join a radio station to share music with friends
|
||||
</p>
|
||||
|
||||
{/* Right Section - Content */}
|
||||
<div className="home-right-section">
|
||||
<div className="home-content">
|
||||
<h1 className="home-title">Serena Hub</h1>
|
||||
<p className="home-subtitle">Create or join a radio station to share music with friends</p>
|
||||
|
||||
<div className="home-button-container">
|
||||
<button
|
||||
className="home-action-button primary"
|
||||
onClick={handleCreateStation}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</svg>
|
||||
Create Radio Station
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="home-action-button secondary"
|
||||
onClick={handleJoinStation}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
||||
</svg>
|
||||
Join Radio Station
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="button-container">
|
||||
<button
|
||||
className="action-button primary"
|
||||
onClick={handleCreateStation}
|
||||
>
|
||||
Create Radio Station
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="action-button secondary"
|
||||
onClick={handleJoinStation}
|
||||
>
|
||||
Join Radio Station
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,14 +7,17 @@ function JoinStation() {
|
|||
const [stations, setStations] = useState([]);
|
||||
|
||||
const handleJoinStation = (stationID) => {
|
||||
console.log("Joining station with ID:" + stationID);
|
||||
navigate("/station");
|
||||
// console.log("Joining station with ID:" + stationID);
|
||||
|
||||
navigate(`/station/${stationID}`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Test");
|
||||
|
||||
getStations(getStations());
|
||||
async function fetchStations() {
|
||||
const result = await getStations();
|
||||
setStations(result);
|
||||
}
|
||||
fetchStations();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -47,8 +50,12 @@ function JoinStation() {
|
|||
<main className="join-station-content">
|
||||
{stations.map((station, index) => {
|
||||
return (
|
||||
<div className="verify-method-section">
|
||||
<text>station</text>
|
||||
<div key={index}>
|
||||
<p>Station Name: {station.name}</p>
|
||||
<p>Station Description: {station.description}</p>
|
||||
<button onClick={() => handleJoinStation(station.id)}>
|
||||
join this station
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -1,27 +1,41 @@
|
|||
import React, { useState } from 'react';
|
||||
import AddSongModal from './AddSongModal';
|
||||
import { useParams } from "react-router-dom";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import AddSongModal from "./AddSongModal";
|
||||
import LoginButton from "../components/LoginButton";
|
||||
import { addClientToStation } from "../utils/AddClientToStation";
|
||||
|
||||
function StationPage() {
|
||||
const { stationID } = useParams();
|
||||
const [clientToken, setClientToken] = useState("");
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [currentSong, setCurrentSong] = useState({
|
||||
title: "No song playing",
|
||||
artist: "Add songs to get started",
|
||||
isPlaying: false,
|
||||
progress: 0,
|
||||
duration: 180 // 3 minutes in seconds
|
||||
});
|
||||
const [accessDenied, setAccessDenied] = useState(true);
|
||||
|
||||
const [nextSong] = useState({
|
||||
title: "No song queued",
|
||||
artist: "No Artist Queued"
|
||||
});
|
||||
const [userName, setUserName] = useState("");
|
||||
const [joinCode, setJoinCode] = useState("");
|
||||
|
||||
const auth = async () => {
|
||||
try {
|
||||
const token = await addClientToStation(userName, joinCode);
|
||||
setClientToken(token);
|
||||
setAccessDenied(false);
|
||||
} catch (error) {
|
||||
console.error("Auth failed:", error);
|
||||
setAccessDenied(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (accessDenied) {
|
||||
return (
|
||||
<div>
|
||||
<textarea onChange={(e) => setUserName(e.target.value)} />
|
||||
<textarea onChange={(e) => setJoinCode(e.target.value)} />
|
||||
<button onClick={auth}>Access RadioTower</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const recommendedSongs = [];
|
||||
|
||||
const handlePlayPause = () => {
|
||||
setCurrentSong(prev => ({ ...prev, isPlaying: !prev.isPlaying }));
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
@ -31,83 +45,44 @@ function StationPage() {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="station-page-desktop">
|
||||
{/* Main Layout */}
|
||||
<div className="desktop-main">
|
||||
{/* Left Section */}
|
||||
<div className="left-section">
|
||||
{/* Vinyl Record */}
|
||||
<div className="vinyl-container">
|
||||
<div className={`vinyl-record-desktop ${currentSong.isPlaying ? 'spinning' : ''}`}>
|
||||
<div className="vinyl-center"></div>
|
||||
<div className="vinyl-grooves"></div>
|
||||
</div>
|
||||
|
||||
{/* Animated Music Notes */}
|
||||
{currentSong.isPlaying && (
|
||||
<div className="music-notes-desktop">
|
||||
<div className="music-note note-1">♪</div>
|
||||
<div className="music-note note-2">♫</div>
|
||||
<div className="music-note note-3">♪</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="station-page">
|
||||
<div className="animated-background">
|
||||
{[...Array(20)].map((_, i) => (
|
||||
<div key={i} className={`star star-${i + 1}`}></div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Next Song Info */}
|
||||
<div className="next-song-info">
|
||||
<p className="next-label">Next song:</p>
|
||||
<h4>{nextSong.title}</h4>
|
||||
<p className="next-artist">{nextSong.artist}</p>
|
||||
</div>
|
||||
<div className="station-content">
|
||||
<header className="station-header">
|
||||
<h1>Serena Station</h1>
|
||||
<p className="station-subtitle">Collaborative Music Experience</p>
|
||||
<LoginButton />
|
||||
</header>
|
||||
|
||||
{/* Bottom Play Icon */}
|
||||
<div className="bottom-play-icon">
|
||||
<button className="mini-play-btn" onClick={handlePlayPause}>
|
||||
{currentSong.isPlaying ? (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
</svg>
|
||||
)}
|
||||
<div className="songs-section">
|
||||
<div className="section-header">
|
||||
<h2>Song Queue</h2>
|
||||
<button className="add-song-btn" onClick={openModal}>
|
||||
Add Song
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Section - Recommendations */}
|
||||
<div className="right-section">
|
||||
<div className="recommendations-container">
|
||||
<div className="recommendations-header">
|
||||
<h2>Song Suggestions</h2>
|
||||
<button className="add-suggestion-btn" onClick={openModal}>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="recommendations-list">
|
||||
{recommendedSongs.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px', color: '#aaa' }}>
|
||||
<p>No songs suggested yet</p>
|
||||
<p style={{ fontSize: '14px', marginTop: '10px' }}>Add songs to get started!</p>
|
||||
</div>
|
||||
) : (
|
||||
recommendedSongs.map(song => (
|
||||
<div key={song.id} className="recommendation-item">
|
||||
<img src={song.coverUrl} alt={`${song.title} cover`} className="song-cover" />
|
||||
<div className="song-info">
|
||||
<h4>{song.title}</h4>
|
||||
<p className="artist">{song.artist}</p>
|
||||
<p className="album">{song.album}</p>
|
||||
</div>
|
||||
<span className="duration">{song.duration}</span>
|
||||
<div className="songs-list">
|
||||
{recommendedSongs.length === 0 ? (
|
||||
<div className="empty-songs-state">
|
||||
<p>No songs in queue yet. Add some songs to get started!</p>
|
||||
</div>
|
||||
) : (
|
||||
recommendedSongs.map((song) => (
|
||||
<div key={song.id} className="song-item">
|
||||
<div className="song-info">
|
||||
<h4>{song.title}</h4>
|
||||
<p>{song.artist}</p>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<span className="song-duration">{song.duration}</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
33
frontend/src/utils/AddClientToStation.js
Normal file
33
frontend/src/utils/AddClientToStation.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import {
|
||||
RADIOSTATION_URL,
|
||||
ADD_CLIENT_ENDPOINT,
|
||||
} from "../constants/ApiConstants";
|
||||
|
||||
export async function addClientToStation(username, joinCode) {
|
||||
const body = {
|
||||
username,
|
||||
joinCode,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(RADIOSTATION_URL + ADD_CLIENT_ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
console.error("Failed to connect to station:", data.message);
|
||||
throw new Error(data.message || "Unknown error");
|
||||
}
|
||||
|
||||
return data.data.clientToken;
|
||||
} catch (error) {
|
||||
console.error("Error connecting client to station:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
|
@ -4,17 +4,22 @@ import {
|
|||
} from "../constants/ApiConstants";
|
||||
|
||||
export async function getStations() {
|
||||
fetch(RADIOSTATION_URL + LIST_RADIOSTATIONS_ENDPOINT, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("Station:", data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error creating station:", error);
|
||||
});
|
||||
try {
|
||||
const response = await fetch(
|
||||
RADIOSTATION_URL + LIST_RADIOSTATIONS_ENDPOINT,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Station:", data.data);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching stations:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,22 +7,32 @@ import {
|
|||
|
||||
export async function createStation(name, description) {
|
||||
const body = {
|
||||
name: "My Awesome Station",
|
||||
description: "The best music station ever",
|
||||
name: name,
|
||||
description: description,
|
||||
};
|
||||
|
||||
fetch(RADIOSTATION_URL + CREATE_RADIOSTATION_ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("Station created:", data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error creating station:", error);
|
||||
});
|
||||
try {
|
||||
const response = await fetch(
|
||||
RADIOSTATION_URL + CREATE_RADIOSTATION_ENDPOINT,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
console.error("Failed to create station:", data.message);
|
||||
throw new Error(data.message || "Unknown error");
|
||||
}
|
||||
|
||||
return data.data.station.joinCode;
|
||||
} catch (error) {
|
||||
console.error("Error connecting client to station:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue