Merge remote-tracking branch 'origin/main' into tobi-dev

This commit is contained in:
Tobias 2025-08-02 02:07:27 +02:00
commit 429111c49f
14 changed files with 280 additions and 673 deletions

View file

@ -50,10 +50,7 @@ Creates a new radio station and returns an owner token.
"ownerId": "owner-uuid",
"joinCode": "ABC123",
"createdAt": "2025-08-01T10:00:00",
"active": true,
"connectedClients": [],
"songQueue": [],
"currentlyPlaying": null
"connectedClients": []
},
"ownerToken": "jwt-token-here",
"message": "Radio station created successfully. Use this token to manage your station."
@ -65,11 +62,7 @@ Creates a new radio station and returns an owner token.
Retrieves all radio stations. Requires authentication.
**Endpoint:** `GET /radio-stations?activeOnly=true`
**Query Parameters:**
- `activeOnly` (boolean, default: false): Filter to only active stations
**Endpoint:** `GET /radio-stations`
**Response:**
@ -85,10 +78,7 @@ Retrieves all radio stations. Requires authentication.
"ownerId": "owner-uuid",
"joinCode": "ABC123",
"createdAt": "2025-08-01T10:00:00",
"active": true,
"connectedClients": ["client1", "client2"],
"songQueue": [],
"currentlyPlaying": null
"connectedClients": []
}
]
}
@ -113,73 +103,7 @@ Retrieves a specific radio station. Requires authentication.
"ownerId": "owner-uuid",
"joinCode": "ABC123",
"createdAt": "2025-08-01T10:00:00",
"active": true,
"connectedClients": [],
"songQueue": [],
"currentlyPlaying": null
}
}
```
#### Get Radio Station by Join Code
Retrieves a radio station using its join code. No authentication required.
**Endpoint:** `GET /radio-stations/join/{joinCode}`
**Response:**
```json
{
"success": true,
"message": "Success",
"data": {
"id": "station-uuid",
"name": "Station Name",
"description": "Station Description",
"ownerId": "owner-uuid",
"joinCode": "ABC123",
"createdAt": "2025-08-01T10:00:00",
"active": true,
"connectedClients": [],
"songQueue": [],
"currentlyPlaying": null
}
}
```
#### Update Radio Station
Updates a radio station. Only the station owner can update it.
**Endpoint:** `PUT /radio-stations/{stationId}`
**Request Body:**
```json
{
"name": "Updated Station Name",
"description": "Updated description"
}
```
**Response:**
```json
{
"success": true,
"message": "Radio station updated successfully",
"data": {
"id": "station-uuid",
"name": "Updated Station Name",
"description": "Updated description",
"ownerId": "owner-uuid",
"joinCode": "ABC123",
"createdAt": "2025-08-01T10:00:00",
"active": true,
"connectedClients": [],
"songQueue": [],
"currentlyPlaying": null
"connectedClients": []
}
}
```
@ -238,8 +162,7 @@ Connects a client to a radio station using a join code. No authentication requir
"id": "client-uuid",
"username": "john_doe",
"radioStationId": "station-uuid",
"connectedAt": "2025-08-01T10:00:00",
"active": true
"connectedAt": "2025-08-01T10:00:00"
},
"clientToken": "jwt-token-here",
"message": "Successfully connected to radio station. Use this token for further requests."
@ -247,258 +170,6 @@ Connects a client to a radio station using a join code. No authentication requir
}
```
#### Disconnect Client
Disconnects a client from a station. Only the station owner can disconnect clients.
**Endpoint:** `DELETE /clients/{clientId}/disconnect`
**Response:**
```json
{
"success": true,
"message": "Client disconnected successfully",
"data": null
}
```
#### Get Client Information
Retrieves information about a specific client. Requires authentication.
**Endpoint:** `GET /clients/{clientId}`
**Response:**
```json
{
"success": true,
"message": "Success",
"data": {
"id": "client-uuid",
"username": "john_doe",
"radioStationId": "station-uuid",
"connectedAt": "2025-08-01T10:00:00",
"active": true
}
}
```
#### Get Connected Clients
Retrieves all clients connected to a radio station. Requires authentication.
**Endpoint:** `GET /clients/station/{radioStationId}`
**Response:**
```json
{
"success": true,
"message": "Success",
"data": [
{
"id": "client-uuid",
"username": "john_doe",
"radioStationId": "station-uuid",
"connectedAt": "2025-08-01T10:00:00",
"active": true
}
]
}
```
### Song Management
#### Add Song to Queue
Adds a song to the station's queue. Requires authentication.
**Endpoint:** `POST /radio-stations/{stationId}/songs`
**Request Body:**
```json
{
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"duration": 355,
"url": "https://example.com/song.mp3"
}
```
**Response:**
```json
{
"success": true,
"message": "Song added to queue successfully",
"data": {
"id": "song-uuid",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"duration": 355,
"url": "https://example.com/song.mp3",
"addedBy": "user-uuid",
"addedAt": "2025-08-01T10:00:00",
"votes": {},
"upvotes": 0,
"downvotes": 0
}
}
```
#### Get Song Queue
Retrieves the current song queue, sorted by score (upvotes - downvotes). Requires authentication.
**Endpoint:** `GET /radio-stations/{stationId}/songs/queue`
**Response:**
```json
{
"success": true,
"message": "Success",
"data": [
{
"id": "song-uuid",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"duration": 355,
"url": "https://example.com/song.mp3",
"addedBy": "user-uuid",
"addedAt": "2025-08-01T10:00:00",
"votes": {
"user1": "UPVOTE",
"user2": "DOWNVOTE"
},
"upvotes": 1,
"downvotes": 1
}
]
}
```
#### Get Currently Playing Song
Retrieves the currently playing song. Requires authentication.
**Endpoint:** `GET /radio-stations/{stationId}/songs/current`
**Response:**
```json
{
"success": true,
"message": "Success",
"data": {
"id": "song-uuid",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"duration": 355,
"url": "https://example.com/song.mp3",
"addedBy": "user-uuid",
"addedAt": "2025-08-01T10:00:00",
"votes": {},
"upvotes": 0,
"downvotes": 0
}
}
```
#### Play Next Song
Plays the next song from the queue (highest scoring song). Only station owners can control playback.
**Endpoint:** `POST /radio-stations/{stationId}/songs/next`
**Response:**
```json
{
"success": true,
"message": "Playing next song",
"data": {
"id": "song-uuid",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"duration": 355,
"url": "https://example.com/song.mp3",
"addedBy": "user-uuid",
"addedAt": "2025-08-01T10:00:00",
"votes": {},
"upvotes": 0,
"downvotes": 0
}
}
```
#### Vote on Song
Casts a vote (upvote or downvote) on a song. Requires authentication.
**Endpoint:** `POST /radio-stations/{stationId}/songs/{songId}/vote`
**Request Body:**
```json
{
"voteType": "UPVOTE"
}
```
**Vote Types:**
- `UPVOTE`: Positive vote
- `DOWNVOTE`: Negative vote
**Response:**
```json
{
"success": true,
"message": "Vote recorded successfully",
"data": {
"id": "song-uuid",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"duration": 355,
"url": "https://example.com/song.mp3",
"addedBy": "user-uuid",
"addedAt": "2025-08-01T10:00:00",
"votes": {
"current-user-id": "UPVOTE"
},
"upvotes": 1,
"downvotes": 0
}
}
```
#### Remove Vote from Song
Removes the current user's vote from a song. Requires authentication.
**Endpoint:** `DELETE /radio-stations/{stationId}/songs/{songId}/vote`
**Response:**
```json
{
"success": true,
"message": "Vote removed successfully",
"data": null
}
```
## Error Responses
All error responses follow this format:
@ -511,28 +182,9 @@ All error responses follow this format:
}
```
### Common HTTP Status Codes
- `200 OK`: Request successful
- `201 Created`: Resource created successfully
- `400 Bad Request`: Invalid request data
- `401 Unauthorized`: Authentication required or token invalid
- `403 Forbidden`: Access denied (e.g., not station owner)
- `404 Not Found`: Resource not found
### Common Error Messages
- `"User not authenticated"`: Missing or invalid authentication token
- `"Radio station not found"`: Station ID or join code not found
- `"Only the station owner can..."`: Action requires owner privileges
- `"Failed to connect to radio station. Invalid join code or station not found."`: Join code is invalid
- `"Client not found"`: Client ID not found
## Usage Examples
### Creating a Station and Adding Songs
1. **Create a station:**
### Creating a Station
```bash
curl -X POST http://localhost:8080/api/radio-stations \
@ -540,18 +192,7 @@ curl -X POST http://localhost:8080/api/radio-stations \
-d '{"name": "My Station", "description": "Great music"}'
```
2. **Use the returned owner token for authenticated requests:**
```bash
curl -X POST http://localhost:8080/api/radio-stations/{stationId}/songs \
-H "Authorization: Bearer {ownerToken}" \
-H "Content-Type: application/json" \
-d '{"title": "Song Title", "artist": "Artist", "duration": 180, "url": "http://example.com/song.mp3"}'
```
### Joining a Station and Voting
1. **Join a station:**
### Joining a Station
```bash
curl -X POST http://localhost:8080/api/clients/connect \
@ -559,20 +200,16 @@ curl -X POST http://localhost:8080/api/clients/connect \
-d '{"username": "john", "joinCode": "ABC123"}'
```
2. **Vote on a song:**
### Getting All Stations (with authentication)
```bash
curl -X POST http://localhost:8080/api/radio-stations/{stationId}/songs/{songId}/vote \
-H "Authorization: Bearer {clientToken}" \
-H "Content-Type: application/json" \
-d '{"voteType": "UPVOTE"}'
curl -X GET "http://localhost:8080/api/radio-stations" \
-H "Authorization: Bearer {token}"
```
## Notes
### Deleting a Station (owner only)
- Join codes are 6-character alphanumeric strings automatically generated for each station
- Songs in the queue are sorted by score (upvotes - downvotes) in descending order
- Users can change their vote on a song, but only have one vote per song
- Only station owners can control playback (play next song)
- Tokens expire after 7 days
- The system uses in-memory storage, so data is lost when the server restarts
```bash
curl -X DELETE http://localhost:8080/api/radio-stations/{stationId} \
-H "Authorization: Bearer {ownerToken}"
```

View file

@ -55,16 +55,8 @@ public class RadioStationController {
}
@GetMapping
public ResponseEntity<ApiResponse<List<RadioStation>>> getAllRadioStations(
@RequestParam(defaultValue = "false") boolean activeOnly) {
String currentUserId = AuthUtil.getCurrentUserId();
if (currentUserId == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error("User not authenticated"));
}
List<RadioStation> stations = activeOnly ? radioStationService.getActiveRadioStations()
: radioStationService.getAllRadioStations();
public ResponseEntity<ApiResponse<List<RadioStation>>> getAllRadioStations() {
List<RadioStation> stations = radioStationService.getAllRadioStations();
return ResponseEntity.ok(ApiResponse.success(stations));
}

View file

@ -0,0 +1,40 @@
package com.serena.backend.controller;
import com.serena.backend.dto.ApiResponse;
import com.serena.backend.dto.ConnectClientRequest;
import com.serena.backend.dto.AddSongRequest;
import com.serena.backend.service.RadioStationService;
import com.serena.backend.service.JwtService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/songs/")
@CrossOrigin(origins = "*")
public class SongController {
@Autowired
private RadioStationService radioStationService;
@Autowired
private JwtService jwtService;
@PostMapping
public ResponseEntity<ApiResponse<Void>> addSong(@RequestBody AddSongRequest request) {
if (request.getSong() == null || request.getRadioStationId() == null) {
return ResponseEntity.badRequest()
.body(new ApiResponse<>(false, "Song data and radio station ID are required", null));
}
boolean success = radioStationService.addSongToQueue(request.getRadioStationId(), request.getSong());
if (success) {
return ResponseEntity.ok(new ApiResponse<>(true, "Song added to queue successfully", null));
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ApiResponse<>(false, "Radio station not found or inactive", null));
}
}
}

View file

@ -0,0 +1,31 @@
package com.serena.backend.dto;
import com.serena.backend.model.Song;
public class AddSongRequest {
private Song song;
private String radioStationId;
public AddSongRequest() {}
public AddSongRequest(Song song, String radioStationId) {
this.song = song;
this.radioStationId = radioStationId;
}
public Song getSong() {
return song;
}
public void setSong(Song song) {
this.song = song;
}
public String getRadioStationId() {
return radioStationId;
}
public void setRadioStationId(String radioStationId) {
this.radioStationId = radioStationId;
}
}

View file

@ -8,12 +8,10 @@ public class Client {
private String username;
private String radioStationId;
private LocalDateTime connectedAt;
private boolean isActive;
public Client() {
this.id = UUID.randomUUID().toString();
this.connectedAt = LocalDateTime.now();
this.isActive = true;
}
public Client(String username, String radioStationId) {
@ -55,11 +53,4 @@ public class Client {
this.connectedAt = connectedAt;
}
public boolean isActive() {
return isActive;
}
public void setActive(boolean active) {
isActive = active;
}
}

View file

@ -5,6 +5,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.LinkedList;
import java.util.Queue;
public class RadioStation {
private String id;
@ -13,15 +15,15 @@ public class RadioStation {
private String ownerId;
private String joinCode;
private LocalDateTime createdAt;
private boolean isActive;
private List<String> connectedClients;
private Queue<Song> songQueue;
public RadioStation() {
this.id = UUID.randomUUID().toString();
this.joinCode = generateJoinCode();
this.createdAt = LocalDateTime.now();
this.isActive = true;
this.connectedClients = new ArrayList<>();
this.songQueue = new LinkedList<>();
}
public RadioStation(String name, String description, String ownerId) {
@ -91,14 +93,6 @@ public class RadioStation {
this.createdAt = createdAt;
}
public boolean isActive() {
return isActive;
}
public void setActive(boolean active) {
isActive = active;
}
public List<String> getConnectedClients() {
return connectedClients;
}
@ -107,4 +101,20 @@ public class RadioStation {
this.connectedClients = connectedClients;
}
public Queue<Song> getSongQueue() {
return songQueue;
}
public void setSongQueue(Queue<Song> songQueue) {
this.songQueue = songQueue;
}
public void addSongToQueue(Song song) {
this.songQueue.offer(song);
}
public Song getNextSong() {
return this.songQueue.poll();
}
}

View file

@ -0,0 +1,60 @@
package com.serena.backend.model;
import java.util.Map;
import java.util.HashMap;
public class Song {
private String id;
private int popularity;
private double tempo;
private Map<String, Double> audioFeatures;
public Song() {
this.audioFeatures = new HashMap<>();
}
public Song(String id, int popularity) {
this.id = id;
this.popularity = popularity;
this.audioFeatures = new HashMap<>();
}
public Song(String id, int popularity, double tempo, Map<String, Double> audioFeatures) {
this.id = id;
this.popularity = popularity;
this.tempo = tempo;
this.audioFeatures = audioFeatures != null ? audioFeatures : new HashMap<>();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getPopularity() {
return popularity;
}
public void setPopularity(int popularity) {
this.popularity = popularity;
}
public double getTempo() {
return tempo;
}
public void setTempo(double tempo) {
this.tempo = tempo;
}
public Map<String, Double> getAudioFeatures() {
return audioFeatures;
}
public void setAudioFeatures(Map<String, Double> audioFeatures) {
this.audioFeatures = audioFeatures;
}
}

View file

@ -2,6 +2,7 @@ package com.serena.backend.service;
import com.serena.backend.model.RadioStation;
import com.serena.backend.model.Client;
import com.serena.backend.model.Song;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@ -29,7 +30,7 @@ public class RadioStationService {
public Optional<RadioStation> getRadioStationByJoinCode(String joinCode) {
return radioStations.values().stream()
.filter(station -> station.getJoinCode().equals(joinCode) && station.isActive())
.filter(station -> station.getJoinCode().equals(joinCode))
.findFirst();
}
@ -37,12 +38,6 @@ public class RadioStationService {
return new ArrayList<>(radioStations.values());
}
public List<RadioStation> getActiveRadioStations() {
return radioStations.values().stream()
.filter(RadioStation::isActive)
.toList();
}
public Optional<RadioStation> updateRadioStation(String stationId, String name, String description) {
RadioStation station = radioStations.get(stationId);
if (station != null) {
@ -58,7 +53,6 @@ public class RadioStationService {
public boolean deleteRadioStation(String stationId) {
RadioStation station = radioStations.get(stationId);
if (station != null) {
station.setActive(false);
// Disconnect all clients
station.getConnectedClients().clear();
// Remove from clients map
@ -72,7 +66,7 @@ public class RadioStationService {
// Client Management
public Optional<Client> connectClient(String username, String radioStationId, String joinCode) {
RadioStation station = radioStations.get(radioStationId);
if (station != null && station.isActive() && station.getJoinCode().equals(joinCode)) {
if (station != null && station.getJoinCode().equals(joinCode)) {
Client client = new Client(username, radioStationId);
clients.put(client.getId(), client);
station.getConnectedClients().add(client.getId());
@ -121,4 +115,21 @@ public class RadioStationService {
return new ArrayList<>();
}
public boolean addSongToQueue(String radioStationId, Song song) {
RadioStation station = radioStations.get(radioStationId);
if (station != null) {
station.addSongToQueue(song);
return true;
}
return false;
}
public Optional<Song> getNextSong(String radioStationId) {
RadioStation station = radioStations.get(radioStationId);
if (station != null) {
return Optional.ofNullable(station.getNextSong());
}
return Optional.empty();
}
}

View file

@ -1,154 +0,0 @@
package com.serena.backend.service;
import com.serena.backend.model.RadioStation;
import com.serena.backend.model.Client;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
class RadioStationServiceTest {
private RadioStationService radioStationService;
@BeforeEach
void setUp() {
radioStationService = new RadioStationService();
}
@Test
@DisplayName("Should create radio station with join code")
void shouldCreateRadioStationWithJoinCode() {
// Given
String name = "Test Station";
String description = "Test Description";
String ownerId = "owner123";
// When
RadioStation station = radioStationService.createRadioStation(name, description, ownerId);
// Then
assertNotNull(station);
assertNotNull(station.getId());
assertNotNull(station.getJoinCode());
assertEquals(6, station.getJoinCode().length());
assertEquals(name, station.getName());
assertEquals(description, station.getDescription());
assertEquals(ownerId, station.getOwnerId());
assertTrue(station.isActive());
}
@Test
@DisplayName("Should find radio station by join code")
void shouldFindRadioStationByJoinCode() {
// Given
RadioStation station = radioStationService.createRadioStation("Test Station", "Description", "owner123");
String joinCode = station.getJoinCode();
// When
Optional<RadioStation> foundStation = radioStationService.getRadioStationByJoinCode(joinCode);
// Then
assertTrue(foundStation.isPresent());
assertEquals(station.getId(), foundStation.get().getId());
assertEquals(joinCode, foundStation.get().getJoinCode());
}
@Test
@DisplayName("Should not find radio station with invalid join code")
void shouldNotFindRadioStationWithInvalidJoinCode() {
// Given
radioStationService.createRadioStation("Test Station", "Description", "owner123");
String invalidJoinCode = "INVALID";
// When
Optional<RadioStation> foundStation = radioStationService.getRadioStationByJoinCode(invalidJoinCode);
// Then
assertFalse(foundStation.isPresent());
}
@Test
@DisplayName("Should connect client with valid join code")
void shouldConnectClientWithValidJoinCode() {
// Given
RadioStation station = radioStationService.createRadioStation("Test Station", "Description", "owner123");
String username = "testuser";
String joinCode = station.getJoinCode();
// When
Optional<Client> client = radioStationService.connectClient(username, station.getId(), joinCode);
// Then
assertTrue(client.isPresent());
assertEquals(username, client.get().getUsername());
assertEquals(station.getId(), client.get().getRadioStationId());
assertTrue(station.getConnectedClients().contains(client.get().getId()));
}
@Test
@DisplayName("Should not connect client with invalid join code")
void shouldNotConnectClientWithInvalidJoinCode() {
// Given
RadioStation station = radioStationService.createRadioStation("Test Station", "Description", "owner123");
String username = "testuser";
String invalidJoinCode = "WRONG1";
// When
Optional<Client> client = radioStationService.connectClient(username, station.getId(), invalidJoinCode);
// Then
assertFalse(client.isPresent());
assertTrue(station.getConnectedClients().isEmpty());
}
@Test
@DisplayName("Should connect client using join code only")
void shouldConnectClientUsingJoinCodeOnly() {
// Given
RadioStation station = radioStationService.createRadioStation("Test Station", "Description", "owner123");
String username = "testuser";
String joinCode = station.getJoinCode();
// When
Optional<Client> client = radioStationService.connectClientByJoinCode(username, joinCode);
// Then
assertTrue(client.isPresent());
assertEquals(username, client.get().getUsername());
assertEquals(station.getId(), client.get().getRadioStationId());
assertTrue(station.getConnectedClients().contains(client.get().getId()));
}
@Test
@DisplayName("Should not connect client to inactive radio station")
void shouldNotConnectClientToInactiveRadioStation() {
// Given
RadioStation station = radioStationService.createRadioStation("Test Station", "Description", "owner123");
station.setActive(false);
String username = "testuser";
String joinCode = station.getJoinCode();
// When
Optional<Client> client = radioStationService.connectClient(username, station.getId(), joinCode);
// Then
assertFalse(client.isPresent());
}
@Test
@DisplayName("Should generate unique join codes for different stations")
void shouldGenerateUniqueJoinCodesForDifferentStations() {
// Given & When
RadioStation station1 = radioStationService.createRadioStation("Station 1", "Desc 1", "owner1");
RadioStation station2 = radioStationService.createRadioStation("Station 2", "Desc 2", "owner2");
// Then
assertNotEquals(station1.getJoinCode(), station2.getJoinCode());
assertEquals(6, station1.getJoinCode().length());
assertEquals(6, station2.getJoinCode().length());
}
}

View file

@ -0,0 +1,4 @@
export const RADIOSTATION_URL = "http://localhost:8080/api";
export const CREATE_RADIOSTATION_ENDPOINT = "/radio-stations";
export const LIST_RADIOSTATIONS_ENDPOINT = "/radio-stations";

View file

@ -1,11 +1,14 @@
import React, { useState } from 'react';
import React, { useState } from "react";
import { createStation } from "../utils/StationsCreate";
// I UNDERSTAND THIS!! --Noah
function CreateStation() {
const [joinMethod, setJoinMethod] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const handleCreateStation = () => {
console.log('Creating station with password:', password);
createStation(name, description);
};
return (
@ -35,52 +38,18 @@ function CreateStation() {
<h1>Create a Station on Serena</h1>
</header>
<main className="create-station-form">
<div className="join-method-section">
<h2>How should people be able to join your station?</h2>
<div className="radio-option">
<label>
<input
type="radio"
name="joinMethod"
value="password"
checked={joinMethod === 'password'}
onChange={(e) => setJoinMethod(e.target.value)}
/>
<span className="radio-custom"></span>
Password
</label>
</div>
<main className="create-station-content">
<textarea onChange={(e) => setName(e.target.value)} />
<textarea onChange={(e) => setDescription(e.target.value)} />
{joinMethod === 'password' && (
<div className="password-input-section">
<label htmlFor="station-password">Station Password:</label>
<input
type="password"
id="station-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter station password"
/>
</div>
)}
</div>
<button
className="create-station-final-btn"
onClick={handleCreateStation}
disabled={joinMethod !== 'password' || !password.trim()}
>
<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>
</main>
</div>
</div>
</div>
<button
className="create-station-final-btn"
onClick={handleCreateStation}
disabled={!name || !description}
>
Create Radio Station
</button>
</main>
</div>
);
}

View file

@ -1,17 +1,22 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { getStations } from "../utils/GetStations";
function JoinStation() {
const [verifyMethod, setVerifyMethod] = useState('');
const [password, setPassword] = useState('');
const navigate = useNavigate();
const [stations, setStations] = useState([]);
const handleJoinStation = () => {
console.log('Joining station with password:', password);
// Redirect to station page after joining
navigate('/station');
const handleJoinStation = (stationID) => {
console.log("Joining station with ID:" + stationID);
navigate("/station");
};
useEffect(() => {
console.log("Test");
getStations(getStations());
}, []);
return (
<div className="join-station-container">
<div className="join-station-main">
@ -39,52 +44,15 @@ function JoinStation() {
<h1>Join a Station on Serena</h1>
</header>
<main className="join-station-form">
<div className="verify-method-section">
<h2>How would you like to verify access?</h2>
<div className="radio-option">
<label>
<input
type="radio"
name="verifyMethod"
value="password"
checked={verifyMethod === 'password'}
onChange={(e) => setVerifyMethod(e.target.value)}
/>
<span className="radio-custom"></span>
Password
</label>
</div>
{verifyMethod === 'password' && (
<div className="password-input-section">
<label htmlFor="station-password">Station Password:</label>
<input
type="password"
id="station-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter station password"
/>
</div>
)}
</div>
<button
className="join-station-final-btn"
onClick={handleJoinStation}
disabled={verifyMethod !== 'password' || !password.trim()}
>
<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>
</main>
</div>
</div>
</div>
<main className="join-station-content">
{stations.map((station, index) => {
return (
<div className="verify-method-section">
<text>station</text>
</div>
);
})}
</main>
</div>
);
}

View file

@ -0,0 +1,20 @@
import {
LIST_RADIOSTATIONS_ENDPOINT,
RADIOSTATION_URL,
} 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);
});
}

View file

@ -0,0 +1,28 @@
import {
CREATE_RADIOSTATION_ENDPOINT,
RADIOSTATION_URL,
} from "../constants/ApiConstants";
// I UNDERSTAND THIS :D --Noah
export async function createStation(name, description) {
const body = {
name: "My Awesome Station",
description: "The best music station ever",
};
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);
});
}