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 2159b87f2..832e255fd 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -61,7 +61,7 @@ export const EditDocumentForm = ({ const { mutateAsync: setPasswordForDocument } = trpc.document.setPasswordForDocument.useMutation(); - const documentFlow: Record = { + const documentFlow: Record = { title: { title: 'Add Title', description: 'Add the title to the document.', diff --git a/apps/web/src/app/(dashboard)/settings/billing/create-billing-portal.action.ts b/apps/web/src/app/(dashboard)/settings/billing/create-billing-portal.action.ts index 885414515..7d952d599 100644 --- a/apps/web/src/app/(dashboard)/settings/billing/create-billing-portal.action.ts +++ b/apps/web/src/app/(dashboard)/settings/billing/create-billing-portal.action.ts @@ -1,16 +1,20 @@ 'use server'; +import { env } from 'next-runtime-env'; + import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer'; import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; export const createBillingPortal = async () => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const { user } = await getRequiredServerComponentSession(); const { stripeCustomer } = await getStripeCustomerByUser(user); return getPortalSession({ customerId: stripeCustomer.id, - returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`, + returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`, }); }; diff --git a/apps/web/src/app/(dashboard)/settings/billing/create-checkout.action.ts b/apps/web/src/app/(dashboard)/settings/billing/create-checkout.action.ts index f8f20030c..ef3fb0f30 100644 --- a/apps/web/src/app/(dashboard)/settings/billing/create-checkout.action.ts +++ b/apps/web/src/app/(dashboard)/settings/billing/create-checkout.action.ts @@ -1,5 +1,7 @@ 'use server'; +import { env } from 'next-runtime-env'; + import { getCheckoutSession } from '@documenso/ee/server-only/stripe/get-checkout-session'; import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer'; import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session'; @@ -11,6 +13,8 @@ export type CreateCheckoutOptions = { }; export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const session = await getRequiredServerComponentSession(); const { user, stripeCustomer } = await getStripeCustomerByUser(session.user); @@ -27,13 +31,13 @@ export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => { if (foundSubscription) { return getPortalSession({ customerId: stripeCustomer.id, - returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`, + returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`, }); } return getCheckoutSession({ customerId: stripeCustomer.id, priceId, - returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`, + returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`, }); }; diff --git a/apps/web/src/app/(share)/share/[slug]/opengraph/route.tsx b/apps/web/src/app/(share)/share/[slug]/opengraph/route.tsx index d8f0ecac8..e9977b8f3 100644 --- a/apps/web/src/app/(share)/share/[slug]/opengraph/route.tsx +++ b/apps/web/src/app/(share)/share/[slug]/opengraph/route.tsx @@ -1,6 +1,7 @@ import { ImageResponse } from 'next/og'; import { NextResponse } from 'next/server'; +import { env } from 'next-runtime-env'; import { P, match } from 'ts-pattern'; import type { ShareHandlerAPIResponse } from '~/pages/api/share'; @@ -22,6 +23,8 @@ type SharePageOpenGraphImageProps = { }; export async function GET(_request: Request, { params: { slug } }: SharePageOpenGraphImageProps) { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const [interSemiBold, interRegular, caveatRegular, shareFrameImage] = await Promise.all([ fetch(new URL('@documenso/assets/fonts/inter-semibold.ttf', import.meta.url)).then( async (res) => res.arrayBuffer(), @@ -37,7 +40,7 @@ export async function GET(_request: Request, { params: { slug } }: SharePageOpen ), ]); - const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const baseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const recipientOrSender: ShareHandlerAPIResponse = await fetch( new URL(`/api/share?slug=${slug}`, baseUrl), diff --git a/apps/web/src/app/(share)/share/[slug]/page.tsx b/apps/web/src/app/(share)/share/[slug]/page.tsx index 8e8fd7769..ab530ba0b 100644 --- a/apps/web/src/app/(share)/share/[slug]/page.tsx +++ b/apps/web/src/app/(share)/share/[slug]/page.tsx @@ -1,7 +1,9 @@ -import { Metadata } from 'next'; +import type { Metadata } from 'next'; import { headers } from 'next/headers'; import { redirect } from 'next/navigation'; +import { env } from 'next-runtime-env'; + import { APP_BASE_URL } from '@documenso/lib/constants/app'; type SharePageProps = { @@ -28,6 +30,8 @@ export function generateMetadata({ params: { slug } }: SharePageProps) { } export default function SharePage() { + const NEXT_PUBLIC_MARKETING_URL = env('NEXT_PUBLIC_MARKETING_URL'); + const userAgent = headers().get('User-Agent') ?? ''; // https://stackoverflow.com/questions/47026171/how-to-detect-bots-for-open-graph-with-user-agent @@ -35,5 +39,5 @@ export default function SharePage() { return null; } - redirect(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001'); + redirect(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001'); } diff --git a/apps/web/src/app/(unauthenticated)/signin/page.tsx b/apps/web/src/app/(unauthenticated)/signin/page.tsx index 5fda07e70..bbea70ecb 100644 --- a/apps/web/src/app/(unauthenticated)/signin/page.tsx +++ b/apps/web/src/app/(unauthenticated)/signin/page.tsx @@ -1,10 +1,14 @@ import Link from 'next/link'; +import { env } from 'next-runtime-env'; + import { IS_GOOGLE_SSO_ENABLED } from '@documenso/lib/constants/auth'; import { SignInForm } from '~/components/forms/signin'; export default function SignInPage() { + const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP'); + return (

Sign in to your account

@@ -15,7 +19,7 @@ export default function SignInPage() { - {process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && ( + {NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && (

Don't have an account?{' '} diff --git a/apps/web/src/app/(unauthenticated)/signup/page.tsx b/apps/web/src/app/(unauthenticated)/signup/page.tsx index 353716d9b..996797d55 100644 --- a/apps/web/src/app/(unauthenticated)/signup/page.tsx +++ b/apps/web/src/app/(unauthenticated)/signup/page.tsx @@ -1,10 +1,14 @@ import Link from 'next/link'; import { redirect } from 'next/navigation'; +import { env } from 'next-runtime-env'; + import { SignUpForm } from '~/components/forms/signup'; export default function SignUpPage() { - if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === 'true') { + const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP'); + + if (NEXT_PUBLIC_DISABLE_SIGNUP === 'true') { redirect('/signin'); } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index ac88469b0..f36d59353 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -2,6 +2,8 @@ import { Suspense } from 'react'; import { Caveat, Inter } from 'next/font/google'; +import { PublicEnvScript, env } from 'next-runtime-env'; + import { FeatureFlagProvider } from '@documenso/lib/client-only/providers/feature-flag'; import { LocaleProvider } from '@documenso/lib/client-only/providers/locale'; import { getServerComponentAllFlags } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag'; @@ -19,6 +21,8 @@ import './globals.css'; const fontInter = Inter({ subsets: ['latin'], variable: '--font-sans' }); const fontCaveat = Caveat({ subsets: ['latin'], variable: '--font-signature' }); +const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + export const metadata = { title: 'Documenso - The Open Source DocuSign Alternative', description: @@ -32,12 +36,12 @@ export const metadata = { description: 'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.', type: 'website', - images: [`${process.env.NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`], + images: [`${NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`], }, twitter: { site: '@documenso', card: 'summary_large_image', - images: [`${process.env.NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`], + images: [`${NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`], description: 'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.', }, @@ -59,6 +63,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo + diff --git a/apps/web/src/components/(dashboard)/avatar/avatar-with-recipient.tsx b/apps/web/src/components/(dashboard)/avatar/avatar-with-recipient.tsx index d04b3a998..727627ddd 100644 --- a/apps/web/src/components/(dashboard)/avatar/avatar-with-recipient.tsx +++ b/apps/web/src/components/(dashboard)/avatar/avatar-with-recipient.tsx @@ -2,6 +2,8 @@ import React from 'react'; +import { env } from 'next-runtime-env'; + import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard'; import { getRecipientType } from '@documenso/lib/client-only/recipient-type'; import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter'; @@ -16,6 +18,8 @@ export type AvatarWithRecipientProps = { }; export function AvatarWithRecipient({ recipient }: AvatarWithRecipientProps) { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const [, copy] = useCopyToClipboard(); const { toast } = useToast(); @@ -24,7 +28,7 @@ export function AvatarWithRecipient({ recipient }: AvatarWithRecipientProps) { return; } - void copy(`${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`).then(() => { + void copy(`${NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`).then(() => { toast({ title: 'Copied to clipboard', description: 'The signing link has been copied to your clipboard.', diff --git a/apps/web/src/helpers/get-asset-buffer.ts b/apps/web/src/helpers/get-asset-buffer.ts index 85887071e..12c27d1a5 100644 --- a/apps/web/src/helpers/get-asset-buffer.ts +++ b/apps/web/src/helpers/get-asset-buffer.ts @@ -1,3 +1,5 @@ +import { env } from 'next-runtime-env'; + /** * getAssetBuffer is used to retrieve array buffers for various assets * that are hosted in the `public` folder. @@ -8,7 +10,9 @@ * @param path The path to the asset, relative to the `public` folder. */ export const getAssetBuffer = async (path: string) => { - const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + + const baseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; return fetch(new URL(path, baseUrl)).then(async (res) => res.arrayBuffer()); }; diff --git a/package-lock.json b/package-lock.json index 69825e8d8..d92461436 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "apps/*", "packages/*" ], + "dependencies": { + "next-runtime-env": "^3.2.0" + }, "devDependencies": { "@commitlint/cli": "^17.7.1", "@commitlint/config-conventional": "^17.7.0", @@ -14503,6 +14506,19 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/next-runtime-env": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/next-runtime-env/-/next-runtime-env-3.2.0.tgz", + "integrity": "sha512-rwe3flUgSRm51hzRN4Vt5MMSYMS4aDMEPJa0r+CMONA3UyUZl8Y5O8zjHSIlaNb3yquTCttZ0ahObPyPprBj9g==", + "dependencies": { + "next": "^14", + "react": "^18" + }, + "peerDependencies": { + "next": "^14", + "react": "^18" + } + }, "node_modules/next-themes": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", diff --git a/package.json b/package.json index 30076100f..853de1c6b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,9 @@ "apps/*", "packages/*" ], - "dependencies": {}, + "dependencies": { + "next-runtime-env": "^3.2.0" + }, "overrides": { "next-auth": { "next": "14.0.3" diff --git a/packages/ee/server-only/limits/client.ts b/packages/ee/server-only/limits/client.ts index 7f48e6856..9bb2bd0ac 100644 --- a/packages/ee/server-only/limits/client.ts +++ b/packages/ee/server-only/limits/client.ts @@ -1,7 +1,8 @@ import { APP_BASE_URL } from '@documenso/lib/constants/app'; import { FREE_PLAN_LIMITS } from './constants'; -import { TLimitsResponseSchema, ZLimitsResponseSchema } from './schema'; +import type { TLimitsResponseSchema } from './schema'; +import { ZLimitsResponseSchema } from './schema'; export type GetLimitsOptions = { headers?: Record; @@ -10,7 +11,7 @@ export type GetLimitsOptions = { export const getLimits = async ({ headers }: GetLimitsOptions = {}) => { const requestHeaders = headers ?? {}; - const url = new URL(`${APP_BASE_URL}/api/limits`); + const url = new URL('/api/limits', APP_BASE_URL ?? 'http://localhost:3000'); return fetch(url, { headers: { diff --git a/packages/email/template-components/template-document-self-signed.tsx b/packages/email/template-components/template-document-self-signed.tsx index 90a1d3951..db16fb000 100644 --- a/packages/email/template-components/template-document-self-signed.tsx +++ b/packages/email/template-components/template-document-self-signed.tsx @@ -1,3 +1,5 @@ +import { env } from 'next-runtime-env'; + import { Button, Column, Img, Link, Section, Text } from '../components'; import { TemplateDocumentImage } from './template-document-image'; @@ -10,7 +12,9 @@ export const TemplateDocumentSelfSigned = ({ documentName, assetBaseUrl, }: TemplateDocumentSelfSignedProps) => { - const signUpUrl = `${process.env.NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000'}/signup`; + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + + const signUpUrl = `${NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000'}/signup`; const getAssetUrl = (path: string) => { return new URL(path, assetBaseUrl).toString(); diff --git a/packages/email/template-components/template-reset-password.tsx b/packages/email/template-components/template-reset-password.tsx index 8788d60b8..d05393c83 100644 --- a/packages/email/template-components/template-reset-password.tsx +++ b/packages/email/template-components/template-reset-password.tsx @@ -1,3 +1,5 @@ +import { env } from 'next-runtime-env'; + import { Button, Section, Text } from '../components'; import { TemplateDocumentImage } from './template-document-image'; @@ -8,6 +10,8 @@ export interface TemplateResetPasswordProps { } export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordProps) => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + return ( <> @@ -24,7 +28,7 @@ export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordPro

diff --git a/packages/lib/constants/app.ts b/packages/lib/constants/app.ts index 827fcef0a..cee5a7586 100644 --- a/packages/lib/constants/app.ts +++ b/packages/lib/constants/app.ts @@ -1,8 +1,12 @@ -export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing'; -export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web'; +import { env } from 'next-runtime-env'; + +const NEXT_PUBLIC_PROJECT = process.env.NEXT_PUBLIC_PROJECT; +const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); +const NEXT_PUBLIC_MARKETING_URL = env('NEXT_PUBLIC_MARKETING_URL'); + +export const IS_APP_MARKETING = NEXT_PUBLIC_PROJECT === 'marketing'; +export const IS_APP_WEB = NEXT_PUBLIC_PROJECT === 'web'; export const APP_FOLDER = IS_APP_MARKETING ? 'marketing' : 'web'; -export const APP_BASE_URL = IS_APP_WEB - ? process.env.NEXT_PUBLIC_WEBAPP_URL - : process.env.NEXT_PUBLIC_MARKETING_URL; +export const APP_BASE_URL = IS_APP_WEB ? NEXT_PUBLIC_WEBAPP_URL : NEXT_PUBLIC_MARKETING_URL; diff --git a/packages/lib/constants/feature-flags.ts b/packages/lib/constants/feature-flags.ts index e972b47c2..a0e958e3a 100644 --- a/packages/lib/constants/feature-flags.ts +++ b/packages/lib/constants/feature-flags.ts @@ -1,5 +1,10 @@ +import { env } from 'next-runtime-env'; + import { APP_BASE_URL } from './app'; +const NEXT_PUBLIC_FEATURE_BILLING_ENABLED = env('NEXT_PUBLIC_FEATURE_BILLING_ENABLED'); +const NEXT_PUBLIC_POSTHOG_KEY = env('NEXT_PUBLIC_POSTHOG_KEY'); + /** * The flag name for global session recording feature flag. */ @@ -16,7 +21,7 @@ export const FEATURE_FLAG_POLL_INTERVAL = 30000; * Does not take any person or group properties into account. */ export const LOCAL_FEATURE_FLAGS: Record = { - app_billing: process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true', + app_billing: NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true', marketing_header_single_player_mode: false, } as const; @@ -24,7 +29,7 @@ export const LOCAL_FEATURE_FLAGS: Record = { * Extract the PostHog configuration from the environment. */ export function extractPostHogConfig(): { key: string; host: string } | null { - const postHogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY; + const postHogKey = NEXT_PUBLIC_POSTHOG_KEY; const postHogHost = `${APP_BASE_URL}/ingest`; if (!postHogKey || !postHogHost) { diff --git a/packages/lib/next-auth/auth-options.ts b/packages/lib/next-auth/auth-options.ts index 3b9492807..dc5f1b6f4 100644 --- a/packages/lib/next-auth/auth-options.ts +++ b/packages/lib/next-auth/auth-options.ts @@ -7,6 +7,7 @@ import type { JWT } from 'next-auth/jwt'; import CredentialsProvider from 'next-auth/providers/credentials'; import type { GoogleProfile } from 'next-auth/providers/google'; import GoogleProvider from 'next-auth/providers/google'; +import { env } from 'next-runtime-env'; import { prisma } from '@documenso/prisma'; @@ -15,6 +16,8 @@ import { validateTwoFactorAuthentication } from '../server-only/2fa/validate-2fa import { getUserByEmail } from '../server-only/user/get-user-by-email'; import { ErrorCode } from './error-codes'; +const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP'); + export const NEXT_AUTH_OPTIONS: AuthOptions = { adapter: PrismaAdapter(prisma), secret: process.env.NEXTAUTH_SECRET ?? 'secret', @@ -166,7 +169,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = { async signIn({ user }) { // We do this to stop OAuth providers from creating an account // when signups are disabled - if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === 'true') { + if (NEXT_PUBLIC_DISABLE_SIGNUP === 'true') { const userData = await getUserByEmail({ email: user.email! }); return !!userData; diff --git a/packages/lib/server-only/auth/send-confirmation-email.ts b/packages/lib/server-only/auth/send-confirmation-email.ts index 7defdb1bd..c808e8f3d 100644 --- a/packages/lib/server-only/auth/send-confirmation-email.ts +++ b/packages/lib/server-only/auth/send-confirmation-email.ts @@ -1,5 +1,7 @@ import { createElement } from 'react'; +import { env } from 'next-runtime-env'; + import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { ConfirmEmailTemplate } from '@documenso/email/templates/confirm-email'; @@ -10,6 +12,10 @@ export interface SendConfirmationEmailProps { } export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailProps) => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const NEXT_PRIVATE_SMTP_FROM_NAME = env('NEXT_PRIVATE_SMTP_FROM_NAME'); + const NEXT_PRIVATE_SMTP_FROM_ADDRESS = env('NEXT_PRIVATE_SMTP_FROM_ADDRESS'); + const user = await prisma.user.findFirstOrThrow({ where: { id: userId, @@ -30,10 +36,10 @@ export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailPro throw new Error('Verification token not found for the user'); } - const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const confirmationLink = `${assetBaseUrl}/verify-email/${verificationToken.token}`; - const senderName = process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso'; - const senderAdress = process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com'; + const senderName = NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso'; + const senderAdress = NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com'; const confirmationTemplate = createElement(ConfirmEmailTemplate, { assetBaseUrl, diff --git a/packages/lib/server-only/auth/send-forgot-password.ts b/packages/lib/server-only/auth/send-forgot-password.ts index e62d5e176..125db9338 100644 --- a/packages/lib/server-only/auth/send-forgot-password.ts +++ b/packages/lib/server-only/auth/send-forgot-password.ts @@ -1,5 +1,7 @@ import { createElement } from 'react'; +import { env } from 'next-runtime-env'; + import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password'; @@ -10,6 +12,8 @@ export interface SendForgotPasswordOptions { } export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const user = await prisma.user.findFirstOrThrow({ where: { id: userId, @@ -29,8 +33,8 @@ export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) } const token = user.PasswordResetToken[0].token; - const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; - const resetPasswordLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/reset-password/${token}`; + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const resetPasswordLink = `${NEXT_PUBLIC_WEBAPP_URL}/reset-password/${token}`; const template = createElement(ForgotPasswordTemplate, { assetBaseUrl, diff --git a/packages/lib/server-only/auth/send-reset-password.ts b/packages/lib/server-only/auth/send-reset-password.ts index 9479f1a45..3dcec113c 100644 --- a/packages/lib/server-only/auth/send-reset-password.ts +++ b/packages/lib/server-only/auth/send-reset-password.ts @@ -1,5 +1,7 @@ import { createElement } from 'react'; +import { env } from 'next-runtime-env'; + import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password'; @@ -10,13 +12,15 @@ export interface SendResetPasswordOptions { } export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const user = await prisma.user.findFirstOrThrow({ where: { id: userId, }, }); - const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const template = createElement(ResetPasswordTemplate, { assetBaseUrl, diff --git a/packages/lib/server-only/document/delete-document.ts b/packages/lib/server-only/document/delete-document.ts index 22365a727..58b1f48a9 100644 --- a/packages/lib/server-only/document/delete-document.ts +++ b/packages/lib/server-only/document/delete-document.ts @@ -2,6 +2,8 @@ import { createElement } from 'react'; +import { env } from 'next-runtime-env'; + import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import DocumentCancelTemplate from '@documenso/email/templates/document-cancel'; @@ -16,6 +18,8 @@ export type DeleteDocumentOptions = { status: DocumentStatus; }; +const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + export const deleteDocument = async ({ id, userId, status }: DeleteDocumentOptions) => { // if the document is a draft, hard-delete if (status === DocumentStatus.DRAFT) { @@ -49,7 +53,7 @@ export const deleteDocument = async ({ id, userId, status }: DeleteDocumentOptio if (document.Recipient.length > 0) { await Promise.all( document.Recipient.map(async (recipient) => { - const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const template = createElement(DocumentCancelTemplate, { documentName: document.title, diff --git a/packages/lib/server-only/document/resend-document.tsx b/packages/lib/server-only/document/resend-document.tsx index da4ffcb58..a048cf600 100644 --- a/packages/lib/server-only/document/resend-document.tsx +++ b/packages/lib/server-only/document/resend-document.tsx @@ -1,5 +1,7 @@ import { createElement } from 'react'; +import { env } from 'next-runtime-env'; + import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite'; @@ -14,6 +16,8 @@ export type ResendDocumentOptions = { recipients: number[]; }; +const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + export const resendDocument = async ({ documentId, userId, recipients }: ResendDocumentOptions) => { const user = await prisma.user.findFirstOrThrow({ where: { @@ -67,8 +71,8 @@ export const resendDocument = async ({ documentId, userId, recipients }: ResendD 'document.name': document.title, }; - const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; - const signDocumentLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`; + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`; const template = createElement(DocumentInviteEmailTemplate, { documentName: document.title, diff --git a/packages/lib/server-only/document/send-completed-email.ts b/packages/lib/server-only/document/send-completed-email.ts index 226ff43ec..9b624a23c 100644 --- a/packages/lib/server-only/document/send-completed-email.ts +++ b/packages/lib/server-only/document/send-completed-email.ts @@ -1,5 +1,7 @@ import { createElement } from 'react'; +import { env } from 'next-runtime-env'; + import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed'; @@ -12,6 +14,8 @@ export interface SendDocumentOptions { } export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const document = await prisma.document.findUnique({ where: { id: documentId, @@ -36,12 +40,12 @@ export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => document.Recipient.map(async (recipient) => { const { email, name, token } = recipient; - const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const template = createElement(DocumentCompletedEmailTemplate, { documentName: document.title, assetBaseUrl, - downloadLink: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`, + downloadLink: `${NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`, }); await mailer.sendMail({ diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx index 25dc132ba..f72c79792 100644 --- a/packages/lib/server-only/document/send-document.tsx +++ b/packages/lib/server-only/document/send-document.tsx @@ -1,5 +1,7 @@ import { createElement } from 'react'; +import { env } from 'next-runtime-env'; + import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite'; @@ -14,6 +16,8 @@ export type SendDocumentOptions = { }; export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const user = await prisma.user.findFirstOrThrow({ where: { id: userId, @@ -59,8 +63,8 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) return; } - const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; - const signDocumentLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`; + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`; const template = createElement(DocumentInviteEmailTemplate, { documentName: document.title, diff --git a/packages/lib/server-only/document/send-pending-email.ts b/packages/lib/server-only/document/send-pending-email.ts index 75861be78..abbecd72a 100644 --- a/packages/lib/server-only/document/send-pending-email.ts +++ b/packages/lib/server-only/document/send-pending-email.ts @@ -1,5 +1,7 @@ import { createElement } from 'react'; +import { env } from 'next-runtime-env'; + import { mailer } from '@documenso/email/mailer'; import { render } from '@documenso/email/render'; import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending'; @@ -11,6 +13,8 @@ export interface SendPendingEmailOptions { } export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingEmailOptions) => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const document = await prisma.document.findFirst({ where: { id: documentId, @@ -41,7 +45,7 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE const { email, name } = recipient; - const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; + const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const template = createElement(DocumentPendingEmailTemplate, { documentName: document.title, diff --git a/packages/lib/server-only/feature-flags/all.ts b/packages/lib/server-only/feature-flags/all.ts index 40e759221..fff6cd855 100644 --- a/packages/lib/server-only/feature-flags/all.ts +++ b/packages/lib/server-only/feature-flags/all.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { getToken } from 'next-auth/jwt'; +import { env } from 'next-runtime-env'; import { LOCAL_FEATURE_FLAGS } from '@documenso/lib/constants/feature-flags'; import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client'; @@ -11,6 +12,9 @@ import { extractDistinctUserId, mapJwtToFlagProperties } from './get'; * Get all the evaluated feature flags based on the current user if possible. */ export default async function handlerFeatureFlagAll(req: Request) { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const NEXT_PUBLIC_MARKETING_URL = env('NEXT_PUBLIC_MARKETING_URL'); + const requestHeaders = Object.fromEntries(req.headers.entries()); const nextReq = new NextRequest(req, { @@ -38,11 +42,11 @@ export default async function handlerFeatureFlagAll(req: Request) { const origin = req.headers.get('origin'); if (origin) { - if (origin.startsWith(process.env.NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) { + if (origin.startsWith(NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) { res.headers.set('Access-Control-Allow-Origin', origin); } - if (origin.startsWith(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) { + if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) { res.headers.set('Access-Control-Allow-Origin', origin); } } diff --git a/packages/lib/server-only/feature-flags/get.ts b/packages/lib/server-only/feature-flags/get.ts index 36aafc7b7..6f8a2c011 100644 --- a/packages/lib/server-only/feature-flags/get.ts +++ b/packages/lib/server-only/feature-flags/get.ts @@ -1,7 +1,9 @@ import { NextRequest, NextResponse } from 'next/server'; import { nanoid } from 'nanoid'; -import { JWT, getToken } from 'next-auth/jwt'; +import type { JWT } from 'next-auth/jwt'; +import { getToken } from 'next-auth/jwt'; +import { env } from 'next-runtime-env'; import { LOCAL_FEATURE_FLAGS, extractPostHogConfig } from '@documenso/lib/constants/feature-flags'; import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client'; @@ -13,6 +15,9 @@ import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-po * @returns A Response with the feature flag value. */ export default async function handleFeatureFlagGet(req: Request) { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + const NEXT_PUBLIC_MARKETING_URL = env('NEXT_PUBLIC_MARKETING_URL'); + const { searchParams } = new URL(req.url ?? ''); const flag = searchParams.get('flag'); @@ -57,11 +62,11 @@ export default async function handleFeatureFlagGet(req: Request) { const origin = req.headers.get('Origin'); if (origin) { - if (origin.startsWith(process.env.NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) { + if (origin.startsWith(NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000')) { res.headers.set('Access-Control-Allow-Origin', origin); } - if (origin.startsWith(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) { + if (origin.startsWith(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001')) { res.headers.set('Access-Control-Allow-Origin', origin); } } diff --git a/packages/lib/universal/get-base-url.ts b/packages/lib/universal/get-base-url.ts index 2120c9f54..b7c9d4ea4 100644 --- a/packages/lib/universal/get-base-url.ts +++ b/packages/lib/universal/get-base-url.ts @@ -1,4 +1,8 @@ /* eslint-disable turbo/no-undeclared-env-vars */ +import { env } from 'next-runtime-env'; + +const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + export const getBaseUrl = () => { if (typeof window !== 'undefined') { return ''; @@ -8,8 +12,8 @@ export const getBaseUrl = () => { return `https://${process.env.VERCEL_URL}`; } - if (process.env.NEXT_PUBLIC_WEBAPP_URL) { - return process.env.NEXT_PUBLIC_WEBAPP_URL; + if (NEXT_PUBLIC_WEBAPP_URL) { + return NEXT_PUBLIC_WEBAPP_URL; } return `http://localhost:${process.env.PORT ?? 3000}`; diff --git a/packages/lib/universal/upload/put-file.ts b/packages/lib/universal/upload/put-file.ts index c54ccef77..498bed3bd 100644 --- a/packages/lib/universal/upload/put-file.ts +++ b/packages/lib/universal/upload/put-file.ts @@ -1,4 +1,5 @@ import { base64 } from '@scure/base'; +import { env } from 'next-runtime-env'; import { match } from 'ts-pattern'; import { DocumentDataType } from '@documenso/prisma/client'; @@ -11,8 +12,10 @@ type File = { arrayBuffer: () => Promise; }; +const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT'); + export const putFile = async (file: File) => { - const { type, data } = await match(process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT) + const { type, data } = await match(NEXT_PUBLIC_UPLOAD_TRANSPORT) .with('s3', async () => putFileInS3(file)) .otherwise(async () => putFileInDatabase(file)); diff --git a/packages/lib/universal/upload/server-actions.ts b/packages/lib/universal/upload/server-actions.ts index 69274c30c..61429cd86 100644 --- a/packages/lib/universal/upload/server-actions.ts +++ b/packages/lib/universal/upload/server-actions.ts @@ -11,12 +11,15 @@ import { } from '@aws-sdk/client-s3'; import slugify from '@sindresorhus/slugify'; import { type JWT, getToken } from 'next-auth/jwt'; +import { env } from 'next-runtime-env'; import path from 'node:path'; import { APP_BASE_URL } from '../../constants/app'; import { ONE_HOUR, ONE_SECOND } from '../../constants/time'; import { alphaid } from '../id'; +const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT'); + export const getPresignPostUrl = async (fileName: string, contentType: string) => { const client = getS3Client(); @@ -117,7 +120,7 @@ export const deleteS3File = async (key: string) => { }; const getS3Client = () => { - if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') { + if (NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') { throw new Error('Invalid upload transport'); } diff --git a/packages/trpc/server/auth-router/router.ts b/packages/trpc/server/auth-router/router.ts index 24dd272ee..0debd7f8d 100644 --- a/packages/trpc/server/auth-router/router.ts +++ b/packages/trpc/server/auth-router/router.ts @@ -1,4 +1,5 @@ import { TRPCError } from '@trpc/server'; +import { env } from 'next-runtime-env'; import { ErrorCode } from '@documenso/lib/next-auth/error-codes'; import { compareSync } from '@documenso/lib/server-only/auth/hash'; @@ -8,10 +9,12 @@ import { sendConfirmationToken } from '@documenso/lib/server-only/user/send-conf import { authenticatedProcedure, procedure, router } from '../trpc'; import { ZSignUpMutationSchema, ZVerifyPasswordMutationSchema } from './schema'; +const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP'); + export const authRouter = router({ signup: procedure.input(ZSignUpMutationSchema).mutation(async ({ input }) => { try { - if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === 'true') { + if (NEXT_PUBLIC_DISABLE_SIGNUP === 'true') { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Signups are disabled.', diff --git a/packages/trpc/server/singleplayer-router/router.ts b/packages/trpc/server/singleplayer-router/router.ts index 8e2266fcc..cb92aa7a4 100644 --- a/packages/trpc/server/singleplayer-router/router.ts +++ b/packages/trpc/server/singleplayer-router/router.ts @@ -1,5 +1,6 @@ import { createElement } from 'react'; +import { env } from 'next-runtime-env'; import { PDFDocument } from 'pdf-lib'; import { mailer } from '@documenso/email/mailer'; @@ -24,6 +25,8 @@ import { procedure, router } from '../trpc'; import { mapField } from './helper'; import { ZCreateSinglePlayerDocumentMutationSchema } from './schema'; +const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); + export const singleplayerRouter = router({ createSinglePlayerDocument: procedure .input(ZCreateSinglePlayerDocumentMutationSchema) @@ -148,7 +151,7 @@ export const singleplayerRouter = router({ const template = createElement(DocumentSelfSignedEmailTemplate, { documentName: documentName, - assetBaseUrl: process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000', + assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000', }); const [html, text] = await Promise.all([ diff --git a/packages/ui/components/document/document-share-button.tsx b/packages/ui/components/document/document-share-button.tsx index b366123fb..f304d92c0 100644 --- a/packages/ui/components/document/document-share-button.tsx +++ b/packages/ui/components/document/document-share-button.tsx @@ -4,6 +4,7 @@ import type { HTMLAttributes } from 'react'; import React, { useState } from 'react'; import { Copy, Sparkles } from 'lucide-react'; +import { env } from 'next-runtime-env'; import { FaXTwitter } from 'react-icons/fa6'; import { useCopyShareLink } from '@documenso/lib/client-only/hooks/use-copy-share-link'; @@ -38,6 +39,7 @@ export const DocumentShareButton = ({ className, trigger, }: DocumentShareButtonProps) => { + const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL'); const { toast } = useToast(); const { copyShareLink, createAndCopyShareLink, isCopyingShareLink } = useCopyShareLink({ @@ -68,7 +70,7 @@ export const DocumentShareButton = ({ const onCopyClick = async () => { if (shareLink) { - await copyShareLink(`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}`); + await copyShareLink(`${NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}`); } else { await createAndCopyShareLink({ token, @@ -92,7 +94,7 @@ export const DocumentShareButton = ({ } // Ensuring we've prewarmed the opengraph image for the Twitter - await fetch(`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${slug}/opengraph`, { + await fetch(`${NEXT_PUBLIC_WEBAPP_URL}/share/${slug}/opengraph`, { // We don't care about the response, so we can use no-cors mode: 'no-cors', }); @@ -100,7 +102,7 @@ export const DocumentShareButton = ({ window.open( generateTwitterIntent( `I just ${token ? 'signed' : 'sent'} a document in style with @documenso. Check it out!`, - `${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${slug}`, + `${NEXT_PUBLIC_WEBAPP_URL}/share/${slug}`, ), '_blank', ); @@ -148,7 +150,7 @@ export const DocumentShareButton = ({ 'animate-pulse': !shareLink?.slug, })} > - {process.env.NEXT_PUBLIC_WEBAPP_URL}/share/{shareLink?.slug || '...'} + {NEXT_PUBLIC_WEBAPP_URL}/share/{shareLink?.slug || '...'}
{shareLink?.slug && ( sharing link