Skip to content

Commit 54a56dd

Browse files
committed
add spotify activity
1 parent 0562599 commit 54a56dd

File tree

9 files changed

+675
-12
lines changed

9 files changed

+675
-12
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { NextResponse } from "next/server";
2+
import { getNowPlaying } from "@/utils/spotify";
3+
4+
export const dynamic = "force-dynamic";
5+
6+
export async function GET() {
7+
try {
8+
const response = await getNowPlaying();
9+
return NextResponse.json(response);
10+
} catch (error) {
11+
console.error("Error fetching now playing:", error);
12+
return NextResponse.json({ isPlaying: false }, { status: 200 });
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { NextResponse } from "next/server";
2+
import { getRecentlyPlayed } from "@/utils/spotify";
3+
4+
export const dynamic = "force-dynamic";
5+
6+
export async function GET() {
7+
try {
8+
const response = await getRecentlyPlayed(5);
9+
return NextResponse.json(response);
10+
} catch (error) {
11+
console.error("Error fetching recently played:", error);
12+
return NextResponse.json({ tracks: [] }, { status: 200 });
13+
}
14+
}

app/globals.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ body {
237237
color: #e7e7eb; /* murasakisuishiyou */
238238
}
239239

240-
.prose img {
240+
.prose img:not(.not-prose) {
241241
width: 100%;
242242
margin-bottom: 0;
243243
display: block;
@@ -252,6 +252,12 @@ body {
252252
box-shadow 0.1s ease;
253253
}
254254

255+
/* Spotify images - exclude from prose styles */
256+
img.not-prose {
257+
width: auto !important;
258+
height: auto !important;
259+
}
260+
255261
::selection {
256262
background: theme("colors.japanese.murasakisuishiyou / 30%");
257263
color: theme("colors.light.text");

app/now/page.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22
import React, { useState, useEffect } from "react";
3+
import SpotifyNowPlaying from "@/components/SpotifyNowPlaying";
34

45
const TIMEZONE = "America/Chicago";
56

@@ -57,9 +58,12 @@ const AboutPage = () => {
5758
more this year
5859
</li>
5960
</ul>
60-
<br></br>
61-
<p>Last updated: Sep 26, 2025</p>
61+
<p className="text-sm text-[#595857]/60 dark:text-[#DCDDDD]/60 mt-6">
62+
Last updated: Sep 26, 2025
63+
</p>
6264
</article>
65+
66+
<SpotifyNowPlaying />
6367
</div>
6468
);
6569
};

components/SpotifyNowPlaying.tsx

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
"use client";
2+
3+
import React from "react";
4+
import useSWR from "swr";
5+
import Image from "next/image";
6+
7+
interface NowPlayingData {
8+
isPlaying: boolean;
9+
title?: string;
10+
artist?: string;
11+
album?: string;
12+
albumImageUrl?: string;
13+
songUrl?: string;
14+
duration_ms?: number;
15+
progress_ms?: number;
16+
progressPercent?: number;
17+
releaseYear?: string;
18+
trackNumber?: number;
19+
totalTracks?: number;
20+
popularity?: number;
21+
}
22+
23+
interface RecentlyPlayedData {
24+
tracks: Array<{
25+
title: string;
26+
artist: string;
27+
album: string;
28+
albumImageUrl: string;
29+
songUrl: string;
30+
playedAt: string;
31+
}>;
32+
}
33+
34+
const fetcher = (url: string) => fetch(url).then((r) => r.json());
35+
36+
export default function SpotifyNowPlaying() {
37+
const { data: nowPlaying } = useSWR<NowPlayingData>(
38+
"/api/spotify/now-playing",
39+
fetcher,
40+
{
41+
refreshInterval: 60000,
42+
}
43+
);
44+
45+
const { data: recentlyPlayed } = useSWR<RecentlyPlayedData>(
46+
!nowPlaying?.isPlaying ? "/api/spotify/recently-played" : null,
47+
fetcher,
48+
{
49+
refreshInterval: 300000,
50+
}
51+
);
52+
53+
if (!nowPlaying && !recentlyPlayed) {
54+
return null;
55+
}
56+
57+
return (
58+
<div
59+
style={{
60+
marginTop: "32px",
61+
paddingLeft: "0px",
62+
paddingRight: "0px",
63+
fontFamily: "monospace",
64+
fontSize: "14px",
65+
color: "#888888",
66+
lineHeight: "1.6",
67+
}}
68+
>
69+
<div
70+
style={{
71+
borderTop: "1px solid #e0e0e0",
72+
paddingTop: "24px",
73+
}}
74+
>
75+
{nowPlaying?.isPlaying ? (
76+
<div>
77+
<div
78+
style={{ marginBottom: "12px", fontSize: "12px", opacity: 0.6 }}
79+
>
80+
♫ now
81+
</div>
82+
<div
83+
style={{ display: "flex", gap: "16px", alignItems: "flex-start" }}
84+
>
85+
{nowPlaying.albumImageUrl && (
86+
<a
87+
href={nowPlaying.songUrl}
88+
target="_blank"
89+
rel="noopener noreferrer"
90+
style={{ flexShrink: 0 }}
91+
>
92+
<Image
93+
src={nowPlaying.albumImageUrl}
94+
alt=""
95+
width={120}
96+
height={120}
97+
className="not-prose"
98+
style={{
99+
width: "120px",
100+
height: "120px",
101+
minWidth: "120px",
102+
maxWidth: "120px",
103+
borderRadius: "4px",
104+
opacity: 0.95,
105+
}}
106+
unoptimized
107+
/>
108+
</a>
109+
)}
110+
<a
111+
href={nowPlaying.songUrl}
112+
target="_blank"
113+
rel="noopener noreferrer"
114+
style={{
115+
textDecoration: "none",
116+
color: "inherit",
117+
display: "flex",
118+
flexDirection: "column",
119+
justifyContent: "flex-start",
120+
flex: 1,
121+
minWidth: 0,
122+
}}
123+
>
124+
<div
125+
style={{
126+
color: "#000000 !important" as any,
127+
fontSize: "16px !important" as any,
128+
opacity: "1 !important" as any,
129+
visibility: "visible !important" as any,
130+
marginBottom: "4px",
131+
fontWeight: "500",
132+
}}
133+
>
134+
{nowPlaying.title}
135+
</div>
136+
<div
137+
style={{
138+
fontSize: "13px",
139+
opacity: 0.6,
140+
}}
141+
>
142+
{nowPlaying.artist}
143+
{nowPlaying.releaseYear && ` · ${nowPlaying.releaseYear}`}
144+
</div>
145+
</a>
146+
</div>
147+
</div>
148+
) : recentlyPlayed?.tracks && recentlyPlayed.tracks.length > 0 ? (
149+
<div>
150+
<div
151+
style={{ marginBottom: "12px", fontSize: "12px", opacity: 0.6 }}
152+
>
153+
♫ recent
154+
</div>
155+
<div
156+
style={{ display: "flex", flexDirection: "column", gap: "14px" }}
157+
>
158+
{recentlyPlayed.tracks.slice(0, 3).map((track, index) => (
159+
<div
160+
key={index}
161+
style={{
162+
display: "flex",
163+
gap: "14px",
164+
alignItems: "flex-start",
165+
}}
166+
>
167+
{track.albumImageUrl && (
168+
<a
169+
href={track.songUrl}
170+
target="_blank"
171+
rel="noopener noreferrer"
172+
style={{ flexShrink: 0 }}
173+
>
174+
<Image
175+
src={track.albumImageUrl}
176+
alt=""
177+
width={80}
178+
height={80}
179+
className="not-prose"
180+
style={{
181+
width: "80px",
182+
height: "80px",
183+
minWidth: "80px",
184+
maxWidth: "80px",
185+
borderRadius: "4px",
186+
opacity: 0.9,
187+
}}
188+
unoptimized
189+
/>
190+
</a>
191+
)}
192+
<a
193+
href={track.songUrl}
194+
target="_blank"
195+
rel="noopener noreferrer"
196+
style={{
197+
textDecoration: "none",
198+
color: "inherit",
199+
display: "flex",
200+
flexDirection: "column",
201+
justifyContent: "flex-start",
202+
flex: 1,
203+
minWidth: 0,
204+
}}
205+
>
206+
<div
207+
style={{
208+
color: "#000000 !important" as any,
209+
fontSize: "14px !important" as any,
210+
opacity: "1 !important" as any,
211+
visibility: "visible !important" as any,
212+
marginBottom: "3px",
213+
}}
214+
>
215+
{track.title}
216+
</div>
217+
<div
218+
style={{
219+
fontSize: "12px",
220+
opacity: 0.6,
221+
}}
222+
>
223+
{track.artist}
224+
</div>
225+
</a>
226+
</div>
227+
))}
228+
</div>
229+
</div>
230+
) : null}
231+
</div>
232+
</div>
233+
);
234+
}

package-lock.json

Lines changed: 25 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"remark-gfm": "^4.0.0",
6060
"remark-math": "^6.0.0",
6161
"remark-parse": "^11.0.0",
62+
"swr": "^2.3.6",
6263
"tsx": "^4.19.2",
6364
"typescript": "4.9.3",
6465
"unified": "^11.0.5",

0 commit comments

Comments
 (0)