diff --git a/apps/marketing/package.json b/apps/marketing/package.json index 2ff7b4d5d..8b866d550 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -19,7 +19,8 @@ "@hookform/resolvers": "^3.1.0", "contentlayer": "^0.3.4", "framer-motion": "^10.12.8", - "lucide-react": "^0.277.0", + "lucide-react": "^0.279.0", + "luxon": "^3.4.0", "micro": "^10.0.1", "next": "14.0.0", "next-auth": "4.24.3", diff --git a/apps/marketing/src/app/(marketing)/open/bar-metrics.tsx b/apps/marketing/src/app/(marketing)/open/bar-metrics.tsx index aa3929833..940adb8fc 100644 --- a/apps/marketing/src/app/(marketing)/open/bar-metrics.tsx +++ b/apps/marketing/src/app/(marketing)/open/bar-metrics.tsx @@ -40,9 +40,9 @@ export const BarMetric = {extraInfo} -
+
- + [Number(value), label]} cursor={{ fill: 'hsl(var(--primary) / 10%)' }} /> - {' '} +
diff --git a/apps/marketing/src/app/(marketing)/open/funding-raised.tsx b/apps/marketing/src/app/(marketing)/open/funding-raised.tsx index 92ba9e097..fbfce47da 100644 --- a/apps/marketing/src/app/(marketing)/open/funding-raised.tsx +++ b/apps/marketing/src/app/(marketing)/open/funding-raised.tsx @@ -21,7 +21,7 @@ export const FundingRaised = ({ className, data, ...props }: FundingRaisedProps)

Total Funding Raised

-
+
@@ -51,7 +51,13 @@ export const FundingRaised = ({ className, data, ...props }: FundingRaisedProps) ]} cursor={{ fill: 'hsl(var(--primary) / 10%)' }} /> - +
diff --git a/apps/marketing/src/app/(marketing)/open/monthly-users-chart.tsx b/apps/marketing/src/app/(marketing)/open/monthly-users-chart.tsx new file mode 100644 index 000000000..c35818596 --- /dev/null +++ b/apps/marketing/src/app/(marketing)/open/monthly-users-chart.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { DateTime } from 'luxon'; +import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; + +import { GetUserMonthlyGrowthResult } from '@documenso/lib/server-only/user/get-user-monthly-growth'; +import { cn } from '@documenso/ui/lib/utils'; + +export type MonthlyUsersChartProps = { + className?: string; + data: GetUserMonthlyGrowthResult; +}; + +export const MonthlyUsersChart = ({ className, data }: MonthlyUsersChartProps) => { + const formattedData = [...data].reverse().map(({ month, count }) => { + return { + month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLL'), + count: Number(count), + }; + }); + + return ( +
+
+

Monthly New Users

+
+ +
+ + + + + + [Number(value).toLocaleString('en-US'), 'Total Users']} + cursor={{ fill: 'hsl(var(--primary) / 10%)' }} + /> + + + + +
+
+ ); +}; diff --git a/apps/marketing/src/app/(marketing)/open/page.tsx b/apps/marketing/src/app/(marketing)/open/page.tsx index debdf92b9..f2964c0eb 100644 --- a/apps/marketing/src/app/(marketing)/open/page.tsx +++ b/apps/marketing/src/app/(marketing)/open/page.tsx @@ -1,5 +1,7 @@ import { z } from 'zod'; +import { getUserMonthlyGrowth } from '@documenso/lib/server-only/user/get-user-monthly-growth'; + import { FUNDING_RAISED } from '~/app/(marketing)/open/data'; import { MetricCard } from '~/app/(marketing)/open/metric-card'; import { SalaryBands } from '~/app/(marketing)/open/salary-bands'; @@ -7,11 +9,14 @@ import { SalaryBands } from '~/app/(marketing)/open/salary-bands'; import { BarMetric } from './bar-metrics'; import { CapTable } from './cap-table'; import { FundingRaised } from './funding-raised'; +import { MonthlyUsersChart } from './monthly-users-chart'; import { TeamMembers } from './team-members'; import { OpenPageTooltip } from './tooltip'; export const revalidate = 3600; +export const dynamic = 'force-dynamic'; + const ZGithubStatsResponse = z.object({ stargazers_count: z.number(), forks_count: z.number(), @@ -43,14 +48,20 @@ export type StargazersType = z.infer; export type EarlyAdoptersType = z.infer; export default async function OpenPage() { + const GITHUB_HEADERS: Record = { + accept: 'application/vnd.github.v3+json', + }; + + if (process.env.NEXT_PRIVATE_GITHUB_TOKEN) { + GITHUB_HEADERS.authorization = `Bearer ${process.env.NEXT_PRIVATE_GITHUB_TOKEN}`; + } + const { forks_count: forksCount, open_issues: openIssues, stargazers_count: stargazersCount, } = await fetch('https://api.github.com/repos/documenso/documenso', { - headers: { - accept: 'application/vnd.github.v3+json', - }, + headers: GITHUB_HEADERS, }) .then(async (res) => res.json()) .then((res) => ZGithubStatsResponse.parse(res)); @@ -58,9 +69,7 @@ export default async function OpenPage() { const { total_count: mergedPullRequests } = await fetch( 'https://api.github.com/search/issues?q=repo:documenso/documenso/+is:pr+merged:>=2010-01-01&page=0&per_page=1', { - headers: { - accept: 'application/vnd.github.v3+json', - }, + headers: GITHUB_HEADERS, }, ) .then(async (res) => res.json()) @@ -82,6 +91,8 @@ export default async function OpenPage() { .then(async (res) => res.json()) .then((res) => ZEarlyAdoptersResponse.parse(res)); + const MONTHLY_USERS = await getUserMonthlyGrowth(); + return (
@@ -122,7 +133,7 @@ export default async function OpenPage() { - + @@ -172,6 +183,8 @@ export default async function OpenPage() { className="col-span-12 lg:col-span-6" /> + +

Where's the rest?

diff --git a/package-lock.json b/package-lock.json index 21e0ef3e6..4ee25390b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,8 @@ "@hookform/resolvers": "^3.1.0", "contentlayer": "^0.3.4", "framer-motion": "^10.12.8", - "lucide-react": "^0.277.0", + "lucide-react": "^0.279.0", + "luxon": "^3.4.0", "micro": "^10.0.1", "next": "14.0.0", "next-auth": "4.24.3", diff --git a/packages/lib/server-only/user/get-user-monthly-growth.ts b/packages/lib/server-only/user/get-user-monthly-growth.ts new file mode 100644 index 000000000..e39301504 --- /dev/null +++ b/packages/lib/server-only/user/get-user-monthly-growth.ts @@ -0,0 +1,32 @@ +import { DateTime } from 'luxon'; + +import { prisma } from '@documenso/prisma'; + +export type GetUserMonthlyGrowthResult = Array<{ + month: string; + count: number; +}>; + +type GetUserMonthlyGrowthQueryResult = Array<{ + month: Date; + count: bigint; +}>; + +export const getUserMonthlyGrowth = async () => { + const result = await prisma.$queryRaw` + SELECT + DATE_TRUNC('month', "createdAt") AS "month", + COUNT("id") AS "count" + FROM "User" + GROUP BY "month" + ORDER BY "month" DESC + LIMIT 12 + `; + + console.log('result', result); + + return result.map((row) => ({ + month: DateTime.fromJSDate(row.month).toFormat('yyyy-MM'), + count: Number(row.count), + })); +}; diff --git a/turbo.json b/turbo.json index 04aa4d5c3..984a657f2 100644 --- a/turbo.json +++ b/turbo.json @@ -82,6 +82,7 @@ "NEXT_PRIVATE_SMTP_FROM_ADDRESS", "NEXT_PRIVATE_STRIPE_API_KEY", "NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET", + "NEXT_PRIVATE_GITHUB_TOKEN", "VERCEL", "VERCEL_ENV", "VERCEL_URL",