Skip to content

Commit ec084e4

Browse files
Pull in latest JSR posts to homepage (#496)
Now that there's a JSON API for Deno blog posts that allows us to filter by tags, we can pull in the three latest JSR blog posts and display them on the JSR homepage. **NOTE:** because of CORS, this won't work locally. The images will load properly once live, however. (You can run your own `dotcom` locally, update its `_middleware.ts` file to allow your local JSR domain, and replace all image URLs to point to your local dotcom localhost domain instead of `deno.com` to verify.) <img width="1279" alt="image" src="https://github.com/jsr-io/jsr/assets/22334764/cd2c2675-1cbf-4098-9ba0-1762c674d706">
1 parent 907b3c2 commit ec084e4

File tree

3 files changed

+101
-14
lines changed

3 files changed

+101
-14
lines changed

frontend/components/ListPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ export function ListPanel(
1717
<div class="w-full">
1818
<div class="mb-2">
1919
{title && (
20-
<h2 class="text-xl md:text-2xl font-semibold">
20+
<h3 class="text-lg md:text-xl font-semibold">
2121
{title}
22-
</h2>
22+
</h3>
2323
)}
2424
{subtitle && (
2525
<div class="text-base text-gray-500">

frontend/components/NewsCard.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
2+
export function NewsCard({
3+
title,
4+
description,
5+
image,
6+
url,
7+
}: {
8+
title: string;
9+
description: string;
10+
image: string;
11+
url: string;
12+
}) {
13+
return (
14+
<li class="group border-1.5 border-jsr-cyan-950 rounded list-none overflow-hidden hover:border-jsr-cyan-400 focus-within:border-jsr-cyan-400 transition-colors duration-150">
15+
<a
16+
href={url}
17+
class="h-full flex flex-col justify-stretch cursor-pointer"
18+
tabIndex={0}
19+
>
20+
<img
21+
src={image}
22+
crossOrigin={"anonymous"}
23+
alt=""
24+
class="w-full h-48 object-cover border-b-1.5 border-jsr-cyan-950 group-hover:border-jsr-cyan-400 group-focus-within:border-jsr-cyan-400 transition-colors duration-150"
25+
/>
26+
<div class="p-4 flex flex-grow flex-col gap-4">
27+
<h3 class="text-xl lg:text-2xl font-semibold !leading-tight text-balance">
28+
{title}
29+
</h3>
30+
<p class="text-sm">
31+
{description}
32+
</p>
33+
</div>
34+
</a>
35+
</li>
36+
);
37+
}

frontend/routes/index.tsx

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,18 @@ import { Head } from "$fresh/runtime.ts";
99
import { ComponentChildren } from "preact";
1010
import { HomepageHero } from "../components/HomepageHero.tsx";
1111
import { Logo } from "../components/Logo.tsx";
12+
import { NewsCard } from "../components/NewsCard.tsx";
13+
14+
interface Post {
15+
title: string;
16+
description: string;
17+
image: string;
18+
link: string;
19+
}
1220

1321
interface Data {
1422
stats: Stats;
23+
posts: Post[];
1524
}
1625

1726
export default function Home({ data }: PageProps<Data>) {
@@ -32,17 +41,46 @@ export default function Home({ data }: PageProps<Data>) {
3241
apiKey={Deno.env.get("ORAMA_PACKAGE_PUBLIC_API_KEY")}
3342
indexId={Deno.env.get("ORAMA_PACKAGE_PUBLIC_INDEX_ID")}
3443
/>
35-
<div class="grid grid-cols-1 gap-8 lg:grid-cols-3">
36-
<ListPanel title="Featured Packages">
37-
{data.stats.featured.map(PackageToPanelEntry)}
38-
</ListPanel>
39-
<ListPanel title="Recent updates">
40-
{data.stats.updated.map(PackageVersionToPanelEntry)}
41-
</ListPanel>
42-
<ListPanel title="New Packages">
43-
{data.stats.newest.map(PackageToPanelEntry)}
44-
</ListPanel>
45-
</div>
44+
{data.posts.length > 0 && (
45+
<section class="flex flex-col gap-4 mb-16 md:mb-32">
46+
<h2 class="text-3xl md:text-4xl mb-4 md:mb-8 font-semibold text-center">
47+
Latest updates
48+
</h2>
49+
<ul class="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-8">
50+
{data?.posts?.slice(0, 3).map((post) => (
51+
<NewsCard
52+
image={post.image}
53+
title={post.title}
54+
description={post.description}
55+
url={post.link}
56+
/>
57+
))}
58+
</ul>
59+
<a
60+
href="https://deno.com/blog?tag=jsr"
61+
class="underline block mt-4 w-full text-center"
62+
>
63+
More JSR updates <span aria-hidden="true">&rsaquo;</span>
64+
</a>
65+
</section>
66+
)}
67+
68+
<section class="flex flex-col gap-4">
69+
<h2 class="text-3xl md:text-4xl mb-4 md:mb-8 font-semibold text-center">
70+
Packages
71+
</h2>
72+
<div class="grid grid-cols-1 gap-8 lg:grid-cols-3">
73+
<ListPanel title="Featured">
74+
{data.stats.featured.map(PackageToPanelEntry)}
75+
</ListPanel>
76+
<ListPanel title="Recently updated">
77+
{data.stats.updated.map(PackageVersionToPanelEntry)}
78+
</ListPanel>
79+
<ListPanel title="New to JSR">
80+
{data.stats.newest.map(PackageToPanelEntry)}
81+
</ListPanel>
82+
</div>
83+
</section>
4684

4785
<h2
4886
class="font-semibold text-5xl md:text-7xl lg:text-center mt-16 md:mt-24 lg:mt-48 lg:mb-16"
@@ -231,8 +269,20 @@ export const handler: Handlers<Data, State> = {
231269
const statsResp = await ctx.state.api.get<Stats>(path`/stats`, undefined, {
232270
anonymous: true,
233271
});
272+
273+
let posts: Post[] = [];
274+
try {
275+
const jsrPosts = await fetch("https://deno.com/blog/json?tag=JSR");
276+
if (jsrPosts.ok) {
277+
posts = await jsrPosts.json() as Post[];
278+
console.log(posts);
279+
}
280+
} catch (e) {
281+
// ignore
282+
}
283+
234284
if (!statsResp.ok) throw statsResp; // gracefully handle this
235-
return ctx.render({ stats: statsResp.data }, {
285+
return ctx.render({ stats: statsResp.data, posts: posts || [] }, {
236286
headers: ctx.state.api.hasToken()
237287
? undefined
238288
: { "Cache-Control": "public, s-maxage=60" },

0 commit comments

Comments
 (0)