Skip to content

Commit e0c0dea

Browse files
authored
Merge pull request #491 from code-hike/next
✨ Update website ✨
2 parents 1931085 + 3733049 commit e0c0dea

File tree

7 files changed

+189
-1
lines changed

7 files changed

+189
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { revalidatePath } from "next/cache"
2+
3+
export async function POST(request: Request) {
4+
try {
5+
const text = await request.text()
6+
console.log("[GitHub] Webhook received", text)
7+
8+
revalidatePath("/")
9+
} catch (error: any) {
10+
return new Response(`Webhook error: ${error.message}`, {
11+
status: 400,
12+
})
13+
}
14+
15+
return new Response("Success!", {
16+
status: 200,
17+
})
18+
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { revalidatePath } from "next/cache"
2+
3+
export async function POST(request: Request) {
4+
try {
5+
const text = await request.text()
6+
console.log("[GitHub] Webhook received", text)
7+
8+
revalidatePath("/test")
9+
} catch (error: any) {
10+
return new Response(`Webhook error: ${error.message}`, {
11+
status: 400,
12+
})
13+
}
14+
15+
return new Response("Success!", {
16+
status: 200,
17+
})
18+
}

apps/web/app/landing/sponsors.tsx

+95
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import Image from "next/image"
88
import Link from "next/link"
99
import sponsorData from "./sponsors.json"
1010
import { Check, CheckCheck, GithubIcon, Heart, Star } from "lucide-react"
11+
import { cn } from "@/lib/utils"
12+
import { TimeAgo } from "@/components/time-ago"
1113

1214
export function Pricing() {
1315
const current = 625
@@ -84,6 +86,65 @@ export function Pricing() {
8486
)
8587
}
8688

89+
export async function LatestSponsor({ className }: { className?: string }) {
90+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN
91+
if (!GITHUB_TOKEN) {
92+
throw new Error("Missing process.env.GITHUB_TOKEN")
93+
}
94+
95+
const r = await fetch("https://api.github.com/graphql", {
96+
method: "POST",
97+
body: JSON.stringify({ query: latestSponsorsQuery }),
98+
headers: { Authorization: "bearer " + GITHUB_TOKEN },
99+
})
100+
if (!r.ok) {
101+
throw new Error(`Failed to fetch: ${r.status} ${r.statusText}`)
102+
}
103+
const { data, errors } = await r.json()
104+
if (errors) {
105+
throw new Error(JSON.stringify(errors))
106+
}
107+
108+
const sponsors = data.organization.sponsorshipsAsMaintainer.edges
109+
if (!sponsors.length) {
110+
throw new Error("No sponsors found")
111+
}
112+
113+
const latest = sponsors[0].node
114+
115+
return (
116+
<a
117+
href={`https://github.com/${latest.sponsorEntity.login}`}
118+
className={cn(
119+
className,
120+
"rounded bg-zinc-50 dark:bg-zinc-900 p-3 flex gap-3 border border-zinc-200/50 dark:border-zinc-700/50 hover:border-zinc-200 dark:hover:border-zinc-700 transition-colors w-96 md:w-full mx-auto",
121+
)}
122+
>
123+
<Image
124+
className="rounded my-0 max-h-20"
125+
src={`${latest.sponsorEntity.avatarUrl}`}
126+
alt={latest.sponsorEntity.name}
127+
height={80}
128+
width={80}
129+
placeholder="empty"
130+
/>
131+
<div className="flex-1 flex flex-col justify-between">
132+
{/* <div>{new Date().toString()}</div> */}
133+
<div className="text-primary/70 text-sm">
134+
Latest sponsor · <TimeAgo date={latest.createdAt} />
135+
</div>
136+
<div className="text-2xl font-bold">
137+
{latest.sponsorEntity.name || latest.sponsorEntity.login}
138+
</div>
139+
<div className="text-primary/90 text-sm">
140+
Sponsoring <strong>{latest.tier.name}</strong>{" "}
141+
</div>
142+
</div>
143+
{/* <pre>{JSON.stringify(latest, null, 2)}</pre> */}
144+
</a>
145+
)
146+
}
147+
87148
export function TopSponsors({
88149
title = "Top Sponsors",
89150
scale = 1,
@@ -440,3 +501,37 @@ function BrowserStack() {
440501
</svg>
441502
)
442503
}
504+
505+
const latestSponsorsQuery = `query {
506+
organization(login: "code-hike") {
507+
sponsorshipsAsMaintainer(first: 50, orderBy: {field: CREATED_AT, direction: DESC}, activeOnly: false) {
508+
edges {
509+
node {
510+
createdAt
511+
privacyLevel
512+
tier {
513+
name
514+
monthlyPriceInDollars
515+
}
516+
sponsorEntity {
517+
... on User {
518+
login
519+
name
520+
avatarUrl
521+
websiteUrl
522+
location
523+
}
524+
... on Organization {
525+
login
526+
name
527+
avatarUrl
528+
websiteUrl
529+
location
530+
}
531+
}
532+
}
533+
}
534+
}
535+
}
536+
}
537+
`

apps/web/app/page.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Link from "next/link"
22
import {
33
AllSponsors,
4+
LatestSponsor,
45
PoweredBy,
56
Pricing,
67
TopSponsors,
@@ -38,7 +39,9 @@ export default function HomePage() {
3839

3940
<Pricing />
4041

41-
<AllSponsors className="mb-24" title="Sponsors" />
42+
<h3 className="text-center pb-8 text-primary/60 text-lg">Sponsors</h3>
43+
<LatestSponsor className="mb-4" />
44+
<AllSponsors cta="Become a sponsor" className="mb-24" />
4245

4346
<PoweredBy className="mb-8 text-center flex items-center justify-center gap-4 w-full flex-wrap" />
4447
</main>

apps/web/app/test/page.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import { LatestSponsor } from "../landing/sponsors"
12
import Content from "./content.md"
23
import { Code } from "@/components/code"
34

45
export default function Page() {
56
return (
67
<div className="m-4 prose">
8+
<div>{new Date().toString()}</div>
9+
<LatestSponsor />
710
<Content components={{ Code }} />
811
</div>
912
)

apps/web/components/time-ago.tsx

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client"
2+
3+
export function TimeAgo({ date }: { date: string }) {
4+
const time = new Date(date)
5+
return (
6+
<time dateTime={time.toString()} title={time.toString()}>
7+
{getTimeAgo(time)}
8+
</time>
9+
)
10+
}
11+
12+
const MINUTE = 60
13+
const HOUR = MINUTE * 60
14+
const DAY = HOUR * 24
15+
const WEEK = DAY * 7
16+
const MONTH = DAY * 30
17+
const YEAR = DAY * 365
18+
19+
function getTimeAgo(date: Date) {
20+
const secondsAgo = Math.round((Date.now() - Number(date)) / 1000)
21+
22+
if (secondsAgo < MINUTE) {
23+
return secondsAgo + ` second${secondsAgo !== 1 ? "s" : ""} ago`
24+
}
25+
26+
let divisor
27+
let unit = ""
28+
29+
if (secondsAgo < HOUR) {
30+
;[divisor, unit] = [MINUTE, "minute"]
31+
} else if (secondsAgo < DAY) {
32+
;[divisor, unit] = [HOUR, "hour"]
33+
} else if (secondsAgo < WEEK) {
34+
;[divisor, unit] = [DAY, "day"]
35+
} else if (secondsAgo < MONTH) {
36+
;[divisor, unit] = [WEEK, "week"]
37+
} else if (secondsAgo < YEAR) {
38+
;[divisor, unit] = [MONTH, "month"]
39+
} else {
40+
;[divisor, unit] = [YEAR, "year"]
41+
}
42+
43+
const count = Math.floor(secondsAgo / divisor)
44+
return `${count} ${unit}${count > 1 ? "s" : ""} ago`
45+
}

apps/web/next.config.mjs

+6
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ const config = {
4848
port: "",
4949
pathname: "/**",
5050
},
51+
{
52+
protocol: "https",
53+
hostname: "avatars.githubusercontent.com",
54+
port: "",
55+
pathname: "/**",
56+
},
5157
],
5258
},
5359
}

0 commit comments

Comments
 (0)