mirror of
https://github.com/documenso/documenso.git
synced 2025-11-22 20:51:33 +10:00
Merge branch 'main' into reattach-pdf
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import { TClaimPlanRequestSchema, ZClaimPlanResponseSchema } from './types';
|
||||
import type { TClaimPlanRequestSchema } from './types';
|
||||
import { ZClaimPlanResponseSchema } from './types';
|
||||
|
||||
export const claimPlan = async ({
|
||||
name,
|
||||
|
||||
@ -1,25 +1,21 @@
|
||||
import { ImageResponse } from 'next/og';
|
||||
|
||||
import { allBlogPosts } from 'contentlayer/generated';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export const IMAGE_SIZE = {
|
||||
const IMAGE_SIZE = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
type BlogPostOpenGraphImageProps = {
|
||||
params: { post: string };
|
||||
};
|
||||
export async function GET(_request: Request) {
|
||||
const url = new URL(_request.url);
|
||||
|
||||
export default async function BlogPostOpenGraphImage({ params }: BlogPostOpenGraphImageProps) {
|
||||
const blogPost = allBlogPosts.find((post) => post._raw.flattenedPath === `blog/${params.post}`);
|
||||
const title = url.searchParams.get('title');
|
||||
const author = url.searchParams.get('author');
|
||||
|
||||
if (!blogPost) {
|
||||
return null;
|
||||
if (!title || !author) {
|
||||
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// The long urls are needed for a compiler optimisation on the Next.js side, lifting this up
|
||||
@ -49,10 +45,10 @@ export default async function BlogPostOpenGraphImage({ params }: BlogPostOpenGra
|
||||
<img src={logoImage} alt="logo" tw="h-8" />
|
||||
|
||||
<h1 tw="mt-8 text-6xl text-center flex items-center justify-center w-full max-w-[800px] font-bold text-center mx-auto">
|
||||
{blogPost.title}
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<p tw="font-normal">Written by {blogPost.authorName}</p>
|
||||
<p tw="font-normal">Written by {author}</p>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
@ -7,6 +7,8 @@ import { ChevronLeft } from 'lucide-react';
|
||||
import type { MDXComponents } from 'mdx/types';
|
||||
import { useMDXComponent } from 'next-contentlayer/hooks';
|
||||
|
||||
import { CallToAction } from '~/components/(marketing)/call-to-action';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export const generateMetadata = ({ params }: { params: { post: string } }) => {
|
||||
@ -18,11 +20,23 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => {
|
||||
};
|
||||
}
|
||||
|
||||
// Use the url constructor to ensure that things are escaped as they should be
|
||||
const searchParams = new URLSearchParams({
|
||||
title: blogPost.title,
|
||||
author: blogPost.authorName,
|
||||
});
|
||||
|
||||
return {
|
||||
title: {
|
||||
absolute: `${blogPost.title} - Documenso Blog`,
|
||||
},
|
||||
description: blogPost.description,
|
||||
openGraph: {
|
||||
images: [`${blogPost.href}/opengraph?${searchParams.toString()}`],
|
||||
},
|
||||
twitter: {
|
||||
images: [`${blogPost.href}/opengraph?${searchParams.toString()}`],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -42,53 +56,57 @@ export default function BlogPostPage({ params }: { params: { post: string } }) {
|
||||
const MDXContent = useMDXComponent(post.body.code);
|
||||
|
||||
return (
|
||||
<article className="prose dark:prose-invert mx-auto py-8">
|
||||
<div className="mb-6 text-center">
|
||||
<time dateTime={post.date} className="text-muted-foreground mb-1 text-xs">
|
||||
{new Date(post.date).toLocaleDateString()}
|
||||
</time>
|
||||
<div>
|
||||
<article className="prose dark:prose-invert mx-auto py-8">
|
||||
<div className="mb-6 text-center">
|
||||
<time dateTime={post.date} className="text-muted-foreground mb-1 text-xs">
|
||||
{new Date(post.date).toLocaleDateString()}
|
||||
</time>
|
||||
|
||||
<h1 className="text-3xl font-bold">{post.title}</h1>
|
||||
<h1 className="text-3xl font-bold">{post.title}</h1>
|
||||
|
||||
<div className="not-prose relative -mt-2 flex items-center gap-x-4 border-b border-t py-4">
|
||||
<div className="bg-foreground h-10 w-10 rounded-full">
|
||||
{post.authorImage && (
|
||||
<img
|
||||
src={post.authorImage}
|
||||
alt={`Image of ${post.authorName}`}
|
||||
className="bg-foreground/10 h-10 w-10 rounded-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="not-prose relative -mt-2 flex items-center gap-x-4 border-b border-t py-4">
|
||||
<div className="bg-foreground h-10 w-10 rounded-full">
|
||||
{post.authorImage && (
|
||||
<img
|
||||
src={post.authorImage}
|
||||
alt={`Image of ${post.authorName}`}
|
||||
className="bg-foreground/10 h-10 w-10 rounded-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-sm leading-6">
|
||||
<p className="text-foreground text-left font-semibold">{post.authorName}</p>
|
||||
<p className="text-muted-foreground">{post.authorRole}</p>
|
||||
<div className="text-sm leading-6">
|
||||
<p className="text-foreground text-left font-semibold">{post.authorName}</p>
|
||||
<p className="text-muted-foreground">{post.authorRole}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MDXContent components={mdxComponents} />
|
||||
<MDXContent components={mdxComponents} />
|
||||
|
||||
{post.tags.length > 0 && (
|
||||
<ul className="not-prose flex list-none flex-row space-x-2 px-0">
|
||||
{post.tags.map((tag, i) => (
|
||||
<li
|
||||
key={`tag-${i}`}
|
||||
className="bg-muted hover:bg-muted/60 text-foreground relative z-10 whitespace-nowrap rounded-full px-3 py-1.5 text-sm font-medium"
|
||||
>
|
||||
{tag}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{post.tags.length > 0 && (
|
||||
<ul className="not-prose flex list-none flex-row space-x-2 px-0">
|
||||
{post.tags.map((tag, i) => (
|
||||
<li
|
||||
key={`tag-${i}`}
|
||||
className="bg-muted hover:bg-muted/60 text-foreground relative z-10 whitespace-nowrap rounded-full px-3 py-1.5 text-sm font-medium"
|
||||
>
|
||||
{tag}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<hr />
|
||||
<hr />
|
||||
|
||||
<Link href="/blog" className="text-muted-foreground flex items-center hover:opacity-60">
|
||||
<ChevronLeft className="mr-2 h-6 w-6" />
|
||||
Back to all posts
|
||||
</Link>
|
||||
</article>
|
||||
<Link href="/blog" className="text-muted-foreground flex items-center hover:opacity-60">
|
||||
<ChevronLeft className="mr-2 h-6 w-6" />
|
||||
Back to all posts
|
||||
</Link>
|
||||
</article>
|
||||
|
||||
{post.cta && <CallToAction className="mt-8" utmSource={`blog_${params.post}`} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,10 +2,8 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import launchWeekTwoImage from '@documenso/assets/images/background-lw-2.png';
|
||||
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
@ -38,7 +36,8 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn('relative flex min-h-[100vh] max-w-[100vw] flex-col pt-20 md:pt-28', {
|
||||
'overflow-y-auto overflow-x-hidden': pathname !== '/singleplayer',
|
||||
'overflow-y-auto overflow-x-hidden':
|
||||
pathname && !['/singleplayer', '/pricing'].includes(pathname),
|
||||
})}
|
||||
>
|
||||
<div
|
||||
@ -47,16 +46,8 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) {
|
||||
})}
|
||||
>
|
||||
{showProfilesAnnouncementBar && (
|
||||
<div className="relative inline-flex w-full items-center justify-center overflow-hidden px-4 py-2.5">
|
||||
<div className="absolute inset-0 -z-[1]">
|
||||
<Image
|
||||
src={launchWeekTwoImage}
|
||||
className="h-full w-full object-cover"
|
||||
alt="Launch Week 2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-background text-center text-sm">
|
||||
<div className="relative inline-flex w-full items-center justify-center overflow-hidden bg-[#e7f3df] px-4 py-2.5">
|
||||
<div className="text-black text-center text-sm font-medium">
|
||||
Claim your documenso public profile username now!{' '}
|
||||
<span className="hidden font-semibold md:inline">documenso.com/u/yourname</span>
|
||||
<div className="mt-1.5 block md:ml-4 md:mt-0 md:inline-block">
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes } from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import { formatMonth } from '@documenso/lib/client-only/format-month';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
export type BarMetricProps<T extends Record<string, unknown>> = HTMLAttributes<HTMLDivElement> & {
|
||||
data: T;
|
||||
@ -34,13 +33,13 @@ export const BarMetric = <T extends Record<string, Record<keyof T[string], unkno
|
||||
.reverse();
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col', className)} {...props}>
|
||||
<div className="flex items-center px-4">
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
<span>{extraInfo}</span>
|
||||
</div>
|
||||
<div className={className} {...props}>
|
||||
<div className="border-border flex flex-col justify-center rounded-2xl border p-6 pl-2 shadow-sm hover:shadow">
|
||||
<div className="mb-6 flex px-4">
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
<span>{extraInfo}</span>
|
||||
</div>
|
||||
|
||||
<div className="border-border mt-2.5 flex flex-1 items-center justify-center rounded-2xl border p-6 pl-2 pt-12 shadow-sm hover:shadow">
|
||||
<ResponsiveContainer width="100%" height={chartHeight}>
|
||||
<BarChart data={formattedData}>
|
||||
<XAxis dataKey="month" />
|
||||
@ -56,6 +55,7 @@ export const BarMetric = <T extends Record<string, Record<keyof T[string], unkno
|
||||
cursor={{ fill: 'hsl(var(--primary) / 10%)' }}
|
||||
/>
|
||||
<Bar
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
dataKey={metricKey as string}
|
||||
maxBarSize={60}
|
||||
fill="hsl(var(--primary))"
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes, useEffect, useState } from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Cell, Legend, Pie, PieChart, Tooltip } from 'recharts';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
import { CAP_TABLE } from './data';
|
||||
|
||||
const COLORS = ['#7fd843', '#a2e771', '#c6f2a4'];
|
||||
@ -48,10 +47,12 @@ export const CapTable = ({ className, ...props }: CapTableProps) => {
|
||||
setIsSSR(false);
|
||||
}, []);
|
||||
return (
|
||||
<div className={cn('flex flex-col', className)} {...props}>
|
||||
<h3 className="px-4 text-lg font-semibold">Cap Table</h3>
|
||||
<div className={className} {...props}>
|
||||
<div className="border-border flex flex-col justify-center rounded-2xl border p-6 pl-2 shadow-sm hover:shadow">
|
||||
<div className="mb-6 flex px-4">
|
||||
<h3 className="text-lg font-semibold">Cap Table</h3>
|
||||
</div>
|
||||
|
||||
<div className="border-border mt-2.5 flex flex-1 items-center justify-center rounded-2xl border shadow-sm hover:shadow">
|
||||
{!isSSR && (
|
||||
<PieChart width={400} height={400}>
|
||||
<Pie
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes } from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import { formatMonth } from '@documenso/lib/client-only/format-month';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
export type FundingRaisedProps = HTMLAttributes<HTMLDivElement> & {
|
||||
data: Record<string, string | number>[];
|
||||
@ -14,14 +13,17 @@ export type FundingRaisedProps = HTMLAttributes<HTMLDivElement> & {
|
||||
export const FundingRaised = ({ className, data, ...props }: FundingRaisedProps) => {
|
||||
const formattedData = data.map((item) => ({
|
||||
amount: Number(item.amount),
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
date: formatMonth(item.date as string),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col', className)} {...props}>
|
||||
<h3 className="px-4 text-lg font-semibold">Total Funding Raised</h3>
|
||||
<div className={className} {...props}>
|
||||
<div className="border-border flex flex-col justify-center rounded-2xl border p-6 pl-2 shadow-sm hover:shadow">
|
||||
<div className="mb-6 flex px-4">
|
||||
<h3 className="text-lg font-semibold">Total Funding Raised</h3>
|
||||
</div>
|
||||
|
||||
<div className="border-border mt-2.5 flex flex-1 flex-col items-center justify-center rounded-2xl border p-6 pl-2 pt-12 shadow-sm hover:shadow">
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={formattedData} margin={{ top: 40, right: 40, bottom: 20, left: 40 }}>
|
||||
<XAxis dataKey="date" />
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
'use client';
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import type { GetUserMonthlyGrowthResult } from '@documenso/lib/server-only/user/get-user-monthly-growth';
|
||||
|
||||
export type MonthlyCompletedDocumentsChartProps = {
|
||||
className?: string;
|
||||
data: GetUserMonthlyGrowthResult;
|
||||
};
|
||||
|
||||
export const MonthlyCompletedDocumentsChart = ({
|
||||
className,
|
||||
data,
|
||||
}: MonthlyCompletedDocumentsChartProps) => {
|
||||
const formattedData = [...data].reverse().map(({ month, count }) => {
|
||||
return {
|
||||
month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLLL'),
|
||||
count: Number(count),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="border-border flex flex-col justify-center rounded-2xl border p-6 pl-2 shadow-sm hover:shadow">
|
||||
<div className="mb-6 flex px-4">
|
||||
<h3 className="text-lg font-semibold">Completed Documents per Month</h3>
|
||||
</div>
|
||||
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={formattedData}>
|
||||
<XAxis dataKey="month" />
|
||||
<YAxis />
|
||||
|
||||
<Tooltip
|
||||
labelStyle={{
|
||||
color: 'hsl(var(--primary-foreground))',
|
||||
}}
|
||||
formatter={(value) => [Number(value).toLocaleString('en-US'), 'Completed Documents']}
|
||||
cursor={{ fill: 'hsl(var(--primary) / 10%)' }}
|
||||
/>
|
||||
|
||||
<Bar
|
||||
dataKey="count"
|
||||
fill="hsl(var(--primary))"
|
||||
radius={[4, 4, 0, 0]}
|
||||
maxBarSize={60}
|
||||
label="Completed Documents"
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -4,7 +4,6 @@ import { DateTime } from 'luxon';
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import type { GetUserMonthlyGrowthResult } from '@documenso/lib/server-only/user/get-user-monthly-growth';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
export type MonthlyNewUsersChartProps = {
|
||||
className?: string;
|
||||
@ -14,24 +13,27 @@ export type MonthlyNewUsersChartProps = {
|
||||
export const MonthlyNewUsersChart = ({ className, data }: MonthlyNewUsersChartProps) => {
|
||||
const formattedData = [...data].reverse().map(({ month, count }) => {
|
||||
return {
|
||||
month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLL'),
|
||||
month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLLL'),
|
||||
count: Number(count),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col', className)}>
|
||||
<div className="flex items-center px-4">
|
||||
<h3 className="text-lg font-semibold">New Users</h3>
|
||||
</div>
|
||||
<div className={className}>
|
||||
<div className="border-border flex flex-col justify-center rounded-2xl border p-6 pl-2 shadow-sm hover:shadow">
|
||||
<div className="mb-6 flex px-4">
|
||||
<h3 className="text-lg font-semibold">New Users</h3>
|
||||
</div>
|
||||
|
||||
<div className="border-border mt-2.5 flex flex-1 items-center justify-center rounded-2xl border p-6 pl-2 pt-12 shadow-sm hover:shadow">
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={formattedData}>
|
||||
<XAxis dataKey="month" />
|
||||
<YAxis />
|
||||
|
||||
<Tooltip
|
||||
labelStyle={{
|
||||
color: 'hsl(var(--primary-foreground))',
|
||||
}}
|
||||
formatter={(value) => [Number(value).toLocaleString('en-US'), 'New Users']}
|
||||
cursor={{ fill: 'hsl(var(--primary) / 10%)' }}
|
||||
/>
|
||||
|
||||
@ -4,7 +4,6 @@ import { DateTime } from 'luxon';
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import type { GetUserMonthlyGrowthResult } from '@documenso/lib/server-only/user/get-user-monthly-growth';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
export type MonthlyTotalUsersChartProps = {
|
||||
className?: string;
|
||||
@ -14,24 +13,27 @@ export type MonthlyTotalUsersChartProps = {
|
||||
export const MonthlyTotalUsersChart = ({ className, data }: MonthlyTotalUsersChartProps) => {
|
||||
const formattedData = [...data].reverse().map(({ month, cume_count: count }) => {
|
||||
return {
|
||||
month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLL'),
|
||||
month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLLL'),
|
||||
count: Number(count),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col', className)}>
|
||||
<div className="flex items-center px-4">
|
||||
<h3 className="text-lg font-semibold">Total Users</h3>
|
||||
</div>
|
||||
<div className={className}>
|
||||
<div className="border-border flex flex-1 flex-col justify-center rounded-2xl border p-6 pl-2 shadow-sm hover:shadow">
|
||||
<div className="mb-6 flex px-4">
|
||||
<h3 className="text-lg font-semibold">Total Users</h3>
|
||||
</div>
|
||||
|
||||
<div className="border-border mt-2.5 flex flex-1 items-center justify-center rounded-2xl border p-6 pl-2 pt-12 shadow-sm hover:shadow">
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={formattedData}>
|
||||
<XAxis dataKey="month" />
|
||||
<YAxis />
|
||||
|
||||
<Tooltip
|
||||
labelStyle={{
|
||||
color: 'hsl(var(--primary-foreground))',
|
||||
}}
|
||||
formatter={(value) => [Number(value).toLocaleString('en-US'), 'Total Users']}
|
||||
cursor={{ fill: 'hsl(var(--primary) / 10%)' }}
|
||||
/>
|
||||
|
||||
@ -2,19 +2,24 @@ import type { Metadata } from 'next';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getCompletedDocumentsMonthly } from '@documenso/lib/server-only/user/get-monthly-completed-document';
|
||||
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';
|
||||
import { CallToAction } from '~/components/(marketing)/call-to-action';
|
||||
|
||||
import { BarMetric } from './bar-metrics';
|
||||
import { CapTable } from './cap-table';
|
||||
import { FundingRaised } from './funding-raised';
|
||||
import { MetricCard } from './metric-card';
|
||||
import { MonthlyCompletedDocumentsChart } from './monthly-completed-documents-chart';
|
||||
import { MonthlyNewUsersChart } from './monthly-new-users-chart';
|
||||
import { MonthlyTotalUsersChart } from './monthly-total-users-chart';
|
||||
import { SalaryBands } from './salary-bands';
|
||||
import { TeamMembers } from './team-members';
|
||||
import { OpenPageTooltip } from './tooltip';
|
||||
import { TotalSignedDocumentsChart } from './total-signed-documents-chart';
|
||||
import { Typefully } from './typefully';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Open Startup',
|
||||
@ -129,123 +134,149 @@ export default async function OpenPage() {
|
||||
{ total_count: mergedPullRequests },
|
||||
STARGAZERS_DATA,
|
||||
EARLY_ADOPTERS_DATA,
|
||||
MONTHLY_USERS,
|
||||
MONTHLY_COMPLETED_DOCUMENTS,
|
||||
] = await Promise.all([
|
||||
fetchGithubStats(),
|
||||
fetchOpenIssues(),
|
||||
fetchMergedPullRequests(),
|
||||
fetchStargazers(),
|
||||
fetchEarlyAdopters(),
|
||||
getUserMonthlyGrowth(),
|
||||
getCompletedDocumentsMonthly(),
|
||||
]);
|
||||
|
||||
const MONTHLY_USERS = await getUserMonthlyGrowth();
|
||||
|
||||
return (
|
||||
<div className="mx-auto mt-6 max-w-screen-lg sm:mt-12">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<h1 className="text-3xl font-bold lg:text-5xl">Open Startup</h1>
|
||||
<div>
|
||||
<div className="mx-auto mt-6 max-w-screen-lg sm:mt-12">
|
||||
<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-[60ch] 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. You can read more about why here:{' '}
|
||||
<a
|
||||
className="font-bold"
|
||||
href="https://documenso.com/blog/pre-seed"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Announcing Open Metrics
|
||||
</a>
|
||||
<p className="text-muted-foreground mt-4 max-w-[60ch] 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. You can read more about why here:{' '}
|
||||
<a
|
||||
className="font-bold"
|
||||
href="https://documenso.com/blog/pre-seed"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Announcing Open Metrics
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="my-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" />
|
||||
</div>
|
||||
|
||||
<h2 className="px-4 text-2xl font-semibold">Finances</h2>
|
||||
<div className="mb-12 mt-4 grid grid-cols-12 gap-8">
|
||||
<FundingRaised data={FUNDING_RAISED} className="col-span-12 lg:col-span-6" />
|
||||
|
||||
<CapTable className="col-span-12 lg:col-span-6" />
|
||||
</div>
|
||||
|
||||
<h2 className="px-4 text-2xl font-semibold">Community</h2>
|
||||
<div className="mb-12 mt-4 grid grid-cols-12 gap-8">
|
||||
<BarMetric<StargazersType>
|
||||
data={STARGAZERS_DATA}
|
||||
metricKey="stars"
|
||||
title="GitHub: Total Stars"
|
||||
label="Stars"
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
|
||||
<BarMetric<StargazersType>
|
||||
data={STARGAZERS_DATA}
|
||||
metricKey="mergedPRs"
|
||||
title="GitHub: Total Merged PRs"
|
||||
label="Merged PRs"
|
||||
chartHeight={400}
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
|
||||
<BarMetric<StargazersType>
|
||||
data={STARGAZERS_DATA}
|
||||
metricKey="forks"
|
||||
title="GitHub: Total Forks"
|
||||
label="Forks"
|
||||
chartHeight={400}
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
|
||||
<BarMetric<StargazersType>
|
||||
data={STARGAZERS_DATA}
|
||||
metricKey="openIssues"
|
||||
title="GitHub: Total Open Issues"
|
||||
label="Open Issues"
|
||||
chartHeight={400}
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
|
||||
<Typefully className="col-span-12 lg:col-span-6" />
|
||||
</div>
|
||||
|
||||
<h2 className="px-4 text-2xl font-semibold">Growth</h2>
|
||||
<div className="mb-12 mt-4 grid grid-cols-12 gap-8">
|
||||
<BarMetric<EarlyAdoptersType>
|
||||
data={EARLY_ADOPTERS_DATA}
|
||||
metricKey="earlyAdopters"
|
||||
title="Early Adopters"
|
||||
label="Early Adopters"
|
||||
className="col-span-12 lg:col-span-6"
|
||||
extraInfo={<OpenPageTooltip />}
|
||||
/>
|
||||
|
||||
<MonthlyTotalUsersChart data={MONTHLY_USERS} className="col-span-12 lg:col-span-6" />
|
||||
<MonthlyNewUsersChart data={MONTHLY_USERS} className="col-span-12 lg:col-span-6" />
|
||||
|
||||
<MonthlyCompletedDocumentsChart
|
||||
data={MONTHLY_COMPLETED_DOCUMENTS}
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
<TotalSignedDocumentsChart
|
||||
data={MONTHLY_COMPLETED_DOCUMENTS}
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 mt-12 flex flex-col items-center justify-center">
|
||||
<h2 className="text-2xl font-bold">Is there more?</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-4 max-w-[55ch] text-center text-lg leading-normal">
|
||||
This page is evolving as we learn what makes a great signing company. We'll update it when
|
||||
we have more to share.
|
||||
</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" />
|
||||
|
||||
<FundingRaised data={FUNDING_RAISED} className="col-span-12 lg:col-span-6" />
|
||||
|
||||
<CapTable className="col-span-12 lg:col-span-6" />
|
||||
|
||||
<BarMetric<EarlyAdoptersType>
|
||||
data={EARLY_ADOPTERS_DATA}
|
||||
metricKey="earlyAdopters"
|
||||
title="Early Adopters"
|
||||
label="Early Adopters"
|
||||
className="col-span-12 lg:col-span-6"
|
||||
extraInfo={<OpenPageTooltip />}
|
||||
/>
|
||||
|
||||
<BarMetric<StargazersType>
|
||||
data={STARGAZERS_DATA}
|
||||
metricKey="stars"
|
||||
title="Github: Total Stars"
|
||||
label="Stars"
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
|
||||
<BarMetric<StargazersType>
|
||||
data={STARGAZERS_DATA}
|
||||
metricKey="mergedPRs"
|
||||
title="Github: Total Merged PRs"
|
||||
label="Merged PRs"
|
||||
chartHeight={300}
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
|
||||
<BarMetric<StargazersType>
|
||||
data={STARGAZERS_DATA}
|
||||
metricKey="forks"
|
||||
title="Github: Total Forks"
|
||||
label="Forks"
|
||||
chartHeight={300}
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
|
||||
<BarMetric<StargazersType>
|
||||
data={STARGAZERS_DATA}
|
||||
metricKey="openIssues"
|
||||
title="Github: Total Open Issues"
|
||||
label="Open Issues"
|
||||
chartHeight={300}
|
||||
className="col-span-12 lg:col-span-6"
|
||||
/>
|
||||
|
||||
<MonthlyTotalUsersChart data={MONTHLY_USERS} className="col-span-12 lg:col-span-6" />
|
||||
<MonthlyNewUsersChart data={MONTHLY_USERS} 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>
|
||||
<CallToAction className="mt-12" utmSource="open-page" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import {
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
'use client';
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import type { GetUserMonthlyGrowthResult } from '@documenso/lib/server-only/user/get-user-monthly-growth';
|
||||
|
||||
export type TotalSignedDocumentsChartProps = {
|
||||
className?: string;
|
||||
data: GetUserMonthlyGrowthResult;
|
||||
};
|
||||
|
||||
export const TotalSignedDocumentsChart = ({ className, data }: TotalSignedDocumentsChartProps) => {
|
||||
const formattedData = [...data].reverse().map(({ month, cume_count: count }) => {
|
||||
return {
|
||||
month: DateTime.fromFormat(month, 'yyyy-MM').toFormat('LLLL'),
|
||||
count: Number(count),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="border-border flex flex-col justify-center rounded-2xl border p-6 pl-2 shadow-sm hover:shadow">
|
||||
<div className="mb-6 flex px-4">
|
||||
<h3 className="text-lg font-semibold">Total Completed Documents</h3>
|
||||
</div>
|
||||
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={formattedData}>
|
||||
<XAxis dataKey="month" />
|
||||
<YAxis />
|
||||
|
||||
<Tooltip
|
||||
labelStyle={{
|
||||
color: 'hsl(var(--primary-foreground))',
|
||||
}}
|
||||
formatter={(value) => [
|
||||
Number(value).toLocaleString('en-US'),
|
||||
'Total Completed Documents',
|
||||
]}
|
||||
cursor={{ fill: 'hsl(var(--primary) / 10%)' }}
|
||||
/>
|
||||
|
||||
<Bar
|
||||
dataKey="count"
|
||||
fill="hsl(var(--primary))"
|
||||
radius={[4, 4, 0, 0]}
|
||||
maxBarSize={60}
|
||||
label="Total Completed Documents"
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
40
apps/marketing/src/app/(marketing)/open/typefully.tsx
Normal file
40
apps/marketing/src/app/(marketing)/open/typefully.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
'use client';
|
||||
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { FaXTwitter } from 'react-icons/fa6';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
export type TypefullyProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const Typefully = ({ className, ...props }: TypefullyProps) => {
|
||||
return (
|
||||
<div className={className} {...props}>
|
||||
<div className="border-border flex flex-col justify-center rounded-2xl border p-6 pl-2 shadow-sm hover:shadow">
|
||||
<div className="mb-6 flex px-4">
|
||||
<h3 className="text-lg font-semibold">Twitter Stats</h3>
|
||||
</div>
|
||||
|
||||
<div className="my-12 flex flex-col items-center gap-y-4 text-center">
|
||||
<FaXTwitter className="h-12 w-12" />
|
||||
<Link href="https://typefully.com/documenso/stats" target="_blank">
|
||||
<h1>Documenso on X</h1>
|
||||
</Link>
|
||||
<Button className="rounded-full" size="sm" asChild>
|
||||
<Link href="https://typefully.com/documenso/stats" target="_blank">
|
||||
View all stats
|
||||
</Link>
|
||||
</Button>
|
||||
<Button className="rounded-full bg-white" size="sm" asChild>
|
||||
<Link href="https://twitter.com/documenso" target="_blank">
|
||||
Follow us on X
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -2,13 +2,14 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Variants, motion } from 'framer-motion';
|
||||
import type { Variants } from 'framer-motion';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent, CardTitle } from '@documenso/ui/primitives/card';
|
||||
|
||||
import { TOSSFriendsSchema } from './schema';
|
||||
import type { TOSSFriendsSchema } from './schema';
|
||||
|
||||
const ContainerVariants: Variants = {
|
||||
initial: {
|
||||
|
||||
@ -8,7 +8,7 @@ import { useRouter } from 'next/navigation';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { base64 } from '@documenso/lib/universal/base64';
|
||||
import { putFile } from '@documenso/lib/universal/upload/put-file';
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||
import { DocumentDataType, Prisma } from '@documenso/prisma/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@ -115,7 +115,7 @@ export const SinglePlayerClient = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const putFileData = await putFile(uploadedFile.file);
|
||||
const putFileData = await putPdfFile(uploadedFile.file);
|
||||
|
||||
const documentToken = await createSinglePlayerDocument({
|
||||
documentData: {
|
||||
@ -158,9 +158,11 @@ export const SinglePlayerClient = () => {
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
readStatus: 'OPENED',
|
||||
documentDeletedAt: null,
|
||||
signingStatus: 'NOT_SIGNED',
|
||||
sendStatus: 'NOT_SENT',
|
||||
role: 'SIGNER',
|
||||
authOptions: null,
|
||||
};
|
||||
|
||||
const onFileDrop = async (file: File) => {
|
||||
@ -203,7 +205,7 @@ export const SinglePlayerClient = () => {
|
||||
target="_blank"
|
||||
className="hover:text-foreground/80 font-semibold transition-colors"
|
||||
>
|
||||
community plan
|
||||
early adopter plan
|
||||
</Link>{' '}
|
||||
for exclusive features, including the ability to collaborate with multiple signers.
|
||||
</p>
|
||||
@ -247,6 +249,7 @@ export const SinglePlayerClient = () => {
|
||||
fields={fields}
|
||||
onSubmit={onFieldsSubmit}
|
||||
canGoBack={true}
|
||||
isDocumentPdfLoaded={true}
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { Suspense } from 'react';
|
||||
|
||||
import { Caveat, Inter } from 'next/font/google';
|
||||
|
||||
import { AxiomWebVitals } from 'next-axiom';
|
||||
import { PublicEnvScript } from 'next-runtime-env';
|
||||
|
||||
import { FeatureFlagProvider } from '@documenso/lib/client-only/providers/feature-flag';
|
||||
@ -67,6 +68,8 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
<PublicEnvScript />
|
||||
</head>
|
||||
|
||||
<AxiomWebVitals />
|
||||
|
||||
<Suspense>
|
||||
<PostHogPageview />
|
||||
</Suspense>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MetadataRoute } from 'next';
|
||||
import type { MetadataRoute } from 'next';
|
||||
|
||||
import { getBaseUrl } from '@documenso/lib/universal/get-base-url';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MetadataRoute } from 'next';
|
||||
import type { MetadataRoute } from 'next';
|
||||
|
||||
import { allBlogPosts, allGenericPages } from 'contentlayer/generated';
|
||||
|
||||
|
||||
31
apps/marketing/src/components/(marketing)/call-to-action.tsx
Normal file
31
apps/marketing/src/components/(marketing)/call-to-action.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
|
||||
type CallToActionProps = {
|
||||
className?: string;
|
||||
utmSource?: string;
|
||||
};
|
||||
|
||||
export const CallToAction = ({ className, utmSource = 'generic-cta' }: CallToActionProps) => {
|
||||
return (
|
||||
<Card spotlight className={className}>
|
||||
<CardContent className="flex flex-col items-center justify-center p-12">
|
||||
<h2 className="text-center text-2xl font-bold">Join the Open Signing Movement</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-4 max-w-[55ch] text-center leading-normal">
|
||||
Create your account and start using state-of-the-art document signing. Open and beautiful
|
||||
signing is within your grasp.
|
||||
</p>
|
||||
|
||||
<Button className="mt-8 rounded-full no-underline" size="lg" asChild>
|
||||
<Link href={`${NEXT_PUBLIC_WEBAPP_URL()}/signup?utm_source=${utmSource}`} target="_blank">
|
||||
Get started
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@ -40,7 +40,7 @@ export const Callout = ({ starCount }: CalloutProps) => {
|
||||
className="rounded-full bg-transparent backdrop-blur-sm"
|
||||
onClick={onSignUpClick}
|
||||
>
|
||||
Claim Community Plan
|
||||
Claim Early Adopter Plan
|
||||
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs font-medium">
|
||||
$30/mo
|
||||
</span>
|
||||
@ -53,7 +53,7 @@ export const Callout = ({ starCount }: CalloutProps) => {
|
||||
>
|
||||
<Button variant="outline" className="rounded-full bg-transparent backdrop-blur-sm">
|
||||
<LuGithub className="mr-2 h-5 w-5" />
|
||||
Star on Github
|
||||
Star on GitHub
|
||||
{starCount && starCount > 0 && (
|
||||
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
||||
{starCount.toLocaleString('en-US')}
|
||||
|
||||
@ -13,6 +13,8 @@ import LogoImage from '@documenso/assets/logo.png';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { ThemeSwitcher } from '@documenso/ui/primitives/theme-switcher';
|
||||
|
||||
// import { StatusWidgetContainer } from './status-widget-container';
|
||||
|
||||
export type FooterProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const SOCIAL_LINKS = [
|
||||
@ -62,6 +64,10 @@ export const Footer = ({ className, ...props }: FooterProps) => {
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* <div className="mt-6">
|
||||
<StatusWidgetContainer />
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
<div className="grid w-full max-w-sm grid-cols-2 gap-x-4 gap-y-2 md:w-auto md:gap-x-8">
|
||||
|
||||
@ -96,7 +96,7 @@ export const Hero = ({ className, ...props }: HeroProps) => {
|
||||
variants={HeroTitleVariants}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
className="text-center text-4xl font-bold leading-tight tracking-tight lg:text-[64px]"
|
||||
className="text-center text-4xl font-bold leading-tight tracking-tight md:text-[48px] lg:text-[64px]"
|
||||
>
|
||||
Document signing,
|
||||
<span className="block" /> finally open source.
|
||||
@ -114,7 +114,7 @@ export const Hero = ({ className, ...props }: HeroProps) => {
|
||||
className="rounded-full bg-transparent backdrop-blur-sm"
|
||||
onClick={onSignUpClick}
|
||||
>
|
||||
Claim Community Plan
|
||||
Claim Early Adopter Plan
|
||||
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs font-medium">
|
||||
$30/mo
|
||||
</span>
|
||||
@ -123,7 +123,7 @@ export const Hero = ({ className, ...props }: HeroProps) => {
|
||||
<Link href="https://github.com/documenso/documenso" onClick={() => event('view-github')}>
|
||||
<Button variant="outline" className="rounded-full bg-transparent backdrop-blur-sm">
|
||||
<LuGithub className="mr-2 h-5 w-5" />
|
||||
Star on Github
|
||||
Star on GitHub
|
||||
</Button>
|
||||
</Link>
|
||||
</motion.div>
|
||||
@ -225,7 +225,8 @@ export const Hero = ({ className, ...props }: HeroProps) => {
|
||||
<span className="bg-primary text-black">
|
||||
(in a non-legally binding, but heartfelt way)
|
||||
</span>{' '}
|
||||
and lock in the community plan for forever, including everything we build this year.
|
||||
and lock in the early adopter plan for forever, including everything we build this
|
||||
year.
|
||||
</p>
|
||||
|
||||
<div className="flex h-24 items-center">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
||||
|
||||
return (
|
||||
<div className={cn('', className)} {...props}>
|
||||
<div className="flex items-center justify-center gap-x-6">
|
||||
<div className="bg-background sticky top-32 flex items-center justify-end gap-x-6 shadow-[-1px_-5px_2px_6px_hsl(var(--background))] md:top-[7.5rem] lg:static lg:justify-center">
|
||||
<AnimatePresence>
|
||||
<motion.button
|
||||
key="MONTHLY"
|
||||
@ -40,7 +40,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
||||
{period === 'MONTHLY' && (
|
||||
<motion.div
|
||||
layoutId={SELECTED_PLAN_BAR_LAYOUT_ID}
|
||||
className="bg-primary absolute bottom-0 left-0 h-[3px] w-full rounded-full"
|
||||
className="bg-foreground lg:bg-primary absolute bottom-0 left-0 h-[3px] w-full rounded-full"
|
||||
/>
|
||||
)}
|
||||
</motion.button>
|
||||
@ -63,7 +63,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
||||
{period === 'YEARLY' && (
|
||||
<motion.div
|
||||
layoutId={SELECTED_PLAN_BAR_LAYOUT_ID}
|
||||
className="bg-primary absolute bottom-0 left-0 h-[3px] w-full rounded-full"
|
||||
className="bg-foreground lg:bg-primary absolute bottom-0 left-0 h-[3px] w-full rounded-full"
|
||||
/>
|
||||
)}
|
||||
</motion.button>
|
||||
@ -102,7 +102,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-plan="community"
|
||||
data-plan="early-adopter"
|
||||
className="border-primary bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border-2 px-8 py-12 shadow-[0px_0px_0px_4px_#E3E3E380]"
|
||||
>
|
||||
<p className="text-foreground text-4xl font-medium">Early Adopters</p>
|
||||
@ -119,7 +119,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
||||
|
||||
<Button className="mt-6 rounded-full text-base" asChild>
|
||||
<Link
|
||||
href={`${NEXT_PUBLIC_WEBAPP_URL()}/signup?utm_source=pricing-community`}
|
||||
href={`${NEXT_PUBLIC_WEBAPP_URL()}/signup?utm_source=pricing-early-adopter`}
|
||||
target="_blank"
|
||||
>
|
||||
Signup Now
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
@ -51,7 +51,7 @@ export const ShareConnectPaidWidgetBento = ({
|
||||
<Card className="col-span-2 lg:col-span-1" spotlight>
|
||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||
<p className="text-foreground/80 leading-relaxed">
|
||||
<strong className="block">Connections (Soon).</strong>
|
||||
<strong className="block">Connections</strong>
|
||||
Create connections and automations with Zapier and more to integrate with your
|
||||
favorite tools.
|
||||
</p>
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
// https://github.com/documenso/documenso/pull/1044/files#r1538258462
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { StatusWidget } from './status-widget';
|
||||
|
||||
export function StatusWidgetContainer() {
|
||||
return (
|
||||
<Suspense fallback={<StatusWidgetFallback />}>
|
||||
<StatusWidget slug="documenso-status" />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusWidgetFallback() {
|
||||
return (
|
||||
<div className="border-border inline-flex max-w-fit items-center justify-between space-x-2 rounded-md border border-gray-200 px-2 py-2 pr-3 text-sm">
|
||||
<span className="bg-muted h-2 w-36 animate-pulse rounded-md" />
|
||||
<span className="bg-muted relative inline-flex h-2 w-2 rounded-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
73
apps/marketing/src/components/(marketing)/status-widget.tsx
Normal file
73
apps/marketing/src/components/(marketing)/status-widget.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import { memo, use } from 'react';
|
||||
|
||||
import { type Status, getStatus } from '@openstatus/react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
const getStatusLevel = (level: Status) => {
|
||||
return {
|
||||
operational: {
|
||||
label: 'Operational',
|
||||
color: 'bg-green-500',
|
||||
color2: 'bg-green-400',
|
||||
},
|
||||
degraded_performance: {
|
||||
label: 'Degraded Performance',
|
||||
color: 'bg-yellow-500',
|
||||
color2: 'bg-yellow-400',
|
||||
},
|
||||
partial_outage: {
|
||||
label: 'Partial Outage',
|
||||
color: 'bg-yellow-500',
|
||||
color2: 'bg-yellow-400',
|
||||
},
|
||||
major_outage: {
|
||||
label: 'Major Outage',
|
||||
color: 'bg-red-500',
|
||||
color2: 'bg-red-400',
|
||||
},
|
||||
unknown: {
|
||||
label: 'Unknown',
|
||||
color: 'bg-gray-500',
|
||||
color2: 'bg-gray-400',
|
||||
},
|
||||
incident: {
|
||||
label: 'Incident',
|
||||
color: 'bg-yellow-500',
|
||||
color2: 'bg-yellow-400',
|
||||
},
|
||||
under_maintenance: {
|
||||
label: 'Under Maintenance',
|
||||
color: 'bg-gray-500',
|
||||
color2: 'bg-gray-400',
|
||||
},
|
||||
}[level];
|
||||
};
|
||||
|
||||
export const StatusWidget = memo(function StatusWidget({ slug }: { slug: string }) {
|
||||
const { status } = use(getStatus(slug));
|
||||
const level = getStatusLevel(status);
|
||||
|
||||
return (
|
||||
<a
|
||||
className="border-border inline-flex max-w-fit items-center justify-between gap-2 space-x-2 rounded-md border border-gray-200 px-3 py-1 text-sm"
|
||||
href="https://status.documenso.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div>
|
||||
<p className="text-sm">{level.label}</p>
|
||||
</div>
|
||||
|
||||
<span className="relative ml-auto flex h-1.5 w-1.5">
|
||||
<span
|
||||
className={cn(
|
||||
'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75',
|
||||
level.color2,
|
||||
)}
|
||||
/>
|
||||
<span className={cn('relative inline-flex h-1.5 w-1.5 rounded-full', level.color)} />
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
});
|
||||
@ -199,7 +199,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
||||
className="bg-foreground/5 col-span-12 flex flex-col rounded-2xl p-6 lg:col-span-5"
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
>
|
||||
<h3 className="text-xl font-semibold">Sign up to Community Plan</h3>
|
||||
<h3 className="text-xl font-semibold">Sign up to Early Adopter Plan</h3>
|
||||
<p className="text-muted-foreground mt-2 text-xs">
|
||||
with Timur Ercan & Lucas Smith from Documenso
|
||||
</p>
|
||||
@ -346,7 +346,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
||||
{signatureText && (
|
||||
<p
|
||||
className={cn(
|
||||
'text-foreground text-4xl font-semibold [font-family:var(--font-caveat)]',
|
||||
'text-foreground truncate text-4xl font-semibold [font-family:var(--font-caveat)]',
|
||||
)}
|
||||
>
|
||||
{signatureText}
|
||||
@ -360,7 +360,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
||||
>
|
||||
<Input
|
||||
id="signatureText"
|
||||
className="text-foreground placeholder:text-muted-foreground border-none p-0 text-sm focus-visible:ring-0"
|
||||
className="text-foreground placeholder:text-muted-foreground truncate border-none p-0 text-sm focus-visible:ring-0"
|
||||
placeholder="Draw or type name here"
|
||||
disabled={isSubmitting}
|
||||
{...register('signatureText', {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { FieldError } from 'react-hook-form';
|
||||
import type { FieldError } from 'react-hook-form';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { SVGAttributes } from 'react';
|
||||
import type { SVGAttributes } from 'react';
|
||||
|
||||
export type BackgroundProps = Omit<SVGAttributes<SVGElement>, 'viewBox'>;
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
||||
import { ThemeProviderProps } from 'next-themes/dist/types';
|
||||
import type { ThemeProviderProps } from 'next-themes/dist/types';
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||
|
||||
Reference in New Issue
Block a user