diff --git a/apps/web/src/components/download-button.tsx b/apps/web/src/components/download-button.tsx index d36048fa56..2bffae0b05 100644 --- a/apps/web/src/components/download-button.tsx +++ b/apps/web/src/components/download-button.tsx @@ -2,10 +2,45 @@ import { Icon } from "@iconify-icon/react"; import { cn } from "@hypr/utils"; +import { usePlatform } from "@/hooks/use-platform"; + export function DownloadButton() { + const platform = usePlatform(); + + const getPlatformData = () => { + switch (platform) { + case "mac": + return { + icon: "mdi:apple", + label: "Download for Mac", + href: "/download/apple-silicon", + }; + case "windows": + return { + icon: "mdi:microsoft-windows", + label: "Download for Windows", + href: "/download/windows", + }; + case "linux": + return { + icon: "mdi:linux", + label: "Download for Linux", + href: "/download/linux", + }; + default: + return { + icon: "mdi:apple", + label: "Download for Mac", + href: "/download/apple-silicon", + }; + } + }; + + const { icon, label, href } = getPlatformData(); + return ( - - Download for Mac + + {label} ); } diff --git a/apps/web/src/queries.ts b/apps/web/src/queries.ts index 799b1bb92b..ddfd13652b 100644 --- a/apps/web/src/queries.ts +++ b/apps/web/src/queries.ts @@ -18,6 +18,71 @@ export function useGitHubStats() { }); } +export interface Stargazer { + username: string; + avatar: string; +} + +export function useGitHubStargazers(count: number = 100) { + return useQuery({ + queryKey: ["github-stargazers", count], + queryFn: async (): Promise => { + try { + const repoResponse = await fetch( + `https://api.github.com/repos/${ORG_REPO}`, + { + headers: { + Accept: "application/vnd.github.v3+json", + }, + }, + ); + if (!repoResponse.ok) { + console.error( + `Failed to fetch repo info: ${repoResponse.status} ${repoResponse.statusText}`, + ); + return []; + } + const repoData = await repoResponse.json(); + const totalStars = repoData.stargazers_count ?? LAST_SEEN_STARS; + + if (totalStars === 0) { + return []; + } + + const perPage = Math.min(count, 100); + const lastPage = Math.ceil(totalStars / perPage); + + const response = await fetch( + `https://api.github.com/repos/${ORG_REPO}/stargazers?per_page=${perPage}&page=${lastPage}`, + { + headers: { + Accept: "application/vnd.github.v3+json", + }, + }, + ); + if (!response.ok) { + console.error( + `Failed to fetch stargazers: ${response.status} ${response.statusText}`, + ); + return []; + } + const data = await response.json(); + const stargazers = data.map( + (user: { login: string; avatar_url: string }) => ({ + username: user.login, + avatar: user.avatar_url, + }), + ); + return stargazers.reverse(); + } catch (error) { + console.error("Error fetching stargazers:", error); + return []; + } + }, + staleTime: 1000 * 60 * 60, + }); +} + export const GITHUB_ORG_REPO = ORG_REPO; export const GITHUB_LAST_SEEN_STARS = LAST_SEEN_STARS; export const GITHUB_LAST_SEEN_FORKS = LAST_SEEN_FORKS; diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index 0dd2a7b37c..0159c6e7c8 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -77,6 +77,8 @@ import { Route as ViewProductAiNotetakingRouteImport } from './routes/_view/prod import { Route as ViewProductAiAssistantRouteImport } from './routes/_view/product/ai-assistant' import { Route as ViewPressKitAppRouteImport } from './routes/_view/press-kit.app' import { Route as ViewLegalSlugRouteImport } from './routes/_view/legal/$slug' +import { Route as ViewDownloadWindowsRouteImport } from './routes/_view/download/windows' +import { Route as ViewDownloadLinuxRouteImport } from './routes/_view/download/linux' import { Route as ViewDownloadAppleSiliconRouteImport } from './routes/_view/download/apple-silicon' import { Route as ViewDownloadAppleIntelRouteImport } from './routes/_view/download/apple-intel' import { Route as ViewDocsSplatRouteImport } from './routes/_view/docs/$' @@ -434,6 +436,16 @@ const ViewLegalSlugRoute = ViewLegalSlugRouteImport.update({ path: '/legal/$slug', getParentRoute: () => ViewRouteRoute, } as any) +const ViewDownloadWindowsRoute = ViewDownloadWindowsRouteImport.update({ + id: '/download/windows', + path: '/download/windows', + getParentRoute: () => ViewRouteRoute, +} as any) +const ViewDownloadLinuxRoute = ViewDownloadLinuxRouteImport.update({ + id: '/download/linux', + path: '/download/linux', + getParentRoute: () => ViewRouteRoute, +} as any) const ViewDownloadAppleSiliconRoute = ViewDownloadAppleSiliconRouteImport.update({ id: '/download/apple-silicon', @@ -538,6 +550,8 @@ export interface FileRoutesByFullPath { '/docs/$': typeof ViewDocsSplatRoute '/download/apple-intel': typeof ViewDownloadAppleIntelRoute '/download/apple-silicon': typeof ViewDownloadAppleSiliconRoute + '/download/linux': typeof ViewDownloadLinuxRoute + '/download/windows': typeof ViewDownloadWindowsRoute '/legal/$slug': typeof ViewLegalSlugRoute '/press-kit/app': typeof ViewPressKitAppRoute '/product/ai-assistant': typeof ViewProductAiAssistantRoute @@ -616,6 +630,8 @@ export interface FileRoutesByTo { '/docs/$': typeof ViewDocsSplatRoute '/download/apple-intel': typeof ViewDownloadAppleIntelRoute '/download/apple-silicon': typeof ViewDownloadAppleSiliconRoute + '/download/linux': typeof ViewDownloadLinuxRoute + '/download/windows': typeof ViewDownloadWindowsRoute '/legal/$slug': typeof ViewLegalSlugRoute '/press-kit/app': typeof ViewPressKitAppRoute '/product/ai-assistant': typeof ViewProductAiAssistantRoute @@ -699,6 +715,8 @@ export interface FileRoutesById { '/_view/docs/$': typeof ViewDocsSplatRoute '/_view/download/apple-intel': typeof ViewDownloadAppleIntelRoute '/_view/download/apple-silicon': typeof ViewDownloadAppleSiliconRoute + '/_view/download/linux': typeof ViewDownloadLinuxRoute + '/_view/download/windows': typeof ViewDownloadWindowsRoute '/_view/legal/$slug': typeof ViewLegalSlugRoute '/_view/press-kit/app': typeof ViewPressKitAppRoute '/_view/product/ai-assistant': typeof ViewProductAiAssistantRoute @@ -782,6 +800,8 @@ export interface FileRouteTypes { | '/docs/$' | '/download/apple-intel' | '/download/apple-silicon' + | '/download/linux' + | '/download/windows' | '/legal/$slug' | '/press-kit/app' | '/product/ai-assistant' @@ -860,6 +880,8 @@ export interface FileRouteTypes { | '/docs/$' | '/download/apple-intel' | '/download/apple-silicon' + | '/download/linux' + | '/download/windows' | '/legal/$slug' | '/press-kit/app' | '/product/ai-assistant' @@ -942,6 +964,8 @@ export interface FileRouteTypes { | '/_view/docs/$' | '/_view/download/apple-intel' | '/_view/download/apple-silicon' + | '/_view/download/linux' + | '/_view/download/windows' | '/_view/legal/$slug' | '/_view/press-kit/app' | '/_view/product/ai-assistant' @@ -1480,6 +1504,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ViewLegalSlugRouteImport parentRoute: typeof ViewRouteRoute } + '/_view/download/windows': { + id: '/_view/download/windows' + path: '/download/windows' + fullPath: '/download/windows' + preLoaderRoute: typeof ViewDownloadWindowsRouteImport + parentRoute: typeof ViewRouteRoute + } + '/_view/download/linux': { + id: '/_view/download/linux' + path: '/download/linux' + fullPath: '/download/linux' + preLoaderRoute: typeof ViewDownloadLinuxRouteImport + parentRoute: typeof ViewRouteRoute + } '/_view/download/apple-silicon': { id: '/_view/download/apple-silicon' path: '/download/apple-silicon' @@ -1651,6 +1689,8 @@ interface ViewRouteRouteChildren { ViewChangelogSlugRoute: typeof ViewChangelogSlugRoute ViewDownloadAppleIntelRoute: typeof ViewDownloadAppleIntelRoute ViewDownloadAppleSiliconRoute: typeof ViewDownloadAppleSiliconRoute + ViewDownloadLinuxRoute: typeof ViewDownloadLinuxRoute + ViewDownloadWindowsRoute: typeof ViewDownloadWindowsRoute ViewLegalSlugRoute: typeof ViewLegalSlugRoute ViewProductAiAssistantRoute: typeof ViewProductAiAssistantRoute ViewProductAiNotetakingRoute: typeof ViewProductAiNotetakingRoute @@ -1709,6 +1749,8 @@ const ViewRouteRouteChildren: ViewRouteRouteChildren = { ViewChangelogSlugRoute: ViewChangelogSlugRoute, ViewDownloadAppleIntelRoute: ViewDownloadAppleIntelRoute, ViewDownloadAppleSiliconRoute: ViewDownloadAppleSiliconRoute, + ViewDownloadLinuxRoute: ViewDownloadLinuxRoute, + ViewDownloadWindowsRoute: ViewDownloadWindowsRoute, ViewLegalSlugRoute: ViewLegalSlugRoute, ViewProductAiAssistantRoute: ViewProductAiAssistantRoute, ViewProductAiNotetakingRoute: ViewProductAiNotetakingRoute, diff --git a/apps/web/src/routes/_view/download/linux.tsx b/apps/web/src/routes/_view/download/linux.tsx new file mode 100644 index 0000000000..1d964cab0a --- /dev/null +++ b/apps/web/src/routes/_view/download/linux.tsx @@ -0,0 +1,10 @@ +import { createFileRoute, redirect } from "@tanstack/react-router"; + +export const Route = createFileRoute("/_view/download/linux")({ + beforeLoad: async () => { + throw redirect({ + // TODO: needs to be fixed + href: "https://desktop2.hyprnote.com/download/latest/appimage?channel=nightly", + }); + }, +}); diff --git a/apps/web/src/routes/_view/download/windows.tsx b/apps/web/src/routes/_view/download/windows.tsx new file mode 100644 index 0000000000..e794a46ef7 --- /dev/null +++ b/apps/web/src/routes/_view/download/windows.tsx @@ -0,0 +1,10 @@ +import { createFileRoute, redirect } from "@tanstack/react-router"; + +export const Route = createFileRoute("/_view/download/windows")({ + beforeLoad: async () => { + throw redirect({ + // TODO: needs to be fixed + href: "https://desktop2.hyprnote.com/download/latest/msi?channel=nightly", + }); + }, +}); diff --git a/apps/web/src/routes/_view/opensource.tsx b/apps/web/src/routes/_view/opensource.tsx index 211988bce8..ad49bfaf9a 100644 --- a/apps/web/src/routes/_view/opensource.tsx +++ b/apps/web/src/routes/_view/opensource.tsx @@ -3,8 +3,10 @@ import { createFileRoute, Link } from "@tanstack/react-router"; import { cn } from "@hypr/utils"; +import { DownloadButton } from "@/components/download-button"; import { GitHubOpenSource } from "@/components/github-open-source"; import { SlashSeparator } from "@/components/slash-separator"; +import { Stargazer, useGitHubStargazers } from "@/queries"; export const Route = createFileRoute("/_view/opensource")({ component: Component, @@ -68,11 +70,67 @@ function Component() { ); } +function StargazerAvatar({ stargazer }: { stargazer: Stargazer }) { + return ( + + {`${stargazer.username}'s + + ); +} + +function StargazersGrid({ stargazers }: { stargazers: Stargazer[] }) { + const rows = 16; + const cols = 32; + + return ( +
+
+ {Array.from({ length: rows }).map((_, rowIndex) => ( +
+ {Array.from({ length: cols }).map((_, colIndex) => { + const index = (rowIndex * cols + colIndex) % stargazers.length; + const stargazer = stargazers[index]; + const delay = (rowIndex * cols + colIndex) * 0.05; + + return ( +
+ +
+ ); + })} +
+ ))} +
+
+ ); +} + function HeroSection() { + const { data: stargazers = [] } = useGitHubStargazers(500); + return ( -
-
-
+
+ {stargazers.length > 0 && } +
+
+

Built in the open,
@@ -97,16 +155,7 @@ function HeroSection() { View on GitHub - - Download for free - +

@@ -464,16 +513,7 @@ function CTASection() { today.

- - Download for free - +