Skip to content

Commit 593711d

Browse files
committed
➕ADD: Snippets page
1 parent 659679d commit 593711d

File tree

7 files changed

+279
-0
lines changed

7 files changed

+279
-0
lines changed

components/FunctionCard.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Link from "next/link";
2+
import Image from "next/image";
3+
4+
export default function FunctionCard({
5+
title,
6+
description,
7+
slug,
8+
logo,
9+
...rest
10+
}) {
11+
return (
12+
<Link href={`/snippets/${slug}`}>
13+
<a
14+
className="border border-grey-200 dark:border-gray-900 rounded p-4 w-full"
15+
{...rest}
16+
>
17+
<Image
18+
alt={title}
19+
height={32}
20+
width={32}
21+
src={`/logos/${logo}`}
22+
className="rounded-full"
23+
/>
24+
<h3 className="text-lg font-bold text-left mt-2 text-gray-900 dark:text-gray-100">
25+
{title}
26+
</h3>
27+
<p className="mt-1 text-gray-700 dark:text-gray-400">{description}</p>
28+
</a>
29+
</Link>
30+
);
31+
}

data/snippets/spotify.mdx

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
title: "Spotify"
3+
description: "Show what you're listening to."
4+
logo: "spotify.png"
5+
---
6+
7+
```js
8+
import { getNowPlaying } from "../../lib/spotify";
9+
10+
export default async (_, res) => {
11+
const response = await getNowPlaying();
12+
13+
if (response.status === 204 || response.status > 400) {
14+
return res.status(200).json({ isPlaying: false });
15+
}
16+
17+
const song = await response.json();
18+
const isPlaying = song.is_playing;
19+
const title = song.item.name;
20+
const artist = song.item.artists.map((_artist) => _artist.name).join(", ");
21+
const album = song.item.album.name;
22+
const albumImageUrl = song.item.album.images[0].url;
23+
const songUrl = song.item.external_urls.spotify;
24+
25+
return res.status(200).json({
26+
album,
27+
albumImageUrl,
28+
artist,
29+
isPlaying,
30+
songUrl,
31+
title,
32+
});
33+
};
34+
```
35+
36+
```js
37+
// lib/spotify.js
38+
39+
import fetch from "isomorphic-unfetch";
40+
import querystring from "querystring";
41+
42+
const {
43+
SPOTIFY_CLIENT_ID: client_id,
44+
SPOTIFY_CLIENT_SECRET: client_secret,
45+
SPOTIFY_REFRESH_TOKEN: refresh_token,
46+
} = process.env;
47+
48+
const basic = Buffer.from(`${client_id}:${client_secret}`).toString("base64");
49+
const NOW_PLAYING_ENDPOINT = `https://api.spotify.com/v1/me/player/currently-playing`;
50+
const TOKEN_ENDPOINT = `https://accounts.spotify.com/api/token`;
51+
52+
const getAccessToken = async () => {
53+
const response = await fetch(TOKEN_ENDPOINT, {
54+
method: "POST",
55+
headers: {
56+
Authorization: `Basic ${basic}`,
57+
"Content-Type": "application/x-www-form-urlencoded",
58+
},
59+
body: querystring.stringify({
60+
grant_type: "refresh_token",
61+
refresh_token,
62+
}),
63+
});
64+
65+
return response.json();
66+
};
67+
68+
export const getNowPlaying = async () => {
69+
const { access_token } = await getAccessToken();
70+
71+
return fetch(NOW_PLAYING_ENDPOINT, {
72+
headers: {
73+
Authorization: `Bearer ${access_token}`,
74+
},
75+
});
76+
};
77+
```
78+
79+
## Usage
80+
81+
<Step number={1} title="Create Spotify Application" />
82+
83+
First, we need to create a Spotify application to give us credentials to authenticate with the API.
84+
85+
- Go to your [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/) and log in.
86+
- Click **Create an App**.
87+
- Fill out the name and description and click **create**.
88+
- Click **Show Client Secret**.
89+
- Save your Client ID and Secret. You'll need these soon.
90+
- Click **Edit Settings**.
91+
- Add `http://localhost:3000` as a redirect URI.
92+
93+
All done! You now have a properly configured Spotify application and the correct credentials to make requests.
94+
95+
<Step number={2} title="Authentication" />
96+
97+
There are a variety of ways to authenticate with the Spotify API, depending on your application. Since we only need permission granted _once_, we'll use the [Authorization Code Flow](https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow).
98+
99+
First, we'll have our application request authorization by logging in with whatever [scopes](https://developer.spotify.com/documentation/general/guides/authorization-guide/#list-of-scopes) we need.
100+
Here's an example of what the URL might look like.
101+
Swap out the `client_id` and scopes for your own.
102+
103+
```bash
104+
https://accounts.spotify.com/authorize?client_id=8e94bde7dd
105+
b84a1f7a0e51bf3bc95be8&response_type=code&redirect_uri=http
106+
%3A%2F%2Flocalhost:3000&scope=user-read-currently-playing%20
107+
user-top-read
108+
```
109+
110+
After authorizing, you'll be redirected back to your `redirect_uri`.
111+
In the URL, there's a `code` query parameter. Save this value.
112+
113+
```bash
114+
http://localhost:3000/callback?code=NApCCg..BkWtQ
115+
```
116+
117+
Next, we'll need to retrieve the refresh token. You'll need to generate a Base 64 encoded string containing the client ID and secret from earlier. You can use [this tool](https://www.base64encode.org/) to encode it online. The format should be `client_id:client_secret`.
118+
119+
```bash
120+
curl -H "Authorization: Basic <base64 encoded client_id:client_secret>"
121+
-d grant_type=authorization_code -d code=<code> -d redirect_uri=http%3A
122+
%2F%2Flocalhost:3000 https://accounts.spotify.com/api/token
123+
```
124+
125+
This will return a JSON response containing a `refresh_token`. This token is [valid indefinitely](https://github.com/spotify/web-api/issues/374) unless you revoke access, so we'll want to save this in an environment variable.
126+
127+
<Step number={3} title="Add Environment Variables" />
128+
129+
To securely access the API, we need to include the secret with each request.
130+
We also _do not_ want to commit secrets to git. Thus, we should use an environment variable.
131+
Learn how to add [environment variables in Vercel](https://vercel.com/docs/environment-variables).

layouts/snippets.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Image from "next/image";
2+
3+
import Container from "@/components/Container";
4+
5+
export default function SnippetLayout({ children, frontMatter }) {
6+
return (
7+
<Container
8+
title={`${frontMatter.title} - Code Snippet`}
9+
description="A collection of code snippets – including serverless functions, Node.js scripts, and CSS tricks."
10+
>
11+
<article className="flex flex-col justify-center items-start max-w-2xl mx-auto mb-16 w-full">
12+
<div className="flex justify-between w-full mb-8">
13+
<div>
14+
<h1 className="font-bold text-3xl md:text-5xl tracking-tight mb-4 text-black dark:text-white">
15+
{frontMatter.title}
16+
</h1>
17+
<p className="text-gray-700 dark:text-gray-300">
18+
{frontMatter.description}
19+
</p>
20+
</div>
21+
<div className="mt-2 sm:mt-0">
22+
<Image
23+
alt={frontMatter.title}
24+
height={48}
25+
width={48}
26+
src={`/logos/${frontMatter.logo}`}
27+
className="rounded-full"
28+
/>
29+
</div>
30+
</div>
31+
<div className="prose dark:prose-dark w-full">{children}</div>
32+
</article>
33+
</Container>
34+
);
35+
}

pages/snippets/[slug].js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import hydrate from "next-mdx-remote/hydrate";
2+
3+
import { getFiles, getFileBySlug } from "@/lib/mdx";
4+
import SnippetLayout from "@/layouts/snippets";
5+
import MDXComponents from "@/components/MDXComponents";
6+
7+
export default function Snippet({ mdxSource, frontMatter }) {
8+
const content = hydrate(mdxSource, {
9+
components: MDXComponents,
10+
});
11+
12+
return <SnippetLayout frontMatter={frontMatter}>{content}</SnippetLayout>;
13+
}
14+
15+
export async function getStaticPaths() {
16+
const snippets = await getFiles("snippets");
17+
18+
return {
19+
paths: snippets.map((s) => ({
20+
params: {
21+
slug: s.replace(/\.mdx/, ""),
22+
},
23+
})),
24+
fallback: false,
25+
};
26+
}
27+
28+
export async function getStaticProps({ params }) {
29+
const snippet = await getFileBySlug("snippets", params.slug);
30+
31+
return { props: snippet };
32+
}

pages/snippets/index.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Container from "@/components/Container";
2+
import FunctionCard from "@/components/FunctionCard";
3+
import { getAllFilesFrontMatter } from "@/lib/mdx";
4+
5+
export default function Snippets({ snippets }) {
6+
return (
7+
<Container
8+
title="Snippets – Manu Arora"
9+
description="Reusable code snippets that contains API integrations, custom CSS or something I find cool."
10+
>
11+
<div className="flex flex-col justify-center items-start max-w-2xl mx-auto mb-16">
12+
<h1 className="font-bold text-3xl md:text-5xl tracking-tight mb-4 text-black dark:text-white">
13+
Snippets
14+
</h1>
15+
<p className="text-gray-600 dark:text-gray-400 mb-4">
16+
Reusable{" "}
17+
<span className="bg-gray-100 border rounded-md px-1 py-0.5 tracking-tight dark:text-gray-300 dark:bg-gray-700">
18+
code snippets
19+
</span>{" "}
20+
that can be easily integrated in your application 🧩. The page
21+
contains functions and code snippets which can be used on your
22+
webpage.
23+
</p>
24+
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 my-2 w-full mt-4">
25+
{snippets.map((snippet) => (
26+
<FunctionCard
27+
key={snippet.slug}
28+
title={snippet.title}
29+
slug={snippet.slug}
30+
logo={snippet.logo}
31+
description={snippet.description}
32+
/>
33+
))}
34+
<FunctionCard
35+
title="Adding more"
36+
slug="/snippets"
37+
description="Adding more each day.."
38+
logo="react.png"
39+
/>
40+
</div>
41+
</div>
42+
</Container>
43+
);
44+
}
45+
46+
export async function getStaticProps() {
47+
const snippets = await getAllFilesFrontMatter("snippets");
48+
49+
return { props: { snippets } };
50+
}

public/logos/react.png

2.58 KB
Loading

public/logos/spotify.png

2.01 KB
Loading

0 commit comments

Comments
 (0)