mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
Merge branch 'main' of https://github.com/documenso/documenso into feat/swagger-styling
This commit is contained in:
@ -12,6 +12,7 @@ export const BlogPost = defineDocumentType(() => ({
|
||||
authorName: { type: 'string', required: true },
|
||||
authorImage: { type: 'string', required: false },
|
||||
authorRole: { type: 'string', required: true },
|
||||
cta: { type: 'boolean', required: false, default: true },
|
||||
},
|
||||
computedFields: {
|
||||
href: { type: 'string', resolve: (post) => `/${post._raw.flattenedPath}` },
|
||||
|
||||
@ -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 } }) => {
|
||||
@ -42,53 +44,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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import { getUserMonthlyGrowth } from '@documenso/lib/server-only/user/get-user-m
|
||||
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';
|
||||
@ -141,114 +142,118 @@ export default async function OpenPage() {
|
||||
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>
|
||||
</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 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="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>
|
||||
|
||||
<CallToAction className="mt-12" utmSource="open-page" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { signOut } from 'next-auth/react';
|
||||
|
||||
import type { User } from '@documenso/prisma/client';
|
||||
@ -16,6 +18,8 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} 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';
|
||||
|
||||
export type DeleteAccountDialogProps = {
|
||||
@ -28,6 +32,8 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
|
||||
|
||||
const hasTwoFactorAuthentication = user.twoFactorEnabled;
|
||||
|
||||
const [enteredEmail, setEnteredEmail] = useState<string>('');
|
||||
|
||||
const { mutateAsync: deleteAccount, isLoading: isDeletingAccount } =
|
||||
trpc.profile.deleteAccount.useMutation();
|
||||
|
||||
@ -76,10 +82,11 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<Dialog>
|
||||
<Dialog onOpenChange={() => setEnteredEmail('')}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="destructive">Delete Account</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent>
|
||||
<DialogHeader className="space-y-4">
|
||||
<DialogTitle>Delete Account</DialogTitle>
|
||||
@ -105,12 +112,29 @@ export const DeleteAccountDialog = ({ className, user }: DeleteAccountDialogProp
|
||||
</DialogDescription>
|
||||
</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>
|
||||
<Button
|
||||
onClick={onDeleteAccount}
|
||||
loading={isDeletingAccount}
|
||||
variant="destructive"
|
||||
disabled={hasTwoFactorAuthentication}
|
||||
disabled={hasTwoFactorAuthentication || enteredEmail !== user.email}
|
||||
>
|
||||
{isDeletingAccount ? 'Deleting account...' : 'Confirm Deletion'}
|
||||
</Button>
|
||||
|
||||
@ -16,6 +16,8 @@ test('delete user', async ({ page }) => {
|
||||
});
|
||||
|
||||
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.waitForURL(`${WEBAPP_BASE_URL}/signin`);
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user