Skip to content

Commit 37b3a07

Browse files
authored
Merge pull request #36 from michiganhackers/pr-34
Adding currently playing song
2 parents 20a1af7 + 0859830 commit 37b3a07

File tree

5 files changed

+620
-323
lines changed

5 files changed

+620
-323
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { GetAccessToken } from "@/src/database/db";
2+
import { NextResponse } from 'next/server'
3+
4+
export async function POST(req: Request) {
5+
const { sid, state } = await req.json();
6+
const access_token = await GetAccessToken(sid);
7+
const endpoint = state ? 'play' : 'pause';
8+
console.log(endpoint)
9+
const url = `https://api.spotify.com/v1/me/player/${endpoint}`;
10+
11+
const response = await fetch(url, {
12+
method: 'PUT',
13+
headers: {
14+
'Authorization': `Bearer ${access_token}`,
15+
'Content-Length': '0' // Required by Spotify API
16+
}
17+
});
18+
19+
if (!response.ok) {
20+
const error = await response.json();
21+
return NextResponse.json(
22+
{ error: error.error.message },
23+
{ status: response.status }
24+
);
25+
}
26+
27+
return NextResponse.json(
28+
{ success: true, is_playing: state },
29+
{ status: 200 }
30+
);
31+
}

src/app/api/spotify/skip/route.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { GetAccessToken } from "@/src/database/db";
2+
import { NextResponse } from 'next/server'
3+
4+
export async function POST(req: Request) {
5+
const { sid } = await req.json();
6+
const access_token = await GetAccessToken(sid);
7+
const url = 'https://api.spotify.com/v1/me/player/next';
8+
9+
const response = await fetch(url, {
10+
method: 'POST',
11+
headers: {
12+
'Authorization': `Bearer ${access_token}`,
13+
'Content-Length': '0'
14+
}
15+
});
16+
17+
if (!response.ok) {
18+
const error = await response.json();
19+
return NextResponse.json(
20+
{ error: error.error.message },
21+
{ status: response.status }
22+
);
23+
}
24+
25+
return NextResponse.json(
26+
{ success: true, message: 'Skipped to next track' },
27+
{ status: 200 }
28+
);
29+
}

src/app/api/spotify/state/route.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { GetAccessToken } from "@/src/database/db";
2+
import { NextResponse } from 'next/server'
3+
4+
export async function GET(req: Request) {
5+
const { searchParams } = new URL(req.url);
6+
const sid = searchParams.get('sid');
7+
8+
if (!sid) {
9+
return NextResponse.json(
10+
{ error: 'Missing session ID' },
11+
{ status: 400 }
12+
);
13+
}
14+
15+
const access_token = await GetAccessToken(sid);
16+
const url = 'https://api.spotify.com/v1/me/player';
17+
18+
const response = await fetch(url, {
19+
headers: {
20+
'Authorization': `Bearer ${access_token}`
21+
}
22+
});
23+
24+
if (!response.ok) {
25+
if (response.status === 204) { // No content = no active playback
26+
return NextResponse.json({
27+
is_playing: false,
28+
progress_ms: 0,
29+
duration_ms: 0
30+
});
31+
}
32+
const error = await response.json();
33+
return NextResponse.json(
34+
{ error: error.error.message },
35+
{ status: response.status }
36+
);
37+
}
38+
39+
const data = await response.json();
40+
return NextResponse.json({
41+
is_playing: data.is_playing,
42+
progress_ms: data.progress_ms || 0,
43+
duration_ms: data.item?.duration_ms || 0
44+
});
45+
}

src/app/page.tsx

Lines changed: 119 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
'use client'
2-
import { stringify } from 'querystring';
32
import { useState, useEffect } from 'react';
4-
import { redirect, useRouter } from 'next/navigation'
5-
import { Router } from 'next/router';
3+
import { useRouter } from 'next/navigation';
64
import { handleSpotifyAuth } from '@/src/utils';
7-
import 'dotenv/config';
5+
86
const Toast: React.FC<{ message: string; onClose: () => void; }> = ({ message, onClose }) => {
97
return (
108
<div className="toast">
@@ -13,83 +11,131 @@ const Toast: React.FC<{ message: string; onClose: () => void; }> = ({ message, o
1311
</div>
1412
);
1513
};
16-
1714

1815
export default function Home() {
19-
const [guestCode, setGuestCode] = useState(""); // Can be set as Next.js cookie and passed into server side session/[id]/page.tsx
20-
const [hostUsername, setHost] = useState(""); // Can be set as Next.js cookie and passed into server side session/[id]/page.tsx
16+
const [guestCode, setGuestCode] = useState("");
17+
const [hostUsername, setHost] = useState("");
2118
const [guestUsername, setGuest] = useState("");
2219
const [toastMessage, setToastMessage] = useState('');
2320
const router = useRouter();
2421

25-
26-
2722
useEffect(() => {
28-
// This effect will run only on the client side
2923
if (toastMessage) {
3024
const timer = setTimeout(() => setToastMessage(''), 3500);
31-
return () => clearTimeout(timer);}
32-
// You can place any client-side specific logic here
33-
}, [toastMessage]); // Empty dependency array ensures the effect runs only once on mount
25+
return () => clearTimeout(timer);
26+
}
27+
}, [toastMessage]);
28+
29+
const handleHostSubmit = (e?: React.FormEvent) => {
30+
if (e) e.preventDefault(); // Prevent form submission behavior
31+
32+
if (hostUsername === "") {
33+
setToastMessage("Error: Username is blank.");
34+
setHost('');
35+
} else {
36+
sessionStorage.setItem("username", hostUsername);
37+
sessionStorage.setItem("isHost", "true");
38+
39+
const client_id: string | undefined = process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID;
40+
const redirect_uri: string = `${process.env.NEXT_PUBLIC_APP_SERVER}/api/spotify/getToken`;
41+
const scope: string = 'user-read-currently-playing user-read-playback-state user-modify-playback-state';
42+
43+
handleSpotifyAuth(client_id, redirect_uri, scope);
44+
}
45+
};
46+
47+
const handleGuestSubmit = (e?: React.FormEvent) => {
48+
if (e) e.preventDefault(); // Prevent form submission behavior
49+
sessionStorage.setItem("username", guestUsername);
50+
sessionStorage.setItem("isHost", "false");
51+
connectToSession(guestCode, guestUsername, router, setToastMessage, setGuestCode, setGuest);
52+
};
53+
3454
return (
3555
<main className="background flex min-h-screen flex-col items-center justify-between p-24">
36-
<img src="GMJ-emblem-color.svg" alt="" />
56+
<img src="GMJ-emblem-color.svg" alt="" className="logo" />
3757
<div className="options">
58+
59+
{/* Host Section */}
3860
<div className="hostoptions">
39-
<h1>I'm a host:</h1>
40-
<form data-testid="host-form">
41-
<input type="text" placeholder='Username' maxLength={6} name="username" onChange={(e) => setHost(e.target.value)}/>
42-
</form>
43-
<button className="SubmitButton" onClick={() => {
44-
if(hostUsername == ""){
45-
setToastMessage('Error: Username is blank.');
46-
setHost('');
47-
} else {
48-
sessionStorage.setItem("username", hostUsername); // change this to a nextjs cookie (server-side)
49-
sessionStorage.setItem("isHost", "true"); // change this to a nextjs cookie (server-side)
50-
const client_id : string | undefined = process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID; // Spotify developer client id for API calls
51-
const redirect_uri : string = `${process.env.NEXT_PUBLIC_APP_SERVER}/api/spotify/getToken`
52-
const scope : string = 'user-read-currently-playing user-read-playback-state user-modify-playback-state';
53-
handleSpotifyAuth(client_id, redirect_uri, scope); }
54-
}}>
55-
Host a Jam
56-
</button>
61+
<h1>I'm a host:</h1>
62+
<form data-testid="host-form" onSubmit={handleHostSubmit}>
63+
<input
64+
type="text"
65+
placeholder="Username"
66+
maxLength={6}
67+
name="username"
68+
className="input-field"
69+
onChange={(e) => setHost(e.target.value)}
70+
onKeyDown={(e) => {
71+
if (e.key === 'Enter') handleHostSubmit();
72+
}}
73+
/>
74+
</form>
75+
<button className="SubmitButton" onClick={handleHostSubmit}>
76+
Host a Jam
77+
</button>
5778
</div>
79+
5880
<div className="divideDiv">
59-
<hr className="divider"></hr>
81+
<hr className="divider" />
6082
</div>
6183

84+
{/* Guest Section */}
6285
<div className="guestoptions">
63-
<h1>I'm a guest:</h1>
64-
<form data-testid="guest-form">
65-
<input type="text" placeholder='Guest Code' maxLength={8} name="guestcode" value={guestCode} onChange={(e) => setGuestCode(e.target.value.toUpperCase())}/>
66-
<input type="text" placeholder='Username' maxLength={25} name="username" onChange={(e) => setGuest(e.target.value)}/>
67-
</form>
68-
<button className="SubmitButton" onClick={() => {
69-
sessionStorage.setItem("username", guestUsername);
70-
// console.log(guestUsername);
71-
sessionStorage.setItem("isHost", "false");
72-
connectToSession(guestCode, guestUsername, router,setToastMessage,setGuestCode, setGuest)}}>
73-
74-
Join
75-
</button>
86+
<h1>I'm a guest:</h1>
87+
<form data-testid="guest-form" onSubmit={handleGuestSubmit}>
88+
<input
89+
type="text"
90+
placeholder="Guest Code"
91+
maxLength={8}
92+
name="guestcode"
93+
value={guestCode}
94+
className="input-field"
95+
onChange={(e) => setGuestCode(e.target.value.toUpperCase())}
96+
onKeyDown={(e) => {
97+
if (e.key === 'Enter') handleGuestSubmit();
98+
}}
99+
/>
100+
<input
101+
type="text"
102+
placeholder="Username"
103+
maxLength={25}
104+
name="username"
105+
className="input-field"
106+
onChange={(e) => setGuest(e.target.value)}
107+
onKeyDown={(e) => {
108+
if (e.key === 'Enter') handleGuestSubmit();
109+
}}
110+
/>
111+
</form>
112+
<button className="SubmitButton" onClick={handleGuestSubmit}>
113+
Join
114+
</button>
76115
</div>
116+
77117
</div>
118+
119+
{/* Toast Notification */}
78120
{toastMessage && (
79121
<Toast message={toastMessage} onClose={() => setToastMessage('')} />
80122
)}
81123
</main>
82124
);
83125
}
84126

85-
async function connectToSession(guestCode : string, username : string, router : any, setToastMessage : any, setGuestCode: any, // Add this setter function to clear guestCode
86-
setGuest: any) : Promise<void> {
87-
// Add this setter function to clear guestCode
88-
89-
let stat;
127+
// Function to connect to a session
128+
async function connectToSession(
129+
guestCode: string,
130+
username: string,
131+
router: any,
132+
setToastMessage: any,
133+
setGuestCode: any,
134+
setGuest: any
135+
): Promise<void> {
136+
let stat;
90137
try {
91-
92-
await fetch(`${process.env.NEXT_PUBLIC_APP_SERVER}/api/sessionDB/connect`, {
138+
const response = await fetch(`${process.env.NEXT_PUBLIC_APP_SERVER}/api/sessionDB/connect`, {
93139
method: 'POST',
94140
headers: {
95141
'Content-Type': 'application/json',
@@ -98,30 +144,25 @@ async function connectToSession(guestCode : string, username : string, router :
98144
guestCode: guestCode,
99145
username: username
100146
}),
101-
}).then((response) => {
102-
if(!response.ok){
103-
stat = response.status;
104-
throw Error(response.statusText);}
105-
106-
return response.json();
107-
}).then((data) => {
108-
const url = data.url;
109-
router.push(url);
110-
})
111-
}
112-
catch(e){
113-
if(stat == 401){
114-
console.error(e);
115-
setToastMessage('Error: Guest code not found.');
116-
setGuestCode('');
117-
}else if (stat == 409){
118-
setToastMessage('Error: Username already in use.');
119-
setGuest('');
120-
} else if (stat == 406) {
121-
setToastMessage('Error: Username is blank.');
122-
setGuest('');
123-
}
124-
147+
});
148+
149+
if (!response.ok) {
150+
stat = response.status;
151+
throw new Error(response.statusText);
152+
}
153+
154+
const data = await response.json();
155+
router.push(`/session/${guestCode}`); // Redirect to the correct session
156+
} catch (e) {
157+
if (stat === 401) {
158+
setToastMessage('Error: Guest code not found.');
159+
setGuestCode('');
160+
} else if (stat === 409) {
161+
setToastMessage('Error: Username already in use.');
162+
setGuest('');
163+
} else if (stat === 406) {
164+
setToastMessage('Error: Username is blank.');
165+
setGuest('');
166+
}
125167
}
126168
}
127-

0 commit comments

Comments
 (0)