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 */} + og-background + + {/* @ts-expect-error Lack of typing from ImageResponse */} + logo + +

+ {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 logo

- {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 */} - og-background - - {/* @ts-expect-error Lack of typing from ImageResponse */} - logo - -

- {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' && (
@@ -360,8 +357,8 @@ export const SignUpFormV2 = ({ {step === 'CLAIM_USERNAME' && (
@@ -431,9 +428,8 @@ export const SignUpFormV2 = ({ type="button" size="lg" className="flex-1 disabled:cursor-not-allowed" - disabled={!canContinue} loading={form.formState.isSubmitting} - onClick={() => setStep('CLAIM_USERNAME')} + onClick={onNextClick} > Next From fd4d5468cfc331f221d4e2d1a74ae60ac6af7fb4 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Thu, 14 Mar 2024 23:52:49 +1100 Subject: [PATCH 8/8] fix: use gif for readme --- README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 93c6d9f95..b83df450a 100644 --- a/README.md +++ b/README.md @@ -30,17 +30,8 @@ Contributor Covenant

-
- - - - - +
+
## About this project