Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e13a3df

Browse files
committed
Latest sponsor
1 parent 1e15b06 commit e13a3df

File tree

6 files changed

+171
-1
lines changed

6 files changed

+171
-1
lines changed
Lines changed: 18 additions & 0 deletions
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

Lines changed: 95 additions & 0 deletions
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

Lines changed: 4 additions & 1 deletion
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-2" />
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 45 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /