Merge branch 'main' of https://github.com/documenso/documenso into feat/swagger-styling

This commit is contained in:
Adithya Krishna
2024-03-13 09:56:16 +05:30
7 changed files with 215 additions and 146 deletions

View File

@ -12,6 +12,7 @@ export const BlogPost = defineDocumentType(() => ({
authorName: { type: 'string', required: true }, authorName: { type: 'string', required: true },
authorImage: { type: 'string', required: false }, authorImage: { type: 'string', required: false },
authorRole: { type: 'string', required: true }, authorRole: { type: 'string', required: true },
cta: { type: 'boolean', required: false, default: true },
}, },
computedFields: { computedFields: {
href: { type: 'string', resolve: (post) => `/${post._raw.flattenedPath}` }, href: { type: 'string', resolve: (post) => `/${post._raw.flattenedPath}` },

View File

@ -7,6 +7,8 @@ import { ChevronLeft } from 'lucide-react';
import type { MDXComponents } from 'mdx/types'; import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks'; import { useMDXComponent } from 'next-contentlayer/hooks';
import { CallToAction } from '~/components/(marketing)/call-to-action';
export const dynamic = 'force-dynamic'; export const dynamic = 'force-dynamic';
export const generateMetadata = ({ params }: { params: { post: string } }) => { export const generateMetadata = ({ params }: { params: { post: string } }) => {
@ -42,53 +44,57 @@ export default function BlogPostPage({ params }: { params: { post: string } }) {
const MDXContent = useMDXComponent(post.body.code); const MDXContent = useMDXComponent(post.body.code);
return ( return (
<article className="prose dark:prose-invert mx-auto py-8"> <div>
<div className="mb-6 text-center"> <article className="prose dark:prose-invert mx-auto py-8">
<time dateTime={post.date} className="text-muted-foreground mb-1 text-xs"> <div className="mb-6 text-center">
{new Date(post.date).toLocaleDateString()} <time dateTime={post.date} className="text-muted-foreground mb-1 text-xs">
</time> {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="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"> <div className="bg-foreground h-10 w-10 rounded-full">
{post.authorImage && ( {post.authorImage && (
<img <img
src={post.authorImage} src={post.authorImage}
alt={`Image of ${post.authorName}`} alt={`Image of ${post.authorName}`}
className="bg-foreground/10 h-10 w-10 rounded-full" className="bg-foreground/10 h-10 w-10 rounded-full"
/> />
)} )}
</div> </div>
<div className="text-sm leading-6"> <div className="text-sm leading-6">
<p className="text-foreground text-left font-semibold">{post.authorName}</p> <p className="text-foreground text-left font-semibold">{post.authorName}</p>
<p className="text-muted-foreground">{post.authorRole}</p> <p className="text-muted-foreground">{post.authorRole}</p>
</div>
</div> </div>
</div> </div>
</div>
<MDXContent components={mdxComponents} /> <MDXContent components={mdxComponents} />
{post.tags.length > 0 && ( {post.tags.length > 0 && (
<ul className="not-prose flex list-none flex-row space-x-2 px-0"> <ul className="not-prose flex list-none flex-row space-x-2 px-0">
{post.tags.map((tag, i) => ( {post.tags.map((tag, i) => (
<li <li
key={`tag-${i}`} 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" 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} {tag}
</li> </li>
))} ))}
</ul> </ul>
)} )}
<hr /> <hr />
<Link href="/blog" className="text-muted-foreground flex items-center hover:opacity-60"> <Link href="/blog" className="text-muted-foreground flex items-center hover:opacity-60">
<ChevronLeft className="mr-2 h-6 w-6" /> <ChevronLeft className="mr-2 h-6 w-6" />
Back to all posts Back to all posts
</Link> </Link>
</article> </article>
{post.cta && <CallToAction className="mt-8" utmSource={`blog__${params.post}`} />}
</div>
); );
} }

View File

@ -7,6 +7,7 @@ import { getUserMonthlyGrowth } from '@documenso/lib/server-only/user/get-user-m
import { FUNDING_RAISED } from '~/app/(marketing)/open/data'; import { FUNDING_RAISED } from '~/app/(marketing)/open/data';
import { MetricCard } from '~/app/(marketing)/open/metric-card'; import { MetricCard } from '~/app/(marketing)/open/metric-card';
import { SalaryBands } from '~/app/(marketing)/open/salary-bands'; import { SalaryBands } from '~/app/(marketing)/open/salary-bands';
import { CallToAction } from '~/components/(marketing)/call-to-action';
import { BarMetric } from './bar-metrics'; import { BarMetric } from './bar-metrics';
import { CapTable } from './cap-table'; import { CapTable } from './cap-table';
@ -141,114 +142,118 @@ export default async function OpenPage() {
const MONTHLY_USERS = await getUserMonthlyGrowth(); const MONTHLY_USERS = await getUserMonthlyGrowth();
return ( return (
<div className="mx-auto mt-6 max-w-screen-lg sm:mt-12"> <div>
<div className="flex flex-col items-center justify-center"> <div className="mx-auto mt-6 max-w-screen-lg sm:mt-12">
<h1 className="text-3xl font-bold lg:text-5xl">Open Startup</h1> <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"> <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 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:{' '} to share our journey with you. You can read more about why here:{' '}
<a <a
className="font-bold" className="font-bold"
href="https://documenso.com/blog/pre-seed" href="https://documenso.com/blog/pre-seed"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
Announcing Open Metrics Announcing Open Metrics
</a> </a>
</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" />
<Typefully 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> </p>
</div> </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" />
<Typefully 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> </div>
<CallToAction className="mt-12" utmSource="open-page" />
</div> </div>
); );
} }

View 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>
);
};

View File

@ -1,5 +1,7 @@
'use client'; 'use client';
import { useState } from 'react';
import { signOut } from 'next-auth/react'; import { signOut } from 'next-auth/react';
import type { User } from '@documenso/prisma/client'; import type { User } from '@documenso/prisma/client';
@ -16,6 +18,8 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from '@documenso/ui/primitives/dialog'; } from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
export type DeleteAccountDialogProps = { export type DeleteAccountDialogProps = {
@ -28,6 +32,8 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
const hasTwoFactorAuthentication = user.twoFactorEnabled; const hasTwoFactorAuthentication = user.twoFactorEnabled;
const [enteredEmail, setEnteredEmail] = useState<string>('');
const { mutateAsync: deleteAccount, isLoading: isDeletingAccount } = const { mutateAsync: deleteAccount, isLoading: isDeletingAccount } =
trpc.profile.deleteAccount.useMutation(); trpc.profile.deleteAccount.useMutation();
@ -76,10 +82,11 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
</div> </div>
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<Dialog> <Dialog onOpenChange={() => setEnteredEmail('')}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="destructive">Delete Account</Button> <Button variant="destructive">Delete Account</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader className="space-y-4"> <DialogHeader className="space-y-4">
<DialogTitle>Delete Account</DialogTitle> <DialogTitle>Delete Account</DialogTitle>
@ -105,12 +112,29 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{!hasTwoFactorAuthentication && (
<div className="mt-4">
<Label>
Please type{' '}
<span className="text-muted-foreground font-semibold">{user.email}</span> to
confirm.
</Label>
<Input
type="text"
className="mt-2"
aria-label="Confirm Email"
value={enteredEmail}
onChange={(e) => setEnteredEmail(e.target.value)}
/>
</div>
)}
<DialogFooter> <DialogFooter>
<Button <Button
onClick={onDeleteAccount} onClick={onDeleteAccount}
loading={isDeletingAccount} loading={isDeletingAccount}
variant="destructive" variant="destructive"
disabled={hasTwoFactorAuthentication} disabled={hasTwoFactorAuthentication || enteredEmail !== user.email}
> >
{isDeletingAccount ? 'Deleting account...' : 'Confirm Deletion'} {isDeletingAccount ? 'Deleting account...' : 'Confirm Deletion'}
</Button> </Button>

View File

@ -16,6 +16,8 @@ test('delete user', async ({ page }) => {
}); });
await page.getByRole('button', { name: 'Delete Account' }).click(); await page.getByRole('button', { name: 'Delete Account' }).click();
await page.getByLabel('Confirm Email').fill(user.email);
await expect(page.getByRole('button', { name: 'Confirm Deletion' })).not.toBeDisabled();
await page.getByRole('button', { name: 'Confirm Deletion' }).click(); await page.getByRole('button', { name: 'Confirm Deletion' }).click();
await page.waitForURL(`${WEBAPP_BASE_URL}/signin`); await page.waitForURL(`${WEBAPP_BASE_URL}/signin`);

View File

@ -1,2 +1,2 @@
export const URL_REGEX = export const URL_REGEX =
/^(https?):\/\/(?:www\.)?[a-zA-Z0-9-]+\.[a-zA-Z0-9()]{2,}(?:\/[a-zA-Z0-9-._?&=/]*)?$/i; /^(https?):\/\/(?:www\.)?(?:[a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z0-9()]{2,}(?:\/[a-zA-Z0-9-._?&=/]*)?$/i;