diff --git a/frontend/Spotify.md b/frontend/Spotify.md new file mode 100644 index 0000000..dfbc74e --- /dev/null +++ b/frontend/Spotify.md @@ -0,0 +1,31 @@ + +# How the Spotify API registration works + +[Spotify Docs](https://developer.spotify.com/documentation/web-api/tutorials/getting-started#create-an-app) + +## Creating an App with their developer console + +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 + +## Requesting an Access Token + +```Bash +curl -X POST "https://accounts.spotify.com/api/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials&client_id=your-client-id&client_secret=your-client-secret" +``` + +Our Codes: +Client: +e1274b6593674771bea12d8366c7978b +Client Secret: +07a3440d8d4b4fbe9d736a48df079085 + +Resulting access token is valid for **One Hour** + +Access Token: +BQAtYzlcWAvIdAEIE38FOnzWbUuXWm-XRYXB7DBe1xYUSqonVGDi4_H-9pWKbhexZ-ePP8uQSe-9VNaz05gCJq549tI3EFT3UoFvcUcBvwtQGK3i0dBh2sQi7bohCgDBIGcnzADcd-Y diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 063cd83..ea0787c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-router-dom": "^6.28.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" } @@ -3075,6 +3076,15 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -13913,6 +13923,38 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index fa36250..ae9f9b1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-router-dom": "^6.28.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, diff --git a/frontend/public/index.html b/frontend/public/index.html index aa069f2..0f8bdff 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -4,10 +4,10 @@ - + - React App + Serena diff --git a/frontend/src/App.css b/frontend/src/App.css index 74b5e05..e5d1c16 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,38 +1,4 @@ .App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } + position: relative; } diff --git a/frontend/src/App.js b/frontend/src/App.js deleted file mode 100644 index 3784575..0000000 --- a/frontend/src/App.js +++ /dev/null @@ -1,25 +0,0 @@ -import logo from './logo.svg'; -import './App.css'; - -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} - -export default App; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..7b0b635 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,24 @@ +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 ( +
+ + + } /> + } /> + } /> + } /> + + +
+ ); +} + +export default App; diff --git a/frontend/src/index.css b/frontend/src/index.css index ec2585e..673a327 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,13 +1,907 @@ -body { +* { margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + color: #333; } -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; +/* Home Component Styles */ +.home-container { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.content { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 24px; + padding: 40px 30px; + text-align: center; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + max-width: 500px; + width: 100%; +} + +.title { + font-size: 2.5rem; + font-weight: 700; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 12px; + line-height: 1.2; +} + +.subtitle { + font-size: 1.1rem; + color: #666; + margin-bottom: 40px; + line-height: 1.5; +} + +.button-container { + display: flex; + flex-direction: column; + gap: 16px; +} + +.action-button { + padding: 16px 32px; + border: none; + border-radius: 12px; + font-size: 1.1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + text-decoration: none; + display: inline-block; + position: relative; + overflow: hidden; +} + +.action-button.primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); +} + +.action-button.primary:hover { + transform: translateY(-2px); + box-shadow: 0 12px 35px rgba(102, 126, 234, 0.4); +} + +.action-button.secondary { + background: white; + color: #667eea; + border: 2px solid #667eea; +} + +.action-button.secondary:hover { + background: #667eea; + color: white; + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); +} + +/* Create Station Component Styles */ +.create-station { + min-height: 100vh; + padding: 20px; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.create-station-header { + text-align: center; + margin-bottom: 40px; + padding-top: 40px; +} + +.create-station-header h1 { + font-size: 2.2rem; + font-weight: 700; + color: white; + margin-bottom: 20px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.create-station-content { + flex: 1; + max-width: 600px; + margin: 0 auto; + width: 100%; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 24px; + padding: 40px 30px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.join-method-section h2 { + font-size: 1.4rem; + font-weight: 600; + color: #333; + margin-bottom: 30px; + line-height: 1.4; +} + +.radio-option { + margin-bottom: 24px; +} + +.radio-option label { + display: flex; + align-items: center; + font-size: 1.1rem; + font-weight: 500; + cursor: pointer; + padding: 16px 20px; + border: 2px solid #e9ecef; + border-radius: 12px; + transition: all 0.3s ease; + background: white; +} + +.radio-option label:hover { + border-color: #667eea; + background: #f8f9ff; +} + +.radio-option input[type="radio"]:checked + label, +.radio-option label:has(input[type="radio"]:checked) { + border-color: #667eea; + background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%); + color: #667eea; +} + +.radio-option input[type="radio"] { + margin-right: 12px; + transform: scale(1.3); + accent-color: #667eea; +} + +.password-input-section { + margin-top: 30px; + animation: slideIn 0.3s ease; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.password-input-section label { + display: block; + font-size: 1rem; + font-weight: 600; + color: #333; + margin-bottom: 12px; +} + +.password-input-section input { + width: 100%; + padding: 16px 20px; + border: 2px solid #e9ecef; + border-radius: 12px; + font-size: 1rem; + transition: all 0.3s ease; + background: white; +} + +.password-input-section input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.create-station-final-btn { + width: 100%; + background: linear-gradient(135deg, #28a745 0%, #20c997 100%); + color: white; + border: none; + padding: 18px 32px; + font-size: 1.1rem; + font-weight: 600; + border-radius: 12px; + cursor: pointer; + margin-top: 40px; + transition: all 0.3s ease; + box-shadow: 0 8px 25px rgba(40, 167, 69, 0.3); +} + +.create-station-final-btn:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 12px 35px rgba(40, 167, 69, 0.4); +} + +.create-station-final-btn:disabled { + background: #ccc; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +/* Join Station Component Styles */ +.join-station { + min-height: 100vh; + padding: 20px; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.join-station-header { + text-align: center; + margin-bottom: 40px; + padding-top: 40px; +} + +.join-station-header h1 { + font-size: 2.2rem; + font-weight: 700; + color: white; + margin-bottom: 20px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.join-station-content { + flex: 1; + max-width: 600px; + margin: 0 auto; + width: 100%; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 24px; + padding: 40px 30px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.verify-method-section h2 { + font-size: 1.4rem; + font-weight: 600; + color: #333; + margin-bottom: 30px; + line-height: 1.4; +} + +.join-station-final-btn { + width: 100%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 18px 32px; + font-size: 1.1rem; + font-weight: 600; + border-radius: 12px; + cursor: pointer; + margin-top: 40px; + transition: all 0.3s ease; + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); +} + +.join-station-final-btn:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 12px 35px rgba(102, 126, 234, 0.4); +} + +.join-station-final-btn:disabled { + background: #ccc; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +/* Station Page Styles */ +.station-page { + min-height: 100vh; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + position: relative; + overflow-x: hidden; +} + +.animated-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 1; +} + +.star { + position: absolute; + background: rgba(255, 255, 255, 0.6); + border-radius: 50%; + animation: float 6s ease-in-out infinite; +} + +.star:nth-child(odd) { + width: 2px; + height: 2px; + animation-duration: 8s; +} + +.star:nth-child(even) { + width: 3px; + height: 3px; + animation-duration: 6s; +} + +.star:nth-child(3n) { + width: 1px; + height: 1px; + animation-duration: 10s; +} + +.star-1 { top: 10%; left: 20%; animation-delay: 0s; } +.star-2 { top: 20%; left: 80%; animation-delay: 1s; } +.star-3 { top: 30%; left: 40%; animation-delay: 2s; } +.star-4 { top: 40%; left: 10%; animation-delay: 1.5s; } +.star-5 { top: 50%; left: 70%; animation-delay: 3s; } +.star-6 { top: 60%; left: 30%; animation-delay: 0.5s; } +.star-7 { top: 70%; left: 90%; animation-delay: 2.5s; } +.star-8 { top: 80%; left: 50%; animation-delay: 1.8s; } +.star-9 { top: 15%; left: 60%; animation-delay: 2.2s; } +.star-10 { top: 25%; left: 15%; animation-delay: 3.5s; } +.star-11 { top: 35%; left: 85%; animation-delay: 0.8s; } +.star-12 { top: 45%; left: 25%; animation-delay: 4s; } +.star-13 { top: 55%; left: 75%; animation-delay: 1.2s; } +.star-14 { top: 65%; left: 5%; animation-delay: 2.8s; } +.star-15 { top: 75%; left: 95%; animation-delay: 0.3s; } +.star-16 { top: 85%; left: 35%; animation-delay: 3.2s; } +.star-17 { top: 5%; left: 45%; animation-delay: 1.7s; } +.star-18 { top: 95%; left: 65%; animation-delay: 2.1s; } +.star-19 { top: 12%; left: 2%; animation-delay: 3.8s; } +.star-20 { top: 88%; left: 98%; animation-delay: 0.9s; } + +@keyframes float { + 0%, 100% { + transform: translateY(0px) translateX(0px); + opacity: 0.6; + } + 50% { + transform: translateY(-10px) translateX(5px); + opacity: 1; + } +} + +.station-content { + position: relative; + z-index: 2; + padding: 20px; + max-width: 800px; + margin: 0 auto; +} + +.station-header { + text-align: center; + margin-bottom: 40px; + padding-top: 40px; +} + +.station-header h1 { + font-size: 2.5rem; + font-weight: 700; + color: white; + margin-bottom: 10px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.station-subtitle { + font-size: 1.1rem; + color: rgba(255, 255, 255, 0.9); + margin: 0; +} + +.media-controls-section { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 20px; + padding: 30px; + margin-bottom: 30px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.current-song { + text-align: center; + margin-bottom: 25px; +} + +.current-song h3 { + font-size: 1.4rem; + font-weight: 600; + color: #333; + margin-bottom: 5px; +} + +.current-song p { + font-size: 1rem; + color: #666; + margin: 0; +} + +.media-controls { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; +} + +.control-btn { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border: none; + border-radius: 50%; + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + color: white; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); +} + +.control-btn.play-pause { + width: 60px; + height: 60px; + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); +} + +.control-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5); +} + +.songs-section { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 20px; + padding: 30px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 25px; +} + +.section-header h2 { + font-size: 1.5rem; + font-weight: 600; + color: #333; + margin: 0; +} + +.add-song-btn { + background: linear-gradient(135deg, #28a745 0%, #20c997 100%); + color: white; + border: none; + padding: 12px 20px; + border-radius: 10px; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3); +} + +.add-song-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4); +} + +.songs-list { + max-height: 400px; + overflow-y: auto; + margin: -5px; + padding: 5px; +} + +.songs-list::-webkit-scrollbar { + width: 6px; +} + +.songs-list::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 3px; +} + +.songs-list::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 3px; +} + +.songs-list::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +.song-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + border-radius: 12px; + margin-bottom: 8px; + transition: all 0.3s ease; + cursor: pointer; + border: 2px solid transparent; +} + +.song-item:hover { + background: rgba(102, 126, 234, 0.05); + border-color: rgba(102, 126, 234, 0.2); +} + +.song-info h4 { + font-size: 1rem; + font-weight: 600; + color: #333; + margin: 0 0 4px 0; +} + +.song-info p { + font-size: 0.9rem; + color: #666; + margin: 0; +} + +.song-duration { + font-size: 0.9rem; + color: #888; + font-weight: 500; +} + +/* Modal Styles */ +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: 20px; +} + +.modal-content { + background: white; + border-radius: 20px; + width: 100%; + max-width: 500px; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 25px 30px 20px; + border-bottom: 1px solid #eee; +} + +.modal-header h2 { + font-size: 1.4rem; + font-weight: 600; + color: #333; + margin: 0; +} + +.close-btn { + background: none; + border: none; + cursor: pointer; + padding: 5px; + border-radius: 50%; + color: #666; + transition: all 0.3s ease; +} + +.close-btn:hover { + background: #f5f5f5; + color: #333; +} + +.modal-body { + padding: 30px; +} + +.modal-body p { + text-align: center; + color: #666; + font-size: 1rem; + margin: 0; +} + +/* Empty State Styles */ +.empty-songs-state { + text-align: center; + padding: 40px 20px; + color: #666; +} + +.empty-songs-state p { + font-size: 1.1rem; + margin: 0; + font-style: italic; +} + +/* Mobile Responsive Design */ +@media (max-width: 768px) { + .content { + padding: 30px 20px; + margin: 10px; + border-radius: 20px; + } + + .title { + font-size: 2rem; + } + + .subtitle { + font-size: 1rem; + margin-bottom: 30px; + } + + .create-station-header { + padding-top: 20px; + margin-bottom: 30px; + } + + .create-station-header h1 { + font-size: 1.8rem; + } + + .create-station-content { + padding: 30px 20px; + border-radius: 20px; + margin: 10px; + } + + .join-method-section h2 { + font-size: 1.2rem; + } + + .radio-option label { + padding: 14px 16px; + font-size: 1rem; + } + + .password-input-section input { + padding: 14px 16px; + } + + .create-station-final-btn { + padding: 16px 24px; + font-size: 1rem; + } + + .join-station-header { + padding-top: 20px; + margin-bottom: 30px; + } + + .join-station-header h1 { + font-size: 1.8rem; + } + + .join-station-content { + padding: 30px 20px; + border-radius: 20px; + margin: 10px; + } + + .verify-method-section h2 { + font-size: 1.2rem; + } + + .join-station-final-btn { + padding: 16px 24px; + font-size: 1rem; + } + + .station-content { + padding: 15px; + } + + .station-header { + padding-top: 20px; + margin-bottom: 30px; + } + + .station-header h1 { + font-size: 2rem; + } + + .station-subtitle { + font-size: 1rem; + } + + .media-controls-section { + padding: 25px 20px; + } + + .songs-section { + padding: 25px 20px; + } + + .section-header { + flex-direction: column; + align-items: stretch; + gap: 15px; + } + + .add-song-btn { + align-self: center; + min-width: 120px; + } + + .song-item { + padding: 12px 15px; + } + + .songs-list { + max-height: 300px; + } + + .modal-header { + padding: 20px 25px 15px; + } + + .modal-body { + padding: 25px; + } +} + +@media (max-width: 480px) { + .home-container { + padding: 15px; + } + + .content { + padding: 25px 15px; + } + + .title { + font-size: 1.8rem; + } + + .create-station { + padding: 15px; + } + + .create-station-content { + padding: 25px 15px; + } + + .join-station { + padding: 15px; + } + + .join-station-content { + padding: 25px 15px; + } + + .station-content { + padding: 10px; + } + + .station-header h1 { + font-size: 1.8rem; + } + + .media-controls-section { + padding: 20px 15px; + } + + .songs-section { + padding: 20px 15px; + } + + .control-btn { + width: 45px; + height: 45px; + } + + .control-btn.play-pause { + width: 55px; + height: 55px; + } + + .modal-backdrop { + padding: 15px; + } + + .modal-header { + padding: 15px 20px 10px; + } + + .modal-body { + padding: 20px; + } +} + +/* Larger screens */ +@media (min-width: 1024px) { + .title { + font-size: 3rem; + } + + .subtitle { + font-size: 1.2rem; + } + + .create-station-header h1 { + font-size: 2.5rem; + } + + .button-container { + flex-direction: row; + justify-content: center; + gap: 20px; + } + + .action-button { + min-width: 200px; + } + + .join-station-header h1 { + font-size: 2.5rem; + } + + .station-header h1 { + font-size: 3rem; + } + + .station-subtitle { + font-size: 1.2rem; + } + + .media-controls { + gap: 30px; + } + + .control-btn { + width: 55px; + height: 55px; + } + + .control-btn.play-pause { + width: 70px; + height: 70px; + } } diff --git a/frontend/src/screens/AddSongModal.jsx b/frontend/src/screens/AddSongModal.jsx new file mode 100644 index 0000000..b4d50bc --- /dev/null +++ b/frontend/src/screens/AddSongModal.jsx @@ -0,0 +1,30 @@ +import React from 'react'; + +function AddSongModal({ onClose }) { + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose(); + } + }; + + return ( +
+
+
+

Add Song

+ +
+ +
+

Song addition functionality coming soon...

+
+
+
+ ); +} + +export default AddSongModal; diff --git a/frontend/src/screens/CreateStation.jsx b/frontend/src/screens/CreateStation.jsx new file mode 100644 index 0000000..8c61b90 --- /dev/null +++ b/frontend/src/screens/CreateStation.jsx @@ -0,0 +1,61 @@ +import React, { useState } from 'react'; + +function CreateStation() { + const [joinMethod, setJoinMethod] = useState(''); + const [password, setPassword] = useState(''); + + const handleCreateStation = () => { + // Handle station creation logic here + console.log('Creating station with password:', password); + }; + + return ( +
+
+

Create a Station on Serena

+
+ +
+
+

How should people be able to join your station?

+ +
+ +
+ + {joinMethod === 'password' && ( +
+ + setPassword(e.target.value)} + placeholder="Enter station password" + /> +
+ )} +
+ + +
+
+ ); +} + +export default CreateStation; diff --git a/frontend/src/screens/Home.jsx b/frontend/src/screens/Home.jsx new file mode 100644 index 0000000..414e9d9 --- /dev/null +++ b/frontend/src/screens/Home.jsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +function Home() { + const navigate = useNavigate(); + + const handleCreateStation = () => { + navigate('/create-station'); + }; + + const handleJoinStation = () => { + navigate('/join-station'); + }; + + return ( +
+
+

Radio Station Hub

+

Create or join a radio station to share music with friends

+ +
+ + + +
+
+
+ ); +} + +export default Home; diff --git a/frontend/src/screens/JoinStation.jsx b/frontend/src/screens/JoinStation.jsx new file mode 100644 index 0000000..3bb3302 --- /dev/null +++ b/frontend/src/screens/JoinStation.jsx @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +function JoinStation() { + const [verifyMethod, setVerifyMethod] = useState(''); + const [password, setPassword] = useState(''); + const navigate = useNavigate(); + + const handleJoinStation = () => { + console.log('Joining station with password:', password); + // Redirect to station page after joining + navigate('/station'); + }; + + return ( +
+
+

Join a Station on Serena

+
+ +
+
+

How would you like to verify access?

+ +
+ +
+ + {verifyMethod === 'password' && ( +
+ + setPassword(e.target.value)} + placeholder="Enter station password" + /> +
+ )} +
+ + +
+
+ ); +} + +export default JoinStation; diff --git a/frontend/src/screens/StationPage.jsx b/frontend/src/screens/StationPage.jsx new file mode 100644 index 0000000..3016fe9 --- /dev/null +++ b/frontend/src/screens/StationPage.jsx @@ -0,0 +1,115 @@ +import React, { useState } from 'react'; +import AddSongModal from './AddSongModal'; + +function StationPage() { + const [isModalOpen, setIsModalOpen] = useState(false); + const [currentSong, setCurrentSong] = useState({ + title: "No song playing", + artist: "Add songs to get started", + isPlaying: false + }); + + // Empty song list - no songs initially + const recommendedSongs = []; + + const handlePlayPause = () => { + setCurrentSong(prev => ({ ...prev, isPlaying: !prev.isPlaying })); + }; + + const handleNext = () => { + console.log('Next song'); + }; + + const handlePrevious = () => { + console.log('Previous song'); + }; + + const openModal = () => { + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + }; + + return ( +
+
+ {[...Array(20)].map((_, i) => ( +
+ ))} +
+ +
+
+

Serena Station

+

Collaborative Music Experience

+
+ +
+
+

{currentSong.title}

+

{currentSong.artist}

+
+ +
+ + + + + +
+
+ +
+
+

Song Queue

+ +
+ +
+ {recommendedSongs.length === 0 ? ( +
+

No songs in queue yet. Add some songs to get started!

+
+ ) : ( + recommendedSongs.map(song => ( +
+
+

{song.title}

+

{song.artist}

+
+ {song.duration} +
+ )) + )} +
+
+
+ + {isModalOpen && } +
+ ); +} + +export default StationPage;