diff --git a/apps/marketing/src/app/(marketing)/blog/[post]/opengraph-image.tsx b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph-image.tsx new file mode 100644 index 000000000..f9987dd27 --- /dev/null +++ b/apps/marketing/src/app/(marketing)/blog/[post]/opengraph-image.tsx @@ -0,0 +1,76 @@ +import { ImageResponse } from 'next/server'; + +import { allBlogPosts } from 'contentlayer/generated'; + +export const runtime = 'edge'; + +export const size = { + width: 1200, + height: 630, +}; + +export const contentType = 'image/png'; + +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('./../../../../assets/inter-bold.ttf', import.meta.url)).then(async (res) => + res.arrayBuffer(), + ), + fetch(new URL('./../../../../assets/inter-regular.ttf', import.meta.url)).then(async (res) => + res.arrayBuffer(), + ), + fetch(new URL('./../../../../assets/background-blog-og.png', import.meta.url)).then( + async (res) => res.arrayBuffer(), + ), + fetch(new URL('./../../../../../public/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}

+
+ ), + { + ...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 5192dec32..7edf29ec2 100644 --- a/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx +++ b/apps/marketing/src/app/(marketing)/blog/[post]/page.tsx @@ -17,7 +17,9 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => { notFound(); } - return { title: `Documenso - ${blogPost.title}` }; + return { + title: `Documenso - ${blogPost.title}`, + }; }; const mdxComponents: MDXComponents = { diff --git a/apps/marketing/src/app/(marketing)/claimed/page.tsx b/apps/marketing/src/app/(marketing)/claimed/page.tsx index ce748006e..f56ae2b26 100644 --- a/apps/marketing/src/app/(marketing)/claimed/page.tsx +++ b/apps/marketing/src/app/(marketing)/claimed/page.tsx @@ -27,7 +27,11 @@ export type ClaimedPlanPageProps = { export default async function ClaimedPlanPage({ searchParams = {} }: ClaimedPlanPageProps) { const { sessionId } = searchParams; - const session = await stripe.checkout.sessions.retrieve(sessionId as string); + if (typeof sessionId !== 'string') { + redirect('/'); + } + + const session = await stripe.checkout.sessions.retrieve(sessionId); const user = await prisma.user.findFirst({ where: { @@ -157,7 +161,6 @@ export default async function ClaimedPlanPage({ searchParams = {} }: ClaimedPlan

{ const event = usePlausible(); const [period, setPeriod] = useState<'MONTHLY' | 'YEARLY'>(() => - // eslint-disable-next-line turbo/no-undeclared-env-vars params?.get('planId') === process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID ? 'YEARLY' : 'MONTHLY', @@ -30,11 +29,9 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => { const planId = useMemo(() => { if (period === 'MONTHLY') { - // eslint-disable-next-line turbo/no-undeclared-env-vars return process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID; } - // eslint-disable-next-line turbo/no-undeclared-env-vars return process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID; }, [period]); diff --git a/apps/marketing/src/components/(marketing)/widget.tsx b/apps/marketing/src/components/(marketing)/widget.tsx index 15e15d04c..def90e0cd 100644 --- a/apps/marketing/src/components/(marketing)/widget.tsx +++ b/apps/marketing/src/components/(marketing)/widget.tsx @@ -139,7 +139,6 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => { setTimeout(resolve, 1000); }); - // eslint-disable-next-line turbo/no-undeclared-env-vars const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID; const claimPlanInput = signatureDataUrl @@ -147,7 +146,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => { name, email, planId, - signatureDataUrl: signatureDataUrl!, + signatureDataUrl: signatureDataUrl, signatureText: null, } : { @@ -155,7 +154,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => { email, planId, signatureDataUrl: null, - signatureText: signatureText!, + signatureText: signatureText ?? '', }; const [result] = await Promise.all([claimPlan(claimPlanInput), delay]); diff --git a/apps/marketing/src/pages/api/claim-plan/index.ts b/apps/marketing/src/pages/api/claim-plan/index.ts index a2e4108d2..abad354a8 100644 --- a/apps/marketing/src/pages/api/claim-plan/index.ts +++ b/apps/marketing/src/pages/api/claim-plan/index.ts @@ -43,7 +43,6 @@ export default async function handler( if (user && user.Subscription.length > 0) { return res.status(200).json({ - // eslint-disable-next-line turbo/no-undeclared-env-vars redirectUrl: `${process.env.NEXT_PUBLIC_APP_URL}/login`, }); } @@ -104,7 +103,6 @@ export default async function handler( mode: 'subscription', metadata, allow_promotion_codes: true, - // eslint-disable-next-line turbo/no-undeclared-env-vars success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/claimed?sessionId={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/pricing?email=${encodeURIComponent( email, diff --git a/apps/marketing/src/pages/api/stripe/webhook/index.ts b/apps/marketing/src/pages/api/stripe/webhook/index.ts index a0a4ccebb..3f3810fd4 100644 --- a/apps/marketing/src/pages/api/stripe/webhook/index.ts +++ b/apps/marketing/src/pages/api/stripe/webhook/index.ts @@ -17,14 +17,13 @@ import { SigningStatus, } from '@documenso/prisma/client'; -const log = (...args: any[]) => console.log('[stripe]', ...args); +const log = (...args: unknown[]) => console.log('[stripe]', ...args); export const config = { api: { bodyParser: false }, }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { - // eslint-disable-next-line turbo/no-undeclared-env-vars // if (!process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS) { // return res.status(500).json({ // success: false, @@ -55,6 +54,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) log('event-type:', event.type); if (event.type === 'checkout.session.completed') { + // This typecast is required since we don't want to create a guard for every event type + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const session = event.data.object as Stripe.Checkout.Session; if (session.metadata?.source === 'landing') { diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx index e1c9a79e1..7ed28feca 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -130,7 +130,13 @@ export const EditDocumentForm = ({ }, }); - router.refresh(); + toast({ + title: 'Document sent', + description: 'Your document has been sent successfully.', + duration: 5000, + }); + + router.push('/dashboard'); } catch (err) { console.error(err); diff --git a/apps/web/src/app/(signing)/sign/[token]/form.tsx b/apps/web/src/app/(signing)/sign/[token]/form.tsx index 3666941dc..e18571e33 100644 --- a/apps/web/src/app/(signing)/sign/[token]/form.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/form.tsx @@ -87,9 +87,6 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) = className="h-44 w-full" defaultValue={signature ?? undefined} onChange={(value) => { - console.log({ - signpadValue: value, - }); setSignature(value); }} /> diff --git a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx index 68a61fb67..cb70ea4db 100644 --- a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx @@ -63,11 +63,6 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { const onSign = async (source: 'local' | 'provider' = 'provider') => { try { - console.log({ - providedSignature, - localSignature, - }); - if (!providedSignature && !localSignature) { setShowSignatureModal(true); return; diff --git a/apps/web/src/components/(dashboard)/period-selector/types.ts b/apps/web/src/components/(dashboard)/period-selector/types.ts index 4ebfe47f1..2b50f5d6c 100644 --- a/apps/web/src/components/(dashboard)/period-selector/types.ts +++ b/apps/web/src/components/(dashboard)/period-selector/types.ts @@ -1,5 +1,6 @@ export type PeriodSelectorValue = '' | '7d' | '14d' | '30d'; export const isPeriodSelectorValue = (value: unknown): value is PeriodSelectorValue => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return ['', '7d', '14d', '30d'].includes(value as string); }; diff --git a/apps/web/src/components/forms/profile.tsx b/apps/web/src/components/forms/profile.tsx index d65a0ce27..5b4045abb 100644 --- a/apps/web/src/components/forms/profile.tsx +++ b/apps/web/src/components/forms/profile.tsx @@ -21,7 +21,7 @@ import { FormErrorMessage } from '../form/form-error-message'; export const ZProfileFormSchema = z.object({ name: z.string().min(1), - signature: z.string().min(1), + signature: z.string().min(1, 'Signature Pad cannot be empty'), }); export type TProfileFormSchema = z.infer; @@ -122,6 +122,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => { /> )} /> + diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index 2f393ce71..25bfbbb40 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -1,25 +1,23 @@ import { NextRequest, NextResponse } from 'next/server'; -export default function middleware(req: NextRequest) { +import { getToken } from 'next-auth/jwt'; + +export default async function middleware(req: NextRequest) { if (req.nextUrl.pathname === '/') { const redirectUrl = new URL('/documents', req.url); return NextResponse.redirect(redirectUrl); } - // if (req.nextUrl.pathname.startsWith('/dashboard')) { - // const token = await getToken({ req }); + if (req.nextUrl.pathname.startsWith('/signin')) { + const token = await getToken({ req }); - // console.log('token', token); + if (token) { + const redirectUrl = new URL('/documents', req.url); - // if (!token) { - // console.log('has no token', req.url); - // const redirectUrl = new URL('/signin', req.url); - // redirectUrl.searchParams.set('callbackUrl', req.url); - - // return NextResponse.redirect(redirectUrl); - // } - // } + return NextResponse.redirect(redirectUrl); + } + } return NextResponse.next(); } diff --git a/apps/web/src/pages/api/claim-plan/index.ts b/apps/web/src/pages/api/claim-plan/index.ts index a2e4108d2..abad354a8 100644 --- a/apps/web/src/pages/api/claim-plan/index.ts +++ b/apps/web/src/pages/api/claim-plan/index.ts @@ -43,7 +43,6 @@ export default async function handler( if (user && user.Subscription.length > 0) { return res.status(200).json({ - // eslint-disable-next-line turbo/no-undeclared-env-vars redirectUrl: `${process.env.NEXT_PUBLIC_APP_URL}/login`, }); } @@ -104,7 +103,6 @@ export default async function handler( mode: 'subscription', metadata, allow_promotion_codes: true, - // eslint-disable-next-line turbo/no-undeclared-env-vars success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/claimed?sessionId={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/pricing?email=${encodeURIComponent( email, diff --git a/apps/web/src/pages/api/stripe/webhook/index.ts b/apps/web/src/pages/api/stripe/webhook/index.ts index dd15d4d81..6c678a33c 100644 --- a/apps/web/src/pages/api/stripe/webhook/index.ts +++ b/apps/web/src/pages/api/stripe/webhook/index.ts @@ -17,14 +17,13 @@ import { SigningStatus, } from '@documenso/prisma/client'; -const log = (...args: any[]) => console.log('[stripe]', ...args); +const log = (...args: unknown[]) => console.log('[stripe]', ...args); export const config = { api: { bodyParser: false }, }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { - // eslint-disable-next-line turbo/no-undeclared-env-vars // if (!process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS) { // return res.status(500).json({ // success: false, @@ -55,6 +54,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) log('event-type:', event.type); if (event.type === 'checkout.session.completed') { + // This is required since we don't want to create a guard for every event type + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const session = event.data.object as Stripe.Checkout.Session; if (session.metadata?.source === 'landing') { diff --git a/package-lock.json b/package-lock.json index 12c957494..1fa10b764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16319,6 +16319,7 @@ } }, "packages/ee": { + "name": "@documenso/ee", "version": "1.0.0", "license": "COMMERCIAL", "dependencies": { @@ -16362,7 +16363,7 @@ "packages/lib": { "name": "@documenso/lib", "version": "1.0.0", - "license": "SEE LICENSE IN LICENSE", + "license": "MIT", "dependencies": { "@documenso/email": "*", "@documenso/prisma": "*", diff --git a/packages/lib/server-only/field/set-fields-for-document.ts b/packages/lib/server-only/field/set-fields-for-document.ts index c54d35bc1..dc477bcea 100644 --- a/packages/lib/server-only/field/set-fields-for-document.ts +++ b/packages/lib/server-only/field/set-fields-for-document.ts @@ -84,6 +84,8 @@ export const setFieldsForDocument = async ({ }) : prisma.field.create({ data: { + // TODO: Rewrite this entire transaction because this is a mess + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion type: field.type!, page: field.pageNumber, positionX: field.pageX, diff --git a/packages/lib/server-only/stripe/index.ts b/packages/lib/server-only/stripe/index.ts index b2ab59fb9..505beaec8 100644 --- a/packages/lib/server-only/stripe/index.ts +++ b/packages/lib/server-only/stripe/index.ts @@ -1,7 +1,6 @@ import Stripe from 'stripe'; -// eslint-disable-next-line turbo/no-undeclared-env-vars -export const stripe = new Stripe(process.env.NEXT_PRIVATE_STRIPE_API_KEY!, { +export const stripe = new Stripe(process.env.NEXT_PRIVATE_STRIPE_API_KEY ?? '', { apiVersion: '2022-11-15', typescript: true, }); diff --git a/packages/lib/server-only/user/update-password.ts b/packages/lib/server-only/user/update-password.ts index 4133bc342..d987085ff 100644 --- a/packages/lib/server-only/user/update-password.ts +++ b/packages/lib/server-only/user/update-password.ts @@ -19,11 +19,13 @@ export const updatePassword = async ({ userId, password }: UpdatePasswordOptions const hashedPassword = await hash(password, SALT_ROUNDS); - // Compare the new password with the old password - const isSamePassword = await compare(password, user.password as string); + if (user.password) { + // Compare the new password with the old password + const isSamePassword = await compare(password, user.password); - if (isSamePassword) { - throw new Error('Your new password cannot be the same as your old password.'); + if (isSamePassword) { + throw new Error('Your new password cannot be the same as your old password.'); + } } const updatedUser = await prisma.user.update({ diff --git a/packages/lib/types/find-result-set.ts b/packages/lib/types/find-result-set.ts index 81b16f1ca..219b9b89c 100644 --- a/packages/lib/types/find-result-set.ts +++ b/packages/lib/types/find-result-set.ts @@ -1,5 +1,5 @@ export type FindResultSet = { - data: T extends Array ? T : T[]; + data: T extends Array ? T : T[]; count: number; currentPage: number; perPage: number; diff --git a/packages/lib/types/is-document-status.ts b/packages/lib/types/is-document-status.ts index 0666308a5..dbb5af489 100644 --- a/packages/lib/types/is-document-status.ts +++ b/packages/lib/types/is-document-status.ts @@ -1,5 +1,6 @@ import { DocumentStatus } from '@documenso/prisma/client'; export const isDocumentStatus = (value: unknown): value is DocumentStatus => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return Object.values(DocumentStatus).includes(value as DocumentStatus); }; diff --git a/turbo.json b/turbo.json index ab4460781..f7d3d342c 100644 --- a/turbo.json +++ b/turbo.json @@ -22,10 +22,13 @@ "globalEnv": [ "NEXTAUTH_URL", "NEXTAUTH_SECRET", + "NEXT_PUBLIC_APP_URL", "NEXT_PUBLIC_SITE_URL", "NEXT_PUBLIC_POSTHOG_KEY", "NEXT_PUBLIC_POSTHOG_HOST", "NEXT_PUBLIC_FEATURE_BILLING_ENABLED", + "NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID", + "NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID", "NEXT_PRIVATE_DATABASE_URL", "NEXT_PRIVATE_NEXT_AUTH_SECRET", "NEXT_PRIVATE_GOOGLE_CLIENT_ID", @@ -44,6 +47,7 @@ "NEXT_PRIVATE_SMTP_APIKEY", "NEXT_PRIVATE_SMTP_SECURE", "NEXT_PRIVATE_SMTP_FROM_NAME", - "NEXT_PRIVATE_SMTP_FROM_ADDRESS" + "NEXT_PRIVATE_SMTP_FROM_ADDRESS", + "NEXT_PRIVATE_STRIPE_API_KEY" ] -} \ No newline at end of file +}