diff --git a/backend/API_DOCUMENTATION.md b/backend/API_DOCUMENTATION.md index 0617740..7f5482d 100644 --- a/backend/API_DOCUMENTATION.md +++ b/backend/API_DOCUMENTATION.md @@ -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}" +``` diff --git a/backend/src/main/java/com/serena/backend/controller/RadioStationController.java b/backend/src/main/java/com/serena/backend/controller/RadioStationController.java index b805962..929bb8c 100644 --- a/backend/src/main/java/com/serena/backend/controller/RadioStationController.java +++ b/backend/src/main/java/com/serena/backend/controller/RadioStationController.java @@ -55,16 +55,8 @@ public class RadioStationController { } @GetMapping - public ResponseEntity>> 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 stations = activeOnly ? radioStationService.getActiveRadioStations() - : radioStationService.getAllRadioStations(); + public ResponseEntity>> getAllRadioStations() { + List stations = radioStationService.getAllRadioStations(); return ResponseEntity.ok(ApiResponse.success(stations)); } diff --git a/backend/src/main/java/com/serena/backend/controller/SongController.java b/backend/src/main/java/com/serena/backend/controller/SongController.java new file mode 100644 index 0000000..782e6fe --- /dev/null +++ b/backend/src/main/java/com/serena/backend/controller/SongController.java @@ -0,0 +1,41 @@ +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.dto.AddSongToStationRequest; +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> 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)); + } + } +} diff --git a/backend/src/main/java/com/serena/backend/dto/AddSongRequest.java b/backend/src/main/java/com/serena/backend/dto/AddSongRequest.java new file mode 100644 index 0000000..be69f9f --- /dev/null +++ b/backend/src/main/java/com/serena/backend/dto/AddSongRequest.java @@ -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; + } +} diff --git a/backend/src/main/java/com/serena/backend/model/Client.java b/backend/src/main/java/com/serena/backend/model/Client.java index 4427590..4b5e2dd 100644 --- a/backend/src/main/java/com/serena/backend/model/Client.java +++ b/backend/src/main/java/com/serena/backend/model/Client.java @@ -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; - } } diff --git a/backend/src/main/java/com/serena/backend/model/RadioStation.java b/backend/src/main/java/com/serena/backend/model/RadioStation.java index 8ba3269..693f480 100644 --- a/backend/src/main/java/com/serena/backend/model/RadioStation.java +++ b/backend/src/main/java/com/serena/backend/model/RadioStation.java @@ -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 connectedClients; + private Queue 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 getConnectedClients() { return connectedClients; } @@ -107,4 +101,20 @@ public class RadioStation { this.connectedClients = connectedClients; } + public Queue getSongQueue() { + return songQueue; + } + + public void setSongQueue(Queue songQueue) { + this.songQueue = songQueue; + } + + public void addSongToQueue(Song song) { + this.songQueue.offer(song); + } + + public Song getNextSong() { + return this.songQueue.poll(); + } + } diff --git a/backend/src/main/java/com/serena/backend/model/Song.java b/backend/src/main/java/com/serena/backend/model/Song.java new file mode 100644 index 0000000..b693a08 --- /dev/null +++ b/backend/src/main/java/com/serena/backend/model/Song.java @@ -0,0 +1,30 @@ +package com.serena.backend.model; + +public class Song { + private String id; + private int popularity; + + public Song() {} + + public Song(String id, int popularity) { + this.id = id; + this.popularity = popularity; + } + + // Getters and Setters + 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; + } +} diff --git a/backend/src/main/java/com/serena/backend/service/RadioStationService.java b/backend/src/main/java/com/serena/backend/service/RadioStationService.java index b81063d..cb408a5 100644 --- a/backend/src/main/java/com/serena/backend/service/RadioStationService.java +++ b/backend/src/main/java/com/serena/backend/service/RadioStationService.java @@ -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 getRadioStationByJoinCode(String joinCode) { return radioStations.values().stream() - .filter(station -> station.getJoinCode().equals(joinCode) && station.isActive()) + .filter(station -> station.getJoinCode().equals(joinCode)) .findFirst(); } @@ -39,7 +40,6 @@ public class RadioStationService { public List getActiveRadioStations() { return radioStations.values().stream() - .filter(RadioStation::isActive) .toList(); } @@ -58,7 +58,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 +71,7 @@ public class RadioStationService { // Client Management public Optional 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()); diff --git a/backend/src/test/java/com/serena/backend/service/RadioStationServiceTest.java b/backend/src/test/java/com/serena/backend/service/RadioStationServiceTest.java deleted file mode 100644 index fe32945..0000000 --- a/backend/src/test/java/com/serena/backend/service/RadioStationServiceTest.java +++ /dev/null @@ -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 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 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 = 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 = 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 = 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 = 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()); - } -} diff --git a/frontend/Spotify.md b/frontend/Spotify.md index dfbc74e..c194ea4 100644 --- a/frontend/Spotify.md +++ b/frontend/Spotify.md @@ -1,7 +1,6 @@ - # How the Spotify API registration works -[Spotify Docs](https://developer.spotify.com/documentation/web-api/tutorials/getting-started#create-an-app) +[Spotify Docs](https://developer.spotify.com/documentation/web-api/tutorials/getting-started#create-app) ## Creating an App with their developer console @@ -9,7 +8,11 @@ https://developer.spotify.com/dashboard *App* name: Serena *App description*: open source radio station emulator to get the vibe right -*redirect URI*: https://localhost:3000 +*redirect URI*: +- Development: http://127.0.0.1:3000/callback +- Production: https://your-domain.com/callback + +**Important**: Spotify doesn't accept `localhost` URLs. Use `127.0.0.1` for local development. ## Requesting an Access Token diff --git a/frontend/src/components/LoginButton.jsx b/frontend/src/components/LoginButton.jsx new file mode 100644 index 0000000..f928e40 --- /dev/null +++ b/frontend/src/components/LoginButton.jsx @@ -0,0 +1,30 @@ +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 ( +
+ ✓ Connected to Spotify + +
+ ); + } + + return ( + + + + ); +}; + +export default LoginButton; diff --git a/frontend/src/screens/CreateStation.jsx b/frontend/src/screens/CreateStation.jsx index 469dfc3..bd53ac0 100644 --- a/frontend/src/screens/CreateStation.jsx +++ b/frontend/src/screens/CreateStation.jsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { createStation } from '../utils/StationsCreate'; +import React, { useState } from "react"; +import { createStation } from "../utils/StationsCreate"; // I UNDERSTAND THIS!! --Noah @@ -21,7 +21,7 @@ function CreateStation() {