Skip to content

Commit

Permalink
Use fresh data w/ Convex for open source stats (#309)
Browse files Browse the repository at this point in the history
* fix svg import ts errors

* fix type errors affecting libraries/index

* add convex client

* add more data

* add dependents count

* animate numbers

* use averages to count up npm downloads

* smooth animations with regular font

* switch to published oss-stats component

* update scripts

* use default convex instance for local dev

* improve library config typing fix

* add note for hardcoded convex url

* add github dependent live counter

* remove unused counter component

* use incrementing github dependent counter

* fix convex logo for light mode

* update convex link
  • Loading branch information
erquhart authored Dec 11, 2024
1 parent 9ba8442 commit e97afb4
Show file tree
Hide file tree
Showing 23 changed files with 1,801 additions and 53 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ yarn.lock
# Sentry Config File
.env.sentry-build-plugin
dist
.vscode/
.env.local
1 change: 1 addition & 0 deletions app/images/convex-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion app/libraries/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const configProject = {
tagline: `Configuration and tools for publishing and maintaining high-quality JavaScript packages`,
description: `The build and publish utilities used by all of our projects. Use it if you dare!`,
ogImage: 'https://github.com/tanstack/config/raw/main/media/repo-header.png',
// badge: 'new',
badge: undefined,
bgStyle: 'bg-slate-500',
textStyle: 'text-slate-500',
repo: 'tanstack/config',
Expand Down
1 change: 1 addition & 0 deletions app/libraries/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const queryProject = {
description:
'Powerful asynchronous state management, server-state utilities and data fetching. Fetch, cache, update, and wrangle all forms of async data in your TS/JS, React, Vue, Solid, Svelte & Angular applications all without touching any "global state"',
ogImage: 'https://github.com/tanstack/query/raw/main/media/repo-header.png',
badge: undefined,
bgStyle: 'bg-red-500',
textStyle: 'text-red-500',
repo: 'tanstack/query',
Expand Down
2 changes: 1 addition & 1 deletion app/libraries/ranger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const rangerProject = {
tagline: `Headless range and multi-range slider utilities.`,
description: `Headless, lightweight, and extensible primitives for building range and multi-range sliders.`,
ogImage: 'https://github.com/tanstack/ranger/raw/main/media/headerv1.png',
// badge: 'new',
badge: undefined,
bgStyle: 'bg-pink-500',
textStyle: 'text-pink-500',
repo: 'tanstack/ranger',
Expand Down
2 changes: 1 addition & 1 deletion app/libraries/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const storeProject = {
tagline: `Framework agnostic data store with reactive framework adapters`,
description: `The immutable-reactive data store that powers the core of TanStack libraries and their framework adapters.`,
ogImage: 'https://github.com/tanstack/store/raw/main/media/repo-header.png',
// badge: 'new',
badge: undefined,
bgStyle: 'bg-stone-700',
textStyle: 'text-stone-500',
repo: 'tanstack/store',
Expand Down
1 change: 1 addition & 0 deletions app/libraries/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const tableProject = {
tagline: `Headless UI for building powerful tables & datagrids`,
description: `Supercharge your tables or build a datagrid from scratch for TS/JS, React, Vue, Solid, Svelte, Qwik, Angular, and Lit while retaining 100% control over markup and styles.`,
ogImage: 'https://github.com/tanstack/table/raw/main/media/repo-header.png',
badge: undefined,
bgStyle: 'bg-blue-500',
textStyle: 'text-blue-500',
repo: 'tanstack/table',
Expand Down
1 change: 1 addition & 0 deletions app/libraries/virtual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const virtualProject = {
tagline: `Headless UI for Virtualizing Large Element Lists`,
description: `Virtualize only the visible content for massive scrollable DOM nodes at 60FPS in TS/JS, React, Vue, Solid, Svelte, Lit & Angular while retaining 100% control over markup and styles.`,
ogImage: 'https://github.com/tanstack/query/raw/main/media/header.png',
badge: undefined,
bgStyle: 'bg-purple-500',
textStyle: 'text-purple-500',
repo: 'tanstack/virtual',
Expand Down
51 changes: 41 additions & 10 deletions app/router.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,53 @@
import { createRouter as TanStackCreateRouter } from '@tanstack/react-router'
import { routerWithQueryClient } from '@tanstack/react-router-with-query'
import { ConvexQueryClient } from '@convex-dev/react-query'
import { ConvexProvider } from 'convex/react'
import { routeTree } from './routeTree.gen'
import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
import { NotFound } from './components/NotFound'
import { QueryClient } from '@tanstack/react-query'

export function createRouter() {
const router = TanStackCreateRouter({
routeTree,
defaultPreload: 'intent',
defaultErrorComponent: DefaultCatchBoundary,
defaultStaleTime: 1,
defaultNotFoundComponent: () => {
return <NotFound />
},
context: {
assets: [],
const CONVEX_URL =
(import.meta as any).env.VITE_CONVEX_URL ||
// Hardcoded production URL as fallback for local development
// Currently set to an instance owned by Convex Devx
// TODO: Replace with URL to an instance owned by the TanStack team
'https://intent-pigeon-358.convex.cloud'
const convexQueryClient = new ConvexQueryClient(CONVEX_URL)

const queryClient: QueryClient = new QueryClient({
defaultOptions: {
queries: {
queryKeyHashFn: convexQueryClient.hashFn(),
queryFn: convexQueryClient.queryFn(),
},
},
})

convexQueryClient.connect(queryClient)

const router = routerWithQueryClient(
TanStackCreateRouter({
routeTree,
defaultPreload: 'intent',
defaultErrorComponent: DefaultCatchBoundary,
defaultStaleTime: 1,
defaultNotFoundComponent: () => {
return <NotFound />
},
context: {
queryClient,
},
Wrap: ({ children }) => (
<ConvexProvider client={convexQueryClient.convexClient}>
{children}
</ConvexProvider>
),
}),
queryClient
)

router.subscribe('onResolved', () => {
try {
;(window as any)._carbonads?.refresh?.()
Expand Down
5 changes: 4 additions & 1 deletion app/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useMatches,
useRouterState,
} from '@tanstack/react-router'
import { QueryClient } from '@tanstack/react-query'
import appCss from '~/styles/app.css?url'
import carbonStyles from '~/styles/carbon.css?url'
import { seo } from '~/utils/seo'
Expand All @@ -21,7 +22,9 @@ import background from '~/images/background.jpg'
import { twMerge } from 'tailwind-merge'
import { getThemeCookie, useThemeStore } from '~/components/ThemeToggle'

export const Route = createRootRouteWithContext()({
export const Route = createRootRouteWithContext<{
queryClient: QueryClient
}>()({
head: () => ({
meta: [
{
Expand Down
182 changes: 144 additions & 38 deletions app/routes/_libraries/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ import {
createFileRoute,
getRouteApi,
} from '@tanstack/react-router'
import { useSuspenseQuery } from '@tanstack/react-query'
import { convexQuery } from '@convex-dev/react-query'
import { useNpmDownloadCounter } from '@erquhart/convex-oss-stats/react'
import NumberFlow from '@number-flow/react'
import { api } from '../../../convex/_generated/api'
import { Carbon } from '~/components/Carbon'
import { twMerge } from 'tailwind-merge'
import { CgSpinner } from 'react-icons/cg'
import { Footer } from '~/components/Footer'
import SponsorPack from '~/components/SponsorPack'
import discordImage from '~/images/discord-logo-white.svg'
import convexImageWhite from '~/images/convex-white.svg'
import convexImageDark from '~/images/convex-dark.svg'
import { useMutation } from '~/hooks/useMutation'
import { sample } from '~/utils/utils'
import { libraries } from '~/libraries'
Expand All @@ -20,7 +27,7 @@ import bytesImage from '~/images/bytes.svg'
// import waves from '~/images/waves.png'
// import background from '~/images/background.jpg'
import { partners } from '../../utils/partners'
import { FaBox, FaCube, FaDownload, FaStar, FaUsers } from 'react-icons/fa'
import { FaCube, FaDownload, FaStar, FaUsers } from 'react-icons/fa'

export const textColors = [
`text-rose-500`,
Expand Down Expand Up @@ -72,6 +79,140 @@ async function bytesSignupServerFn({ email }: { email: string }) {

const librariesRouteApi = getRouteApi('/_libraries')

const StableCounter = ({ value }: { value?: number }) => {
const dummyString = Number(
Array(value?.toString().length ?? 1)
.fill('8')
.join('')
).toLocaleString()

return (
<>
{/* Dummy span to prevent layout shift */}
<span className="opacity-0">{dummyString}</span>
<span className="absolute -top-0.5 left-0">
<NumberFlow
transformTiming={{
duration: 1000,
easing: 'linear',
}}
value={value}
trend={1}
continuous
isolate
willChange
/>
</span>
</>
)
}

const NpmDownloadCounter = ({
npmData,
}: {
npmData: Parameters<typeof useNpmDownloadCounter>[0]
}) => {
const liveNpmDownloadCount = useNpmDownloadCounter(npmData)
return <StableCounter value={liveNpmDownloadCount} />
}

const OssStats = () => {
const { data: github } = useSuspenseQuery(
convexQuery(api.stats.getGithubOwner, {
owner: 'tanstack',
})
)
console.log('github', github)
const { data: npm } = useSuspenseQuery(
convexQuery(api.stats.getNpmOrg, {
name: 'tanstack',
})
)

return (
<div>
<div className="p-8 grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-8 items-center justify-center place-items-center bg-white/50 dark:bg-gray-700/30 dark:shadow-none rounded-xl shadow-xl">
<a
href="https://www.npmjs.com/org/tanstack"
target="_blank"
rel="noreferrer"
className="group flex gap-4 items-center"
>
<FaDownload className="text-2xl group-hover:text-emerald-500 transition-colors duration-200" />
<div>
<div className="text-2xl font-bold opacity-80 relative group-hover:text-emerald-500 transition-colors duration-200">
<NpmDownloadCounter npmData={npm} />
</div>
<div className="text-sm opacity-50 font-medium italic group-hover:text-emerald-500 transition-colors duration-200">
NPM Downloads
</div>
</div>
</a>
<a
href="https://github.com/orgs/TanStack/repositories?q=sort:stars"
target="_blank"
rel="noreferrer"
className="group flex gap-4 items-center"
>
<FaStar className="group-hover:text-yellow-500 text-2xl transition-colors duration-200" />
<div>
<div className="text-2xl font-bold opacity-80 leading-none group-hover:text-yellow-500 transition-colors duration-200">
<NumberFlow value={github?.starCount} />
</div>
<div className="text-sm opacity-50 font-medium italic -mt-1 group-hover:text-yellow-500 transition-colors duration-200">
Stars on Github
</div>
</div>
</a>
<div className="flex gap-4 items-center">
<FaUsers className="text-2xl" />
<div className="">
<div className="text-2xl font-bold opacity-80">
<NumberFlow value={github?.contributorCount} />
</div>
<div className="text-sm opacity-50 font-medium italic -mt-1">
Contributors on GitHub
</div>
</div>
</div>
<div className="flex gap-4 items-center">
<FaCube className="text-2xl" />
<div className="">
<div className="text-2xl font-bold opacity-80 relative">
<NumberFlow value={github?.dependentCount} />
</div>
<div className="text-sm opacity-50 font-medium italic -mt-1">
Dependents on GitHub
</div>
</div>
</div>
</div>
<div className="px-4 py-2 flex justify-end">
<a href="https://convex.dev" className="group flex items-center gap-2">
<div className="h-2 w-2 animate-pulse rounded-full bg-green-500"></div>
<div className="flex items-center gap-1">
<span className="text-[.75rem] opacity-30 relative -top-px">
Powered by
</span>
<img
className="dark:hidden opacity-30 group-hover:opacity-50"
src={convexImageDark}
alt="Convex Logo"
width={80}
/>
<img
className="hidden dark:block opacity-30 group-hover:opacity-50"
src={convexImageWhite}
alt="Convex Logo"
width={80}
/>
</div>
</a>
</div>
</div>
)
}

function Index() {
const bytesSignupMutation = useMutation({
fn: bytesSignupServerFn,
Expand Down Expand Up @@ -136,43 +277,8 @@ function Index() {
</p>
</div>
<div className="h-8" />
<div className="p-8 w-fit mx-auto grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-8 items-center justify-center place-items-center bg-white/30 dark:bg-gray-600/30 rounded-xl shadow-xl backdrop-blur-lg">
<div className="flex gap-4 items-center">
<FaDownload className="text-2xl" />
<div className="">
<div className="text-2xl font-bold opacity-80">352,749,203</div>
<div className="text-sm opacity-50 font-medium italic">
NPM Downloads
</div>
</div>
</div>
<div className="flex gap-4 items-center">
<FaStar className="text-2xl" />
<div className="">
<div className="text-2xl font-bold opacity-80">91,478</div>
<div className="text-sm opacity-50 font-medium italic">
Stars on Github
</div>
</div>
</div>
<div className="flex gap-4 items-center">
<FaUsers className="text-2xl" />
<div className="">
<div className="text-2xl font-bold opacity-80">1,959</div>
<div className="text-sm opacity-50 font-medium italic">
Contributors on GitHub
</div>
</div>
</div>
<div className="flex gap-4 items-center">
<FaCube className="text-2xl" />
<div className="">
<div className="text-2xl font-bold opacity-80">1,126,523</div>
<div className="text-sm opacity-50 font-medium italic">
Dependents on GitHub
</div>
</div>
</div>
<div className="w-fit mx-auto">
<OssStats />
</div>
<div className="h-24" />
<div className="px-4 lg:max-w-screen-lg md:mx-auto">
Expand Down
Loading

0 comments on commit e97afb4

Please sign in to comment.