From 231488859ced6273a5faabb30afa74097b71781a Mon Sep 17 00:00:00 2001 From: Alvalen Date: Fri, 19 Apr 2024 19:21:34 +0700 Subject: [PATCH] feat: add spotify widget --- .env.example | 3 ++ .gitignore | 3 +- app/(root)/page.jsx | 2 +- app/about/(spotify)/animation.jsx | 25 ++++++++++ app/about/(spotify)/card.jsx | 78 +++++++++++++++++++++++++++++++ app/about/(spotify)/fetch.js | 53 +++++++++++++++++++++ app/about/page.jsx | 6 +-- next.config.js | 3 ++ 8 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 .env.example create mode 100644 app/about/(spotify)/animation.jsx create mode 100644 app/about/(spotify)/card.jsx create mode 100644 app/about/(spotify)/fetch.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..324a37d --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +SPOTIFY_CLIENT_ID= +SPOTIFY_CLIENT_SECRET= +SPOTIFY_REFRESH_TOKEN= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 62190d7..194b389 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -.env \ No newline at end of file +.env +.env.local \ No newline at end of file diff --git a/app/(root)/page.jsx b/app/(root)/page.jsx index ab4628b..bb9cb4f 100644 --- a/app/(root)/page.jsx +++ b/app/(root)/page.jsx @@ -1,7 +1,7 @@ "use client"; import ReactFullpage from "@fullpage/react-fullpage"; import Image from "next/legacy/image"; -import "../globals.css"; +// import "../globals.css"; import { motion } from "framer-motion"; import Link from "next/link"; diff --git a/app/about/(spotify)/animation.jsx b/app/about/(spotify)/animation.jsx new file mode 100644 index 0000000..769fc77 --- /dev/null +++ b/app/about/(spotify)/animation.jsx @@ -0,0 +1,25 @@ +import { motion } from "framer-motion"; + +const PlayingAnimation = () => { + return ( +
+ + + +
+ ); +}; + +export default PlayingAnimation; diff --git a/app/about/(spotify)/card.jsx b/app/about/(spotify)/card.jsx new file mode 100644 index 0000000..735c674 --- /dev/null +++ b/app/about/(spotify)/card.jsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from "react"; +import getNowPlayingItem from "./fetch"; +import PlayingAnimation from "./animation"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSpotify } from "@fortawesome/free-brands-svg-icons"; +import Image from "next/image"; + +const Card = () => { + const [loading, setLoading] = useState(true); + const [result, setResult] = useState({}); + +useEffect(() => { + const fetchData = async () => { + const results = await Promise.all([getNowPlayingItem()]); + console.log(results); + setResult(results[0]); + setLoading(false); + }; + fetchData(); // Fetch data immediately on component mount + + const intervalId = setInterval(fetchData, 60 * 1000); // ! Fetch data every minute: temporary + + return () => clearInterval(intervalId); +}, []); + + return ( +
+ {loading ? ( +
+ {/* Replace this with your own loading spinner */} +
+
+ ) : ( +
+ backgroundImage +
+ +

+ {result.isPlaying + ? "Now playing" + : "Currently offline"} +

+ {result.isPlaying && } +
+ {result.isPlaying && ( +
+
+
+ + {result.title} + +

+ {result.artist} +

+
+
+
+ )} +
+ )} +
+ ); +}; + +export default Card; + diff --git a/app/about/(spotify)/fetch.js b/app/about/(spotify)/fetch.js new file mode 100644 index 0000000..2866dcf --- /dev/null +++ b/app/about/(spotify)/fetch.js @@ -0,0 +1,53 @@ +import querystring from "querystring"; +const TOKEN_ENDPOINT = `https://accounts.spotify.com/api/token`; +const NOW_PLAYING_ENDPOINT = `https://api.spotify.com/v1/me/player/currently-playing`; +const client_id = process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID; +const client_secret = process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_SECRET; +const refresh_token = process.env.NEXT_PUBLIC_SPOTIFY_REFRESH_TOKEN; + +const basic = Buffer.from(`${client_id}:${client_secret}`).toString("base64"); +const getAccessToken = async () => { + const response = await fetch(TOKEN_ENDPOINT, { + method: "POST", + headers: { + Authorization: `Basic ${basic}`, + "Content-Type": "application/x-www-form-urlencoded", + }, + body: querystring.stringify({ + grant_type: "refresh_token", + refresh_token, + }), + }); + + return response.json(); +}; + +export const getNowPlaying = async () => { + const { access_token } = await getAccessToken(); + return fetch(NOW_PLAYING_ENDPOINT, { + headers: { + Authorization: `Bearer ${access_token}`, + }, + }); +}; + +export default async function getNowPlayingItem() { + const response = await getNowPlaying(); + if (response.status === 204 || response.status > 400) { + return false; + } + const song = await response.json(); + const albumImageUrl = song.item.album.images[0].url; + const artist = song.item.artists.map((_artist) => _artist.name).join(", "); + const isPlaying = song.is_playing; + const songUrl = song.item.external_urls.spotify; + const title = song.item.name; + + return { + albumImageUrl, + artist, + isPlaying, + songUrl, + title, + }; +} \ No newline at end of file diff --git a/app/about/page.jsx b/app/about/page.jsx index bd8b1d2..7d8b095 100644 --- a/app/about/page.jsx +++ b/app/about/page.jsx @@ -1,6 +1,5 @@ "use client"; import { useEffect } from "react"; -import Link from "next/link"; import { motion } from "framer-motion"; import Button from "@/components/Button"; import Image from "next/legacy/image"; @@ -8,19 +7,19 @@ import FixedButon from "@/components/FixedButton"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons"; - +import Card from "./(spotify)/card.jsx"; // images import Me1 from "@/public/img/me1.jpg"; import Me2 from "@/public/img/me2.jpg"; import Me3 from "@/public/img/me3.jpg"; import Hr from "@/components/Hr"; + export default function Page() { useEffect(() => { window.scrollTo(0, 0); }, []); - const handleBack = () => { window.location.href = "/"; }; @@ -207,6 +206,7 @@ export default function Page() { connect and explore this ever-evolving world of tech together!

+
diff --git a/next.config.js b/next.config.js index d30af7d..47f6ef3 100644 --- a/next.config.js +++ b/next.config.js @@ -3,6 +3,9 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({ }); module.exports = withBundleAnalyzer({ // your Next.js configuration + images: { + domains: ["i.scdn.co"], + }, webpack: (config, options) => { config.module.rules.push({ test: /\.pdf$/i,