Skip to content

Commit 400eb50

Browse files
feat(web): Improve GitHub stats and add slash separators
Implement dynamic GitHub repository stats fetching using React Query and replace hardcoded values. Extract slash separator pattern into a reusable component to clean up section transitions across the landing page.
1 parent 9b3d975 commit 400eb50

File tree

9 files changed

+208
-119
lines changed

9 files changed

+208
-119
lines changed

apps/web/src/components/github-open-source.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { cn } from "@hypr/utils";
22

33
import { Icon } from "@iconify-icon/react";
4+
import { useQuery } from "@tanstack/react-query";
45

56
const ORG_REPO = "fastrepl/hyprnote";
67

@@ -136,15 +137,26 @@ function GridRow({
136137
}
137138

138139
export function GitHubOpenSource() {
139-
const STARS_COUNT = 6419;
140-
const FORKS_COUNT = 396;
140+
const LAST_SEEN_STARS = 6419;
141+
const LAST_SEEN_FORKS = 396;
142+
143+
const githubStats = useQuery({
144+
queryKey: ["github-stats"],
145+
queryFn: async () => {
146+
const response = await fetch(`https://api.github.com/repos/${ORG_REPO}`);
147+
const data = await response.json();
148+
return {
149+
stars: data.stargazers_count ?? LAST_SEEN_STARS,
150+
forks: data.forks_count ?? LAST_SEEN_FORKS,
151+
};
152+
},
153+
});
154+
155+
const STARS_COUNT = githubStats.data?.stars ?? LAST_SEEN_STARS;
156+
const FORKS_COUNT = githubStats.data?.forks ?? LAST_SEEN_FORKS;
141157

142158
return (
143159
<section className="border-t border-neutral-100">
144-
<div
145-
className="border-b border-neutral-100 bg-neutral-50 h-4"
146-
style={{ backgroundImage: "url(/patterns/slash.svg)" }}
147-
/>
148160
<div className="px-4 py-8">
149161
<div className="lg:hidden max-w-4xl mx-auto">
150162
<OpenSourceButton showStars={true} starCount={STARS_COUNT} />
@@ -280,10 +292,6 @@ export function GitHubOpenSource() {
280292
</div>
281293
</div>
282294
</div>
283-
<div
284-
className="border-t border-neutral-100 bg-neutral-50 h-4"
285-
style={{ backgroundImage: "url(/patterns/slash.svg)" }}
286-
/>
287295
</section>
288296
);
289297
}

apps/web/src/components/github-stars.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,26 @@ import { Icon } from "@iconify-icon/react";
44
import { useQuery } from "@tanstack/react-query";
55

66
export function GithubStars() {
7-
const LAST_SEEN = 6400;
7+
const LAST_SEEN_STARS = 6400;
8+
const LAST_SEEN_FORKS = 396;
89
const ORG_REPO = "fastrepl/hyprnote";
910

10-
const star = useQuery({
11-
queryKey: ["github-stars"],
11+
const githubStats = useQuery({
12+
queryKey: ["github-stats"],
1213
queryFn: async () => {
1314
const response = await fetch(`https://api.github.com/repos/${ORG_REPO}`);
1415
const data = await response.json();
15-
return data.stargazers_count ?? LAST_SEEN;
16+
return {
17+
stars: data.stargazers_count ?? LAST_SEEN_STARS,
18+
forks: data.forks_count ?? LAST_SEEN_FORKS,
19+
};
1620
},
1721
});
1822

1923
const render = (n: number) => n > 1000 ? `${(n / 1000).toFixed(1)}k` : n;
2024

25+
const starCount = githubStats.data?.stars ?? LAST_SEEN_STARS;
26+
2127
return (
2228
<a href={`https://github.com/${ORG_REPO}`} target="_blank">
2329
<button
@@ -29,7 +35,7 @@ export function GithubStars() {
2935
])}
3036
>
3137
<Icon icon="mdi:github" className="text-xl" />
32-
<span className="ml-2">{star.data ? render(star.data) : render(LAST_SEEN)} stars</span>
38+
<span className="ml-2">{render(starCount)} stars</span>
3339
</button>
3440
</a>
3541
);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function SlashSeparator() {
2+
return (
3+
<div
4+
className="border-b border-neutral-100 bg-neutral-50 h-4"
5+
style={{ backgroundImage: "url(/patterns/slash.svg)" }}
6+
/>
7+
);
8+
}

apps/web/src/routeTree.gen.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Route as XRouteImport } from './routes/x'
1313
import { Route as LinkedinRouteImport } from './routes/linkedin'
1414
import { Route as JoinWaitlistRouteImport } from './routes/join-waitlist'
1515
import { Route as GithubRouteImport } from './routes/github'
16+
import { Route as FoundersRouteImport } from './routes/founders'
1617
import { Route as DiscordRouteImport } from './routes/discord'
1718
import { Route as CalRouteImport } from './routes/cal'
1819
import { Route as AuthRouteImport } from './routes/auth'
@@ -58,6 +59,11 @@ const GithubRoute = GithubRouteImport.update({
5859
path: '/github',
5960
getParentRoute: () => rootRouteImport,
6061
} as any)
62+
const FoundersRoute = FoundersRouteImport.update({
63+
id: '/founders',
64+
path: '/founders',
65+
getParentRoute: () => rootRouteImport,
66+
} as any)
6167
const DiscordRoute = DiscordRouteImport.update({
6268
id: '/discord',
6369
path: '/discord',
@@ -182,6 +188,7 @@ export interface FileRoutesByFullPath {
182188
'/auth': typeof AuthRoute
183189
'/cal': typeof CalRoute
184190
'/discord': typeof DiscordRoute
191+
'/founders': typeof FoundersRoute
185192
'/github': typeof GithubRoute
186193
'/join-waitlist': typeof JoinWaitlistRoute
187194
'/linkedin': typeof LinkedinRoute
@@ -211,6 +218,7 @@ export interface FileRoutesByTo {
211218
'/auth': typeof AuthRoute
212219
'/cal': typeof CalRoute
213220
'/discord': typeof DiscordRoute
221+
'/founders': typeof FoundersRoute
214222
'/github': typeof GithubRoute
215223
'/join-waitlist': typeof JoinWaitlistRoute
216224
'/linkedin': typeof LinkedinRoute
@@ -240,6 +248,7 @@ export interface FileRoutesById {
240248
'/auth': typeof AuthRoute
241249
'/cal': typeof CalRoute
242250
'/discord': typeof DiscordRoute
251+
'/founders': typeof FoundersRoute
243252
'/github': typeof GithubRoute
244253
'/join-waitlist': typeof JoinWaitlistRoute
245254
'/linkedin': typeof LinkedinRoute
@@ -271,6 +280,7 @@ export interface FileRouteTypes {
271280
| '/auth'
272281
| '/cal'
273282
| '/discord'
283+
| '/founders'
274284
| '/github'
275285
| '/join-waitlist'
276286
| '/linkedin'
@@ -300,6 +310,7 @@ export interface FileRouteTypes {
300310
| '/auth'
301311
| '/cal'
302312
| '/discord'
313+
| '/founders'
303314
| '/github'
304315
| '/join-waitlist'
305316
| '/linkedin'
@@ -328,6 +339,7 @@ export interface FileRouteTypes {
328339
| '/auth'
329340
| '/cal'
330341
| '/discord'
342+
| '/founders'
331343
| '/github'
332344
| '/join-waitlist'
333345
| '/linkedin'
@@ -359,6 +371,7 @@ export interface RootRouteChildren {
359371
AuthRoute: typeof AuthRoute
360372
CalRoute: typeof CalRoute
361373
DiscordRoute: typeof DiscordRoute
374+
FoundersRoute: typeof FoundersRoute
362375
GithubRoute: typeof GithubRoute
363376
JoinWaitlistRoute: typeof JoinWaitlistRoute
364377
LinkedinRoute: typeof LinkedinRoute
@@ -397,6 +410,13 @@ declare module '@tanstack/react-router' {
397410
preLoaderRoute: typeof GithubRouteImport
398411
parentRoute: typeof rootRouteImport
399412
}
413+
'/founders': {
414+
id: '/founders'
415+
path: '/founders'
416+
fullPath: '/founders'
417+
preLoaderRoute: typeof FoundersRouteImport
418+
parentRoute: typeof rootRouteImport
419+
}
400420
'/discord': {
401421
id: '/discord'
402422
path: '/discord'
@@ -639,6 +659,7 @@ const rootRouteChildren: RootRouteChildren = {
639659
AuthRoute: AuthRoute,
640660
CalRoute: CalRoute,
641661
DiscordRoute: DiscordRoute,
662+
FoundersRoute: FoundersRoute,
642663
GithubRoute: GithubRoute,
643664
JoinWaitlistRoute: JoinWaitlistRoute,
644665
LinkedinRoute: LinkedinRoute,

apps/web/src/routes/_view/download.tsx

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { cn } from "@hypr/utils";
22

33
import { Icon } from "@iconify-icon/react";
4-
import { createFileRoute } from "@tanstack/react-router";
4+
import { createFileRoute, Link } from "@tanstack/react-router";
5+
6+
import { SlashSeparator } from "@/components/slash-separator";
57

68
export const Route = createFileRoute("/_view/download")({
79
component: Component,
@@ -21,16 +23,13 @@ function Component() {
2123
])}
2224
>
2325
<div className="py-3 px-4 mx-auto max-w-6xl border-x border-neutral-100 w-full">
24-
<Icon icon="mdi:information-outline" className="text-base" />
25-
<span>
26-
Mac (Apple Silicon) features on-device speech-to-text. Other platforms coming soon without on-device
27-
processing.
28-
</span>
26+
Mac (Apple Silicon) features on-device speech-to-text. Other platforms coming soon without on-device
27+
processing.
2928
</div>
3029
</div>
3130

32-
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-12 bg-white border-x border-neutral-100">
33-
<section className="py-16">
31+
<div className="max-w-6xl mx-auto py-12 bg-white border-x border-neutral-100">
32+
<section className="py-16 px-4 sm:px-6">
3433
<div className="space-y-6 max-w-2xl mx-auto text-center mb-16">
3534
<h1 className="text-4xl sm:text-5xl font-serif tracking-tight text-stone-600">
3635
Download Hyprnote
@@ -92,6 +91,14 @@ function Component() {
9291
</div>
9392
</div>
9493
</section>
94+
95+
<SlashSeparator />
96+
97+
<FAQSection />
98+
99+
<SlashSeparator />
100+
101+
<CTASection />
95102
</div>
96103
</div>
97104
);
@@ -109,7 +116,7 @@ function DownloadCard({
109116
available: boolean;
110117
}) {
111118
return (
112-
<div className="flex flex-col items-center p-6 rounded-xl border border-neutral-100 bg-white hover:bg-stone-50 transition-all duration-200">
119+
<div className="flex flex-col items-center p-6 rounded-sm border border-neutral-100 bg-white hover:bg-stone-50 transition-all duration-200">
113120
<Icon icon={iconName} className="text-5xl text-neutral-700 mb-4" />
114121
<p className="text-sm text-neutral-600 mb-6 text-center">{spec}</p>
115122

@@ -131,9 +138,84 @@ function DownloadCard({
131138
disabled
132139
className="w-full px-4 h-11 bg-neutral-100 text-neutral-400 rounded-full font-medium cursor-not-allowed"
133140
>
134-
Coming Soon
141+
Planned
135142
</button>
136143
)}
137144
</div>
138145
);
139146
}
147+
148+
function FAQSection() {
149+
const faqs = [
150+
{
151+
question: "Which platforms are currently supported?",
152+
answer:
153+
"macOS 14.2+ with Apple Silicon is currently available. macOS Intel and Windows are planned for January 2026, Linux for February 2026, and iOS/Android for April 2026. Please note that these dates are subject to change and may be delayed.",
154+
},
155+
{
156+
question: "What's special about the Mac version?",
157+
answer:
158+
"The Mac (Apple Silicon) version features on-device speech-to-text, ensuring your audio never leaves your device for complete privacy.",
159+
},
160+
{
161+
question: "Do I need an internet connection?",
162+
answer:
163+
"For the free version with local transcription on Mac, no internet is required. Cloud features in the Pro plan require an internet connection.",
164+
},
165+
{
166+
question: "How do I get started after downloading?",
167+
answer:
168+
"Simply install the app and launch it. For the free version, you can optionally bring your own API keys for LLM features. Check our documentation for detailed setup instructions.",
169+
},
170+
];
171+
172+
return (
173+
<section className="py-16 px-4 laptop:px-0">
174+
<div className="max-w-3xl mx-auto">
175+
<h2 className="text-3xl font-serif text-stone-600 mb-8 text-center">
176+
Frequently Asked Questions
177+
</h2>
178+
<div className="space-y-6">
179+
{faqs.map((faq, idx) => (
180+
<div key={idx} className="border-b border-neutral-100 pb-6 last:border-b-0">
181+
<h3 className="text-lg font-medium text-neutral-900 mb-2">
182+
{faq.question}
183+
</h3>
184+
<p className="text-neutral-600">{faq.answer}</p>
185+
</div>
186+
))}
187+
</div>
188+
</div>
189+
</section>
190+
);
191+
}
192+
193+
function CTASection() {
194+
return (
195+
<section className="py-16 bg-linear-to-t from-stone-50/30 to-stone-100/30 px-4 laptop:px-0">
196+
<div className="flex flex-col gap-6 items-center text-center">
197+
<div className="mb-4 size-40 shadow-2xl border border-neutral-100 flex justify-center items-center rounded-[48px] bg-transparent">
198+
<img
199+
src="/hyprnote/icon.png"
200+
alt="Hyprnote"
201+
className="size-36 mx-auto rounded-[40px] border border-neutral-100"
202+
/>
203+
</div>
204+
<h2 className="text-2xl sm:text-3xl font-serif">
205+
Need a team plan?
206+
</h2>
207+
<p className="text-lg text-neutral-600 max-w-2xl mx-auto">
208+
Book a call to discuss custom team pricing and enterprise solutions
209+
</p>
210+
<div className="pt-6">
211+
<Link
212+
to="/founders"
213+
className="px-6 h-12 flex items-center justify-center text-base sm:text-lg bg-linear-to-t from-stone-600 to-stone-500 text-white rounded-full shadow-md hover:shadow-lg hover:scale-[102%] active:scale-[98%] transition-all"
214+
>
215+
Book a call
216+
</Link>
217+
</div>
218+
</div>
219+
</section>
220+
);
221+
}

0 commit comments

Comments
 (0)