mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
feat: open page
This commit is contained in:
committed by
Mythie
parent
dc58f77c61
commit
f882be4338
95
apps/marketing/src/app/(marketing)/open/data.ts
Normal file
95
apps/marketing/src/app/(marketing)/open/data.ts
Normal file
@ -0,0 +1,95 @@
|
||||
export const TEAM_MEMBERS = [
|
||||
{
|
||||
name: 'Timur Ercan',
|
||||
role: 'Co-Founder, CEO',
|
||||
salary: 95_000,
|
||||
location: 'Germany',
|
||||
joinDate: 'November 14th, 2022',
|
||||
},
|
||||
{
|
||||
name: 'Lucas Smith',
|
||||
role: 'Co-Founder, CTO',
|
||||
salary: 95_000,
|
||||
location: 'Australia',
|
||||
joinDate: 'April 19th, 2023',
|
||||
},
|
||||
{
|
||||
name: 'Ephraim Atta-Duncan',
|
||||
role: 'Software Engineer - Intern (Part-Time)',
|
||||
salary: 15_000,
|
||||
location: 'Ghana',
|
||||
joinDate: 'June 6th, 2023',
|
||||
},
|
||||
{
|
||||
name: 'Florent Merian',
|
||||
role: 'Marketer - III',
|
||||
salary: 80_000,
|
||||
location: 'France',
|
||||
joinDate: 'July 10th, 2023',
|
||||
},
|
||||
{
|
||||
name: 'David Nguyen',
|
||||
role: 'Software Engineer - III',
|
||||
salary: 100_000,
|
||||
location: 'Australia',
|
||||
joinDate: 'July 26th, 2023',
|
||||
},
|
||||
];
|
||||
|
||||
export const FUNDING_RAISED = [
|
||||
{
|
||||
date: '2023-05-20',
|
||||
amount: 0,
|
||||
},
|
||||
{
|
||||
date: '2023-05-20',
|
||||
amount: 300_000,
|
||||
},
|
||||
{
|
||||
date: '2023-07-25',
|
||||
amount: 1_250_000,
|
||||
},
|
||||
];
|
||||
|
||||
export const SALARY_BANDS = [
|
||||
{
|
||||
title: 'Software Engineer - Intern',
|
||||
seniority: 'Intern',
|
||||
salary: 30_000,
|
||||
},
|
||||
{
|
||||
title: 'Software Engineer - I',
|
||||
seniority: 'Junior',
|
||||
salary: 60_000,
|
||||
},
|
||||
{
|
||||
title: 'Software Engineer - II',
|
||||
seniority: 'Mid',
|
||||
salary: 80_000,
|
||||
},
|
||||
{
|
||||
title: 'Software Engineer - III',
|
||||
seniority: 'Senior',
|
||||
salary: 100_000,
|
||||
},
|
||||
{
|
||||
title: 'Software Engineer - IV',
|
||||
seniority: 'Principal',
|
||||
salary: 120_000,
|
||||
},
|
||||
{
|
||||
title: 'Marketer - I',
|
||||
seniority: 'Junior',
|
||||
salary: 50_000,
|
||||
},
|
||||
{
|
||||
title: 'Marketer - II',
|
||||
seniority: 'Mid',
|
||||
salary: 65_000,
|
||||
},
|
||||
{
|
||||
title: 'Marketer - III',
|
||||
seniority: 'Senior',
|
||||
salary: 80_000,
|
||||
},
|
||||
];
|
||||
51
apps/marketing/src/app/(marketing)/open/funding-raised.tsx
Normal file
51
apps/marketing/src/app/(marketing)/open/funding-raised.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes } from 'react';
|
||||
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
import { FUNDING_RAISED } from '~/app/(marketing)/open/data';
|
||||
|
||||
export type FundingRaisedProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const FundingRaised = ({ className, ...props }: FundingRaisedProps) => {
|
||||
return (
|
||||
<div className={cn('flex flex-col', className)} {...props}>
|
||||
<h3 className="px-4 text-lg font-semibold">Funding Raised</h3>
|
||||
|
||||
<div className="border-border mt-2.5 flex flex-1 flex-col items-center justify-center rounded-2xl border p-4 shadow-sm hover:shadow">
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={FUNDING_RAISED} margin={{ top: 40, right: 40, bottom: 20, left: 40 }}>
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis
|
||||
tickFormatter={(value) =>
|
||||
Number(value).toLocaleString('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
maximumFractionDigits: 0,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
itemStyle={{
|
||||
color: 'hsl(var(--primary-foreground))',
|
||||
}}
|
||||
formatter={(value) => [
|
||||
Number(value).toLocaleString('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
maximumFractionDigits: 0,
|
||||
}),
|
||||
'Amount Raised',
|
||||
]}
|
||||
cursor={{ fill: 'hsl(var(--primary) / 10%)' }}
|
||||
/>
|
||||
<Bar dataKey="amount" fill="hsl(var(--primary))" label="Amount Raised" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
18
apps/marketing/src/app/(marketing)/open/metric-card.tsx
Normal file
18
apps/marketing/src/app/(marketing)/open/metric-card.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
export type MetricCardProps = HTMLAttributes<HTMLDivElement> & {
|
||||
title: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const MetricCard = ({ className, title, value, ...props }: MetricCardProps) => {
|
||||
return (
|
||||
<div className={cn('rounded-md border p-4 shadow-sm hover:shadow', className)} {...props}>
|
||||
<h4 className="text-muted-foreground text-sm font-medium">{title}</h4>
|
||||
|
||||
<p className="mb-2 mt-6 text-4xl font-bold">{value}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
99
apps/marketing/src/app/(marketing)/open/page.tsx
Normal file
99
apps/marketing/src/app/(marketing)/open/page.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { MetricCard } from '~/app/(marketing)/open/metric-card';
|
||||
import { SalaryBands } from '~/app/(marketing)/open/salary-bands';
|
||||
|
||||
import { FundingRaised } from './funding-raised';
|
||||
import { TeamMembers } from './team-members';
|
||||
|
||||
export const revalidate = 86400;
|
||||
|
||||
const ZGithubStatsResponse = z.object({
|
||||
stargazers_count: z.number(),
|
||||
forks_count: z.number(),
|
||||
open_issues: z.number(),
|
||||
});
|
||||
|
||||
const ZMergedPullRequestsResponse = z.object({
|
||||
total_count: z.number(),
|
||||
});
|
||||
|
||||
// const ZOpenPullRequestsResponse = ZMergedPullRequestsResponse;
|
||||
|
||||
export default async function OpenPage() {
|
||||
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',
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => ZGithubStatsResponse.parse(res));
|
||||
|
||||
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',
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((res) => ZMergedPullRequestsResponse.parse(res));
|
||||
|
||||
return (
|
||||
<div className="mx-auto mt-12 max-w-screen-lg">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<h1 className="text-3xl font-bold lg:text-5xl">Open Startup</h1>
|
||||
|
||||
<p className="text-muted-foreground mt-4 max-w-[55ch] text-center text-lg leading-normal">
|
||||
All our metrics, finances, and learnings are public. We believe in transparency and want
|
||||
to share our journey with you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 grid grid-cols-12 gap-8">
|
||||
<div className="col-span-12 grid grid-cols-4 gap-4">
|
||||
<MetricCard
|
||||
className="col-span-2 lg:col-span-1"
|
||||
title="Stargazers"
|
||||
value={stargazersCount.toLocaleString('en-US')}
|
||||
/>
|
||||
<MetricCard
|
||||
className="col-span-2 lg:col-span-1"
|
||||
title="Forks"
|
||||
value={forksCount.toLocaleString('en-US')}
|
||||
/>
|
||||
<MetricCard
|
||||
className="col-span-2 lg:col-span-1"
|
||||
title="Open Issues"
|
||||
value={openIssues.toLocaleString('en-US')}
|
||||
/>
|
||||
<MetricCard
|
||||
className="col-span-2 lg:col-span-1"
|
||||
title="Merged PR's"
|
||||
value={mergedPullRequests.toLocaleString('en-US')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TeamMembers className="col-span-12" />
|
||||
|
||||
<SalaryBands className="col-span-12 lg:col-span-6" />
|
||||
|
||||
<FundingRaised className="col-span-12 lg:col-span-6" />
|
||||
|
||||
<div className="col-span-12 mt-12 flex flex-col items-center justify-center">
|
||||
<h2 className="text-2xl font-bold">Where's the rest?</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-4 max-w-[55ch] text-center text-lg leading-normal">
|
||||
We're still working on getting all our metrics together. We'll update this page as soon
|
||||
as we have more to share.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
50
apps/marketing/src/app/(marketing)/open/salary-bands.tsx
Normal file
50
apps/marketing/src/app/(marketing)/open/salary-bands.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@documenso/ui/primitives/table';
|
||||
|
||||
import { SALARY_BANDS } from '~/app/(marketing)/open/data';
|
||||
|
||||
export type SalaryBandsProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const SalaryBands = ({ className, ...props }: SalaryBandsProps) => {
|
||||
return (
|
||||
<div className={cn('flex flex-col', className)} {...props}>
|
||||
<h3 className="px-4 text-lg font-semibold">Global Salary Bands</h3>
|
||||
|
||||
<div className="border-border mt-2.5 flex-1 rounded-2xl border shadow-sm hover:shadow">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[200px]">Title</TableHead>
|
||||
<TableHead>Seniority</TableHead>
|
||||
<TableHead className="w-[100px] text-right">Salary</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{SALARY_BANDS.map((band, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="font-medium">{band.title}</TableCell>
|
||||
<TableCell>{band.seniority}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{band.salary.toLocaleString('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
maximumFractionDigits: 0,
|
||||
})}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
56
apps/marketing/src/app/(marketing)/open/team-members.tsx
Normal file
56
apps/marketing/src/app/(marketing)/open/team-members.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@documenso/ui/primitives/table';
|
||||
|
||||
import { TEAM_MEMBERS } from './data';
|
||||
|
||||
export type TeamMembersProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const TeamMembers = ({ className, ...props }: TeamMembersProps) => {
|
||||
return (
|
||||
<div className={cn('flex flex-col', className)} {...props}>
|
||||
<h2 className="px-4 text-2xl font-semibold">Team</h2>
|
||||
|
||||
<div className="border-border mt-2.5 flex-1 rounded-2xl border shadow-sm hover:shadow">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="">Name</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
{/* <TableHead>Status</TableHead> */}
|
||||
<TableHead>Salary</TableHead>
|
||||
<TableHead>Location</TableHead>
|
||||
<TableHead className="w-[100px] text-right">Join Date</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{TEAM_MEMBERS.map((member) => (
|
||||
<TableRow key={member.name}>
|
||||
<TableCell className="font-medium">{member.name}</TableCell>
|
||||
<TableCell>{member.role}</TableCell>
|
||||
{/* <TableCell>{teamMember.employmentStatus}</TableCell> */}
|
||||
<TableCell>
|
||||
{member.salary.toLocaleString('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
maximumFractionDigits: 0,
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell>{member.location}</TableCell>
|
||||
<TableCell className="text-right">{member.joinDate}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -9,6 +9,8 @@ import { Hero } from '~/components/(marketing)/hero';
|
||||
import { OpenBuildTemplateBento } from '~/components/(marketing)/open-build-template-bento';
|
||||
import { ShareConnectPaidWidgetBento } from '~/components/(marketing)/share-connect-paid-widget-bento';
|
||||
|
||||
export const revalidate = 600;
|
||||
|
||||
const fontCaveat = Caveat({
|
||||
weight: ['500'],
|
||||
subsets: ['latin'],
|
||||
@ -17,15 +19,24 @@ const fontCaveat = Caveat({
|
||||
});
|
||||
|
||||
export default async function IndexPage() {
|
||||
const starCount = await fetch('https://api.github.com/repos/documenso/documenso', {
|
||||
headers: {
|
||||
accept: 'application/vnd.github.v3+json',
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => (typeof res.stargazers_count === 'number' ? res.stargazers_count : undefined))
|
||||
.catch(() => undefined);
|
||||
|
||||
return (
|
||||
<div className={cn('mt-12', fontCaveat.variable)}>
|
||||
<Hero />
|
||||
<Hero starCount={starCount} />
|
||||
|
||||
<FasterSmarterBeautifulBento className="my-48" />
|
||||
<ShareConnectPaidWidgetBento className="my-48" />
|
||||
<OpenBuildTemplateBento className="my-48" />
|
||||
|
||||
<Callout />
|
||||
<Callout starCount={starCount} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,7 +7,12 @@ import { usePlausible } from 'next-plausible';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
export const Callout = () => {
|
||||
export type CalloutProps = {
|
||||
starCount?: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export const Callout = ({ starCount }: CalloutProps) => {
|
||||
const event = usePlausible();
|
||||
|
||||
const onSignUpClick = () => {
|
||||
@ -36,7 +41,7 @@ export const Callout = () => {
|
||||
onClick={onSignUpClick}
|
||||
>
|
||||
Get the Community Plan
|
||||
<span className="bg-primary -mr-2 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
||||
<span className="bg-primary -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
||||
$30/mo. forever!
|
||||
</span>
|
||||
</Button>
|
||||
@ -49,6 +54,11 @@ export const Callout = () => {
|
||||
<Button variant="outline" className="rounded-full bg-transparent backdrop-blur-sm">
|
||||
<Github className="mr-2 h-5 w-5" />
|
||||
Star on Github
|
||||
{starCount && starCount > 0 && (
|
||||
<span className="bg-primary -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
||||
{starCount.toLocaleString('en-US')}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user