From a9bb559568917540f3274480ce10564f925f9d85 Mon Sep 17 00:00:00 2001
From: Catalin Pit <25515812+catalinpit@users.noreply.github.com>
Date: Thu, 14 Mar 2024 10:56:46 +0200
Subject: [PATCH 1/8] fix: avoid opengraph image limit
---
.../blog/[post]/opengraph/route.tsx | 77 +++++++++++++++++++
.../src/app/(marketing)/blog/[post]/page.tsx | 8 +-
2 files changed, 84 insertions(+), 1 deletion(-)
create mode 100644 apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
new file mode 100644
index 000000000..906ee18cd
--- /dev/null
+++ b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
@@ -0,0 +1,77 @@
+import { ImageResponse } from 'next/og';
+import { NextResponse } from 'next/server';
+
+import { allBlogPosts } from 'contentlayer/generated';
+
+export const runtime = 'edge';
+
+const contentType = 'image/png';
+
+const IMAGE_SIZE = {
+ width: 1200,
+ height: 630,
+};
+
+type BlogPostOpenGraphImageProps = {
+ params: { post: string };
+};
+
+export async function GET(_request: Request, { params }: BlogPostOpenGraphImageProps) {
+ const blogPost = allBlogPosts.find((post) => post._raw.flattenedPath === `blog/${params.post}`);
+
+ if (!blogPost) {
+ 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
+ // to a constant will break og image generation.
+ const [interBold, interRegular, backgroundImage, logoImage] = await Promise.all([
+ fetch(new URL('@documenso/assets/fonts/inter-bold.ttf', import.meta.url)).then(async (res) =>
+ res.arrayBuffer(),
+ ),
+ fetch(new URL('@documenso/assets/fonts/inter-regular.ttf', import.meta.url)).then(async (res) =>
+ res.arrayBuffer(),
+ ),
+ fetch(new URL('@documenso/assets/images/background-blog-og.png', import.meta.url)).then(
+ async (res) => res.arrayBuffer(),
+ ),
+ fetch(new URL('@documenso/assets/logo.png', import.meta.url)).then(async (res) =>
+ res.arrayBuffer(),
+ ),
+ ]);
+
+ return new ImageResponse(
+ (
+
+ {/* @ts-expect-error Lack of typing from ImageResponse */}
+

+
+ {/* @ts-expect-error Lack of typing from ImageResponse */}
+

+
+
+ {blogPost.title}
+
+
+
Written by {blogPost.authorName}
+
+ ),
+ {
+ ...IMAGE_SIZE,
+ fonts: [
+ {
+ name: 'Inter',
+ data: interRegular,
+ style: 'normal',
+ weight: 400,
+ },
+ {
+ name: 'Inter',
+ data: interBold,
+ style: 'normal',
+ weight: 700,
+ },
+ ],
+ },
+ );
+}
diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
index bd5fdb2da..fc65d9772 100644
--- a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
+++ b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
@@ -25,6 +25,12 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => {
absolute: `${blogPost.title} - Documenso Blog`,
},
description: blogPost.description,
+ openGraph: {
+ images: [`${blogPost.href}/opengraph`],
+ },
+ twitter: {
+ images: [`${blogPost.href}/opengraph`],
+ },
};
};
@@ -94,7 +100,7 @@ export default function BlogPostPage({ params }: { params: { post: string } }) {
- {post.cta && }
+ {post.cta && }
);
}
From d6c8a3d32c5939536f8c87e4b6938f03d35744f8 Mon Sep 17 00:00:00 2001
From: Lucas Smith
Date: Thu, 14 Mar 2024 09:20:01 +0000
Subject: [PATCH 2/8] fix: what happens if we use a dynamic import?
---
.../src/app/(marketing)/blog/[post]/opengraph/route.tsx | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
index 906ee18cd..6f16b5092 100644
--- a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
+++ b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
@@ -1,12 +1,8 @@
import { ImageResponse } from 'next/og';
import { NextResponse } from 'next/server';
-import { allBlogPosts } from 'contentlayer/generated';
-
export const runtime = 'edge';
-const contentType = 'image/png';
-
const IMAGE_SIZE = {
width: 1200,
height: 630,
@@ -17,6 +13,8 @@ type BlogPostOpenGraphImageProps = {
};
export async function GET(_request: Request, { params }: BlogPostOpenGraphImageProps) {
+ const { allBlogPosts } = await import('contentlayer/generated');
+
const blogPost = allBlogPosts.find((post) => post._raw.flattenedPath === `blog/${params.post}`);
if (!blogPost) {
From 4926b6de509b7b5942a16238030507faf7098f74 Mon Sep 17 00:00:00 2001
From: Lucas Smith
Date: Thu, 14 Mar 2024 09:40:26 +0000
Subject: [PATCH 3/8] fix: boring sign/verify approach
---
.../blog/[post]/opengraph/route.tsx | 22 +++++++++++--------
.../src/app/(marketing)/blog/[post]/page.tsx | 18 +++++++++++++--
2 files changed, 29 insertions(+), 11 deletions(-)
diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
index 6f16b5092..f17a7931a 100644
--- a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
+++ b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
@@ -1,6 +1,8 @@
import { ImageResponse } from 'next/og';
import { NextResponse } from 'next/server';
+import { verify } from '@documenso/lib/server-only/crypto/verify';
+
export const runtime = 'edge';
const IMAGE_SIZE = {
@@ -8,16 +10,18 @@ const IMAGE_SIZE = {
height: 630,
};
-type BlogPostOpenGraphImageProps = {
- params: { post: string };
-};
+export async function GET(_request: Request) {
+ const url = new URL(_request.url);
-export async function GET(_request: Request, { params }: BlogPostOpenGraphImageProps) {
- const { allBlogPosts } = await import('contentlayer/generated');
+ const signature = url.searchParams.get('sig');
+ const title = url.searchParams.get('title');
+ const author = url.searchParams.get('author');
- const blogPost = allBlogPosts.find((post) => post._raw.flattenedPath === `blog/${params.post}`);
+ if (!title || !author || !signature) {
+ return NextResponse.json({ error: 'Not found' }, { status: 404 });
+ }
- if (!blogPost) {
+ if (!verify({ title, author }, signature)) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
@@ -48,10 +52,10 @@ export async function GET(_request: Request, { params }: BlogPostOpenGraphImageP
- {blogPost.title}
+ {title}
- Written by {blogPost.authorName}
+ Written by {author}
),
{
diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
index fc65d9772..d8ef587c4 100644
--- a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
+++ b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
@@ -7,6 +7,8 @@ import { ChevronLeft } from 'lucide-react';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
+import { sign } from '@documenso/lib/server-only/crypto/sign';
+
import { CallToAction } from '~/components/(marketing)/call-to-action';
export const dynamic = 'force-dynamic';
@@ -20,16 +22,28 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => {
};
}
+ const signature = sign({
+ title: blogPost.title,
+ author: blogPost.authorName,
+ });
+
+ // Use the url constructor to ensure that things are escaped as they should be
+ const openGraphImageUrl = new URL(`${blogPost.href}/opengraph`);
+
+ openGraphImageUrl.searchParams.set('title', blogPost.title);
+ openGraphImageUrl.searchParams.set('author', blogPost.authorName);
+ openGraphImageUrl.searchParams.set('sig', signature);
+
return {
title: {
absolute: `${blogPost.title} - Documenso Blog`,
},
description: blogPost.description,
openGraph: {
- images: [`${blogPost.href}/opengraph`],
+ images: [openGraphImageUrl.toString()],
},
twitter: {
- images: [`${blogPost.href}/opengraph`],
+ images: [openGraphImageUrl.toString()],
},
};
};
From f5967e28c3fa751b54e1c9ad79bbb04ee52641b9 Mon Sep 17 00:00:00 2001
From: Lucas Smith
Date: Thu, 14 Mar 2024 10:02:09 +0000
Subject: [PATCH 4/8] fix: without protection?
---
.../blog/[post]/opengraph-image.tsx | 76 -------------------
.../blog/[post]/opengraph/route.tsx | 9 +--
2 files changed, 1 insertion(+), 84 deletions(-)
delete mode 100644 apps/marketing/src/app/(marketing)/blog/[post]/opengraph-image.tsx
diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph-image.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph-image.tsx
deleted file mode 100644
index 4c01967d2..000000000
--- a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph-image.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { ImageResponse } from 'next/og';
-
-import { allBlogPosts } from 'contentlayer/generated';
-
-export const runtime = 'edge';
-
-export const contentType = 'image/png';
-
-export const IMAGE_SIZE = {
- width: 1200,
- height: 630,
-};
-
-type BlogPostOpenGraphImageProps = {
- params: { post: string };
-};
-
-export default async function BlogPostOpenGraphImage({ params }: BlogPostOpenGraphImageProps) {
- const blogPost = allBlogPosts.find((post) => post._raw.flattenedPath === `blog/${params.post}`);
-
- if (!blogPost) {
- return null;
- }
-
- // The long urls are needed for a compiler optimisation on the Next.js side, lifting this up
- // to a constant will break og image generation.
- const [interBold, interRegular, backgroundImage, logoImage] = await Promise.all([
- fetch(new URL('@documenso/assets/fonts/inter-bold.ttf', import.meta.url)).then(async (res) =>
- res.arrayBuffer(),
- ),
- fetch(new URL('@documenso/assets/fonts/inter-regular.ttf', import.meta.url)).then(async (res) =>
- res.arrayBuffer(),
- ),
- fetch(new URL('@documenso/assets/images/background-blog-og.png', import.meta.url)).then(
- async (res) => res.arrayBuffer(),
- ),
- fetch(new URL('@documenso/assets/logo.png', import.meta.url)).then(async (res) =>
- res.arrayBuffer(),
- ),
- ]);
-
- return new ImageResponse(
- (
-
- {/* @ts-expect-error Lack of typing from ImageResponse */}
-

-
- {/* @ts-expect-error Lack of typing from ImageResponse */}
-

-
-
- {blogPost.title}
-
-
-
Written by {blogPost.authorName}
-
- ),
- {
- ...IMAGE_SIZE,
- fonts: [
- {
- name: 'Inter',
- data: interRegular,
- style: 'normal',
- weight: 400,
- },
- {
- name: 'Inter',
- data: interBold,
- style: 'normal',
- weight: 700,
- },
- ],
- },
- );
-}
diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
index f17a7931a..70233bbdd 100644
--- a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
+++ b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph/route.tsx
@@ -1,8 +1,6 @@
import { ImageResponse } from 'next/og';
import { NextResponse } from 'next/server';
-import { verify } from '@documenso/lib/server-only/crypto/verify';
-
export const runtime = 'edge';
const IMAGE_SIZE = {
@@ -13,15 +11,10 @@ const IMAGE_SIZE = {
export async function GET(_request: Request) {
const url = new URL(_request.url);
- const signature = url.searchParams.get('sig');
const title = url.searchParams.get('title');
const author = url.searchParams.get('author');
- if (!title || !author || !signature) {
- return NextResponse.json({ error: 'Not found' }, { status: 404 });
- }
-
- if (!verify({ title, author }, signature)) {
+ if (!title || !author) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
From 0db2e6643dd8678283eb3b1ab3ab499bfe62a0fc Mon Sep 17 00:00:00 2001
From: Lucas Smith
Date: Thu, 14 Mar 2024 10:39:48 +0000
Subject: [PATCH 5/8] fix: final final v2
---
.../src/app/(marketing)/blog/[post]/page.tsx | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
index d8ef587c4..b0d59edc1 100644
--- a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
+++ b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
@@ -28,11 +28,11 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => {
});
// Use the url constructor to ensure that things are escaped as they should be
- const openGraphImageUrl = new URL(`${blogPost.href}/opengraph`);
-
- openGraphImageUrl.searchParams.set('title', blogPost.title);
- openGraphImageUrl.searchParams.set('author', blogPost.authorName);
- openGraphImageUrl.searchParams.set('sig', signature);
+ const searchParams = new URLSearchParams({
+ title: blogPost.title,
+ author: blogPost.authorName,
+ sig: signature,
+ });
return {
title: {
@@ -40,10 +40,10 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => {
},
description: blogPost.description,
openGraph: {
- images: [openGraphImageUrl.toString()],
+ images: [`${blogPost.href}/opengraph?${searchParams.toString()}`],
},
twitter: {
- images: [openGraphImageUrl.toString()],
+ images: [`${blogPost.href}/opengraph?${searchParams.toString()}`],
},
};
};
From 524a7918d55754a7c26e107f268db298cd7a3eb3 Mon Sep 17 00:00:00 2001
From: Lucas Smith
Date: Thu, 14 Mar 2024 10:41:59 +0000
Subject: [PATCH 6/8] fix: toss the signature
---
apps/marketing/src/app/(marketing)/blog/[post]/page.tsx | 8 --------
1 file changed, 8 deletions(-)
diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
index b0d59edc1..3e50f8305 100644
--- a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
+++ b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx
@@ -7,8 +7,6 @@ import { ChevronLeft } from 'lucide-react';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
-import { sign } from '@documenso/lib/server-only/crypto/sign';
-
import { CallToAction } from '~/components/(marketing)/call-to-action';
export const dynamic = 'force-dynamic';
@@ -22,16 +20,10 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => {
};
}
- const signature = sign({
- title: blogPost.title,
- author: blogPost.authorName,
- });
-
// Use the url constructor to ensure that things are escaped as they should be
const searchParams = new URLSearchParams({
title: blogPost.title,
author: blogPost.authorName,
- sig: signature,
});
return {
From d5c4885c67a0a71aabf0c370b5a19e77da4de119 Mon Sep 17 00:00:00 2001
From: Lucas Smith
Date: Thu, 14 Mar 2024 12:39:09 +0000
Subject: [PATCH 7/8] fix: update signup form to handle password managers
better
---
apps/web/src/app/(unauthenticated)/layout.tsx | 2 +-
.../src/app/(unauthenticated)/signup/page.tsx | 2 +-
apps/web/src/components/forms/v2/signup.tsx | 32 ++++++++-----------
3 files changed, 16 insertions(+), 20 deletions(-)
diff --git a/apps/web/src/app/(unauthenticated)/layout.tsx b/apps/web/src/app/(unauthenticated)/layout.tsx
index 03a73278f..05055d508 100644
--- a/apps/web/src/app/(unauthenticated)/layout.tsx
+++ b/apps/web/src/app/(unauthenticated)/layout.tsx
@@ -10,7 +10,7 @@ type UnauthenticatedLayoutProps = {
export default function UnauthenticatedLayout({ children }: UnauthenticatedLayoutProps) {
return (
-
+
diff --git a/apps/web/src/components/forms/v2/signup.tsx b/apps/web/src/components/forms/v2/signup.tsx
index a7e33a759..b3b502993 100644
--- a/apps/web/src/components/forms/v2/signup.tsx
+++ b/apps/web/src/components/forms/v2/signup.tsx
@@ -108,17 +108,6 @@ export const SignUpFormV2 = ({
const name = form.watch('name');
const url = form.watch('url');
- // To continue we need to make sure name, email, password and signature are valid
- const canContinue =
- form.formState.dirtyFields.name &&
- form.formState.errors.name === undefined &&
- form.formState.dirtyFields.email &&
- form.formState.errors.email === undefined &&
- form.formState.dirtyFields.password &&
- form.formState.errors.password === undefined &&
- form.formState.dirtyFields.signature &&
- form.formState.errors.signature === undefined;
-
const { mutateAsync: signup } = trpc.auth.signup.useMutation();
const onFormSubmit = async ({ name, email, password, signature, url }: TSignUpFormV2Schema) => {
@@ -169,6 +158,14 @@ export const SignUpFormV2 = ({
}
};
+ const onNextClick = async () => {
+ const valid = await form.trigger(['name', 'email', 'password', 'signature']);
+
+ if (valid) {
+ setStep('CLAIM_USERNAME');
+ }
+ };
+
const onSignUpWithGoogleClick = async () => {
try {
await signIn('google', { callbackUrl: SIGN_UP_REDIRECT_PATH });
@@ -224,7 +221,7 @@ export const SignUpFormV2 = ({
-
+
{step === 'BASIC_DETAILS' && (
Create a new account
@@ -257,8 +254,8 @@ export const SignUpFormV2 = ({
{step === 'BASIC_DETAILS' && (