feat: add next-runtime-env

This commit is contained in:
Catalin Pit
2024-01-25 10:48:20 +02:00
parent bc1d5cea0a
commit d451a7acce
34 changed files with 192 additions and 56 deletions

View File

@ -61,7 +61,7 @@ export const EditDocumentForm = ({
const { mutateAsync: setPasswordForDocument } = const { mutateAsync: setPasswordForDocument } =
trpc.document.setPasswordForDocument.useMutation(); trpc.document.setPasswordForDocument.useMutation();
const documentFlow: Record<EditDocumentStep, DocumentFlowStep> = { const documentFlow: Record<EditDocumentStep, DcumentFlowStep> = {
title: { title: {
title: 'Add Title', title: 'Add Title',
description: 'Add the title to the document.', description: 'Add the title to the document.',

View File

@ -1,16 +1,20 @@
'use server'; 'use server';
import { env } from 'next-runtime-env';
import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer'; import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer';
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session'; import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
export const createBillingPortal = async () => { export const createBillingPortal = async () => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const { user } = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
const { stripeCustomer } = await getStripeCustomerByUser(user); const { stripeCustomer } = await getStripeCustomerByUser(user);
return getPortalSession({ return getPortalSession({
customerId: stripeCustomer.id, customerId: stripeCustomer.id,
returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`, returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
}); });
}; };

View File

@ -1,5 +1,7 @@
'use server'; 'use server';
import { env } from 'next-runtime-env';
import { getCheckoutSession } from '@documenso/ee/server-only/stripe/get-checkout-session'; import { getCheckoutSession } from '@documenso/ee/server-only/stripe/get-checkout-session';
import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer'; import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer';
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session'; import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
@ -11,6 +13,8 @@ export type CreateCheckoutOptions = {
}; };
export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => { export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const session = await getRequiredServerComponentSession(); const session = await getRequiredServerComponentSession();
const { user, stripeCustomer } = await getStripeCustomerByUser(session.user); const { user, stripeCustomer } = await getStripeCustomerByUser(session.user);
@ -27,13 +31,13 @@ export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
if (foundSubscription) { if (foundSubscription) {
return getPortalSession({ return getPortalSession({
customerId: stripeCustomer.id, customerId: stripeCustomer.id,
returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`, returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
}); });
} }
return getCheckoutSession({ return getCheckoutSession({
customerId: stripeCustomer.id, customerId: stripeCustomer.id,
priceId, priceId,
returnUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`, returnUrl: `${NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
}); });
}; };

View File

@ -1,6 +1,7 @@
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { env } from 'next-runtime-env';
import { P, match } from 'ts-pattern'; import { P, match } from 'ts-pattern';
import type { ShareHandlerAPIResponse } from '~/pages/api/share'; import type { ShareHandlerAPIResponse } from '~/pages/api/share';
@ -22,6 +23,8 @@ type SharePageOpenGraphImageProps = {
}; };
export async function GET(_request: Request, { params: { slug } }: 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([ const [interSemiBold, interRegular, caveatRegular, shareFrameImage] = await Promise.all([
fetch(new URL('@documenso/assets/fonts/inter-semibold.ttf', import.meta.url)).then( fetch(new URL('@documenso/assets/fonts/inter-semibold.ttf', import.meta.url)).then(
async (res) => res.arrayBuffer(), 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( const recipientOrSender: ShareHandlerAPIResponse = await fetch(
new URL(`/api/share?slug=${slug}`, baseUrl), new URL(`/api/share?slug=${slug}`, baseUrl),

View File

@ -1,7 +1,9 @@
import { Metadata } from 'next'; import type { Metadata } from 'next';
import { headers } from 'next/headers'; import { headers } from 'next/headers';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { env } from 'next-runtime-env';
import { APP_BASE_URL } from '@documenso/lib/constants/app'; import { APP_BASE_URL } from '@documenso/lib/constants/app';
type SharePageProps = { type SharePageProps = {
@ -28,6 +30,8 @@ export function generateMetadata({ params: { slug } }: SharePageProps) {
} }
export default function SharePage() { export default function SharePage() {
const NEXT_PUBLIC_MARKETING_URL = env('NEXT_PUBLIC_MARKETING_URL');
const userAgent = headers().get('User-Agent') ?? ''; const userAgent = headers().get('User-Agent') ?? '';
// https://stackoverflow.com/questions/47026171/how-to-detect-bots-for-open-graph-with-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; return null;
} }
redirect(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001'); redirect(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001');
} }

View File

@ -1,10 +1,14 @@
import Link from 'next/link'; import Link from 'next/link';
import { env } from 'next-runtime-env';
import { IS_GOOGLE_SSO_ENABLED } from '@documenso/lib/constants/auth'; import { IS_GOOGLE_SSO_ENABLED } from '@documenso/lib/constants/auth';
import { SignInForm } from '~/components/forms/signin'; import { SignInForm } from '~/components/forms/signin';
export default function SignInPage() { export default function SignInPage() {
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
return ( return (
<div> <div>
<h1 className="text-4xl font-semibold">Sign in to your account</h1> <h1 className="text-4xl font-semibold">Sign in to your account</h1>
@ -15,7 +19,7 @@ export default function SignInPage() {
<SignInForm className="mt-4" isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED} /> <SignInForm className="mt-4" isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED} />
{process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && ( {NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && (
<p className="text-muted-foreground mt-6 text-center text-sm"> <p className="text-muted-foreground mt-6 text-center text-sm">
Don't have an account?{' '} Don't have an account?{' '}
<Link href="/signup" className="text-primary duration-200 hover:opacity-70"> <Link href="/signup" className="text-primary duration-200 hover:opacity-70">

View File

@ -1,10 +1,14 @@
import Link from 'next/link'; import Link from 'next/link';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { env } from 'next-runtime-env';
import { SignUpForm } from '~/components/forms/signup'; import { SignUpForm } from '~/components/forms/signup';
export default function SignUpPage() { 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'); redirect('/signin');
} }

View File

@ -2,6 +2,8 @@ import { Suspense } from 'react';
import { Caveat, Inter } from 'next/font/google'; 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 { FeatureFlagProvider } from '@documenso/lib/client-only/providers/feature-flag';
import { LocaleProvider } from '@documenso/lib/client-only/providers/locale'; import { LocaleProvider } from '@documenso/lib/client-only/providers/locale';
import { getServerComponentAllFlags } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag'; 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 fontInter = Inter({ subsets: ['latin'], variable: '--font-sans' });
const fontCaveat = Caveat({ subsets: ['latin'], variable: '--font-signature' }); const fontCaveat = Caveat({ subsets: ['latin'], variable: '--font-signature' });
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
export const metadata = { export const metadata = {
title: 'Documenso - The Open Source DocuSign Alternative', title: 'Documenso - The Open Source DocuSign Alternative',
description: description:
@ -32,12 +36,12 @@ export const metadata = {
description: 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.', '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', type: 'website',
images: [`${process.env.NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`], images: [`${NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
}, },
twitter: { twitter: {
site: '@documenso', site: '@documenso',
card: 'summary_large_image', card: 'summary_large_image',
images: [`${process.env.NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`], images: [`${NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
description: 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.', '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
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />
<PublicEnvScript />
</head> </head>
<Suspense> <Suspense>

View File

@ -2,6 +2,8 @@
import React from 'react'; import React from 'react';
import { env } from 'next-runtime-env';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard'; import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
import { getRecipientType } from '@documenso/lib/client-only/recipient-type'; import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter'; import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
@ -16,6 +18,8 @@ export type AvatarWithRecipientProps = {
}; };
export function AvatarWithRecipient({ recipient }: AvatarWithRecipientProps) { export function AvatarWithRecipient({ recipient }: AvatarWithRecipientProps) {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const [, copy] = useCopyToClipboard(); const [, copy] = useCopyToClipboard();
const { toast } = useToast(); const { toast } = useToast();
@ -24,7 +28,7 @@ export function AvatarWithRecipient({ recipient }: AvatarWithRecipientProps) {
return; return;
} }
void copy(`${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`).then(() => { void copy(`${NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`).then(() => {
toast({ toast({
title: 'Copied to clipboard', title: 'Copied to clipboard',
description: 'The signing link has been copied to your clipboard.', description: 'The signing link has been copied to your clipboard.',

View File

@ -1,3 +1,5 @@
import { env } from 'next-runtime-env';
/** /**
* getAssetBuffer is used to retrieve array buffers for various assets * getAssetBuffer is used to retrieve array buffers for various assets
* that are hosted in the `public` folder. * that are hosted in the `public` folder.
@ -8,7 +10,9 @@
* @param path The path to the asset, relative to the `public` folder. * @param path The path to the asset, relative to the `public` folder.
*/ */
export const getAssetBuffer = async (path: string) => { 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()); return fetch(new URL(path, baseUrl)).then(async (res) => res.arrayBuffer());
}; };

16
package-lock.json generated
View File

@ -9,6 +9,9 @@
"apps/*", "apps/*",
"packages/*" "packages/*"
], ],
"dependencies": {
"next-runtime-env": "^3.2.0"
},
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.7.1", "@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "^17.7.0",
@ -14503,6 +14506,19 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" "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": { "node_modules/next-themes": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz",

View File

@ -47,7 +47,9 @@
"apps/*", "apps/*",
"packages/*" "packages/*"
], ],
"dependencies": {}, "dependencies": {
"next-runtime-env": "^3.2.0"
},
"overrides": { "overrides": {
"next-auth": { "next-auth": {
"next": "14.0.3" "next": "14.0.3"

View File

@ -1,7 +1,8 @@
import { APP_BASE_URL } from '@documenso/lib/constants/app'; import { APP_BASE_URL } from '@documenso/lib/constants/app';
import { FREE_PLAN_LIMITS } from './constants'; import { FREE_PLAN_LIMITS } from './constants';
import { TLimitsResponseSchema, ZLimitsResponseSchema } from './schema'; import type { TLimitsResponseSchema } from './schema';
import { ZLimitsResponseSchema } from './schema';
export type GetLimitsOptions = { export type GetLimitsOptions = {
headers?: Record<string, string>; headers?: Record<string, string>;
@ -10,7 +11,7 @@ export type GetLimitsOptions = {
export const getLimits = async ({ headers }: GetLimitsOptions = {}) => { export const getLimits = async ({ headers }: GetLimitsOptions = {}) => {
const requestHeaders = headers ?? {}; 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, { return fetch(url, {
headers: { headers: {

View File

@ -1,3 +1,5 @@
import { env } from 'next-runtime-env';
import { Button, Column, Img, Link, Section, Text } from '../components'; import { Button, Column, Img, Link, Section, Text } from '../components';
import { TemplateDocumentImage } from './template-document-image'; import { TemplateDocumentImage } from './template-document-image';
@ -10,7 +12,9 @@ export const TemplateDocumentSelfSigned = ({
documentName, documentName,
assetBaseUrl, assetBaseUrl,
}: TemplateDocumentSelfSignedProps) => { }: 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) => { const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString(); return new URL(path, assetBaseUrl).toString();

View File

@ -1,3 +1,5 @@
import { env } from 'next-runtime-env';
import { Button, Section, Text } from '../components'; import { Button, Section, Text } from '../components';
import { TemplateDocumentImage } from './template-document-image'; import { TemplateDocumentImage } from './template-document-image';
@ -8,6 +10,8 @@ export interface TemplateResetPasswordProps {
} }
export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordProps) => { export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordProps) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
return ( return (
<> <>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} /> <TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
@ -24,7 +28,7 @@ export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordPro
<Section className="mb-6 mt-8 text-center"> <Section className="mb-6 mt-8 text-center">
<Button <Button
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline" className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
href={`${process.env.NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000'}/signin`} href={`${NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000'}/signin`}
> >
Sign In Sign In
</Button> </Button>

View File

@ -1,8 +1,12 @@
export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing'; import { env } from 'next-runtime-env';
export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web';
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_FOLDER = IS_APP_MARKETING ? 'marketing' : 'web';
export const APP_BASE_URL = IS_APP_WEB export const APP_BASE_URL = IS_APP_WEB ? NEXT_PUBLIC_WEBAPP_URL : NEXT_PUBLIC_MARKETING_URL;
? process.env.NEXT_PUBLIC_WEBAPP_URL
: process.env.NEXT_PUBLIC_MARKETING_URL;

View File

@ -1,5 +1,10 @@
import { env } from 'next-runtime-env';
import { APP_BASE_URL } from './app'; 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. * 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. * Does not take any person or group properties into account.
*/ */
export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = { export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
app_billing: process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true', app_billing: NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true',
marketing_header_single_player_mode: false, marketing_header_single_player_mode: false,
} as const; } as const;
@ -24,7 +29,7 @@ export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
* Extract the PostHog configuration from the environment. * Extract the PostHog configuration from the environment.
*/ */
export function extractPostHogConfig(): { key: string; host: string } | null { 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`; const postHogHost = `${APP_BASE_URL}/ingest`;
if (!postHogKey || !postHogHost) { if (!postHogKey || !postHogHost) {

View File

@ -7,6 +7,7 @@ import type { JWT } from 'next-auth/jwt';
import CredentialsProvider from 'next-auth/providers/credentials'; import CredentialsProvider from 'next-auth/providers/credentials';
import type { GoogleProfile } from 'next-auth/providers/google'; import type { GoogleProfile } from 'next-auth/providers/google';
import GoogleProvider from 'next-auth/providers/google'; import GoogleProvider from 'next-auth/providers/google';
import { env } from 'next-runtime-env';
import { prisma } from '@documenso/prisma'; 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 { getUserByEmail } from '../server-only/user/get-user-by-email';
import { ErrorCode } from './error-codes'; import { ErrorCode } from './error-codes';
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
export const NEXT_AUTH_OPTIONS: AuthOptions = { export const NEXT_AUTH_OPTIONS: AuthOptions = {
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),
secret: process.env.NEXTAUTH_SECRET ?? 'secret', secret: process.env.NEXTAUTH_SECRET ?? 'secret',
@ -166,7 +169,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
async signIn({ user }) { async signIn({ user }) {
// We do this to stop OAuth providers from creating an account // We do this to stop OAuth providers from creating an account
// when signups are disabled // 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! }); const userData = await getUserByEmail({ email: user.email! });
return !!userData; return !!userData;

View File

@ -1,5 +1,7 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render'; import { render } from '@documenso/email/render';
import { ConfirmEmailTemplate } from '@documenso/email/templates/confirm-email'; import { ConfirmEmailTemplate } from '@documenso/email/templates/confirm-email';
@ -10,6 +12,10 @@ export interface SendConfirmationEmailProps {
} }
export const sendConfirmationEmail = async ({ userId }: 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({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
id: userId, id: userId,
@ -30,10 +36,10 @@ export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailPro
throw new Error('Verification token not found for the user'); 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 confirmationLink = `${assetBaseUrl}/verify-email/${verificationToken.token}`;
const senderName = process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso'; const senderName = NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso';
const senderAdress = process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com'; const senderAdress = NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com';
const confirmationTemplate = createElement(ConfirmEmailTemplate, { const confirmationTemplate = createElement(ConfirmEmailTemplate, {
assetBaseUrl, assetBaseUrl,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render'; import { render } from '@documenso/email/render';
import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password'; import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password';
@ -10,6 +12,8 @@ export interface SendForgotPasswordOptions {
} }
export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) => { export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
id: userId, id: userId,
@ -29,8 +33,8 @@ export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions)
} }
const token = user.PasswordResetToken[0].token; const token = user.PasswordResetToken[0].token;
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const resetPasswordLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/reset-password/${token}`; const resetPasswordLink = `${NEXT_PUBLIC_WEBAPP_URL}/reset-password/${token}`;
const template = createElement(ForgotPasswordTemplate, { const template = createElement(ForgotPasswordTemplate, {
assetBaseUrl, assetBaseUrl,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render'; import { render } from '@documenso/email/render';
import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password'; import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password';
@ -10,13 +12,15 @@ export interface SendResetPasswordOptions {
} }
export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => { export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
id: userId, 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, { const template = createElement(ResetPasswordTemplate, {
assetBaseUrl, assetBaseUrl,

View File

@ -2,6 +2,8 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render'; import { render } from '@documenso/email/render';
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel'; import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
@ -16,6 +18,8 @@ export type DeleteDocumentOptions = {
status: DocumentStatus; status: DocumentStatus;
}; };
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
export const deleteDocument = async ({ id, userId, status }: DeleteDocumentOptions) => { export const deleteDocument = async ({ id, userId, status }: DeleteDocumentOptions) => {
// if the document is a draft, hard-delete // if the document is a draft, hard-delete
if (status === DocumentStatus.DRAFT) { if (status === DocumentStatus.DRAFT) {
@ -49,7 +53,7 @@ export const deleteDocument = async ({ id, userId, status }: DeleteDocumentOptio
if (document.Recipient.length > 0) { if (document.Recipient.length > 0) {
await Promise.all( await Promise.all(
document.Recipient.map(async (recipient) => { 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, { const template = createElement(DocumentCancelTemplate, {
documentName: document.title, documentName: document.title,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render'; import { render } from '@documenso/email/render';
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite'; import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
@ -14,6 +16,8 @@ export type ResendDocumentOptions = {
recipients: number[]; recipients: number[];
}; };
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
export const resendDocument = async ({ documentId, userId, recipients }: ResendDocumentOptions) => { export const resendDocument = async ({ documentId, userId, recipients }: ResendDocumentOptions) => {
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
@ -67,8 +71,8 @@ export const resendDocument = async ({ documentId, userId, recipients }: ResendD
'document.name': document.title, 'document.name': document.title,
}; };
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const signDocumentLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`; const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`;
const template = createElement(DocumentInviteEmailTemplate, { const template = createElement(DocumentInviteEmailTemplate, {
documentName: document.title, documentName: document.title,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render'; import { render } from '@documenso/email/render';
import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed'; import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed';
@ -12,6 +14,8 @@ export interface SendDocumentOptions {
} }
export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => { export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const document = await prisma.document.findUnique({ const document = await prisma.document.findUnique({
where: { where: {
id: documentId, id: documentId,
@ -36,12 +40,12 @@ export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) =>
document.Recipient.map(async (recipient) => { document.Recipient.map(async (recipient) => {
const { email, name, token } = 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, { const template = createElement(DocumentCompletedEmailTemplate, {
documentName: document.title, documentName: document.title,
assetBaseUrl, assetBaseUrl,
downloadLink: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`, downloadLink: `${NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`,
}); });
await mailer.sendMail({ await mailer.sendMail({

View File

@ -1,5 +1,7 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render'; import { render } from '@documenso/email/render';
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite'; import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
@ -14,6 +16,8 @@ export type SendDocumentOptions = {
}; };
export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => { export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
id: userId, id: userId,
@ -59,8 +63,8 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
return; return;
} }
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000'; const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
const signDocumentLink = `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`; const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`;
const template = createElement(DocumentInviteEmailTemplate, { const template = createElement(DocumentInviteEmailTemplate, {
documentName: document.title, documentName: document.title,

View File

@ -1,5 +1,7 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render'; import { render } from '@documenso/email/render';
import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending'; import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending';
@ -11,6 +13,8 @@ export interface SendPendingEmailOptions {
} }
export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingEmailOptions) => { export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingEmailOptions) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const document = await prisma.document.findFirst({ const document = await prisma.document.findFirst({
where: { where: {
id: documentId, id: documentId,
@ -41,7 +45,7 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
const { email, name } = recipient; 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, { const template = createElement(DocumentPendingEmailTemplate, {
documentName: document.title, documentName: document.title,

View File

@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt'; import { getToken } from 'next-auth/jwt';
import { env } from 'next-runtime-env';
import { LOCAL_FEATURE_FLAGS } from '@documenso/lib/constants/feature-flags'; import { LOCAL_FEATURE_FLAGS } from '@documenso/lib/constants/feature-flags';
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client'; 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. * Get all the evaluated feature flags based on the current user if possible.
*/ */
export default async function handlerFeatureFlagAll(req: Request) { 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 requestHeaders = Object.fromEntries(req.headers.entries());
const nextReq = new NextRequest(req, { const nextReq = new NextRequest(req, {
@ -38,11 +42,11 @@ export default async function handlerFeatureFlagAll(req: Request) {
const origin = req.headers.get('origin'); const origin = req.headers.get('origin');
if (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); 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); res.headers.set('Access-Control-Allow-Origin', origin);
} }
} }

View File

@ -1,7 +1,9 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { nanoid } from 'nanoid'; 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 { LOCAL_FEATURE_FLAGS, extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client'; 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. * @returns A Response with the feature flag value.
*/ */
export default async function handleFeatureFlagGet(req: Request) { 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 { searchParams } = new URL(req.url ?? '');
const flag = searchParams.get('flag'); const flag = searchParams.get('flag');
@ -57,11 +62,11 @@ export default async function handleFeatureFlagGet(req: Request) {
const origin = req.headers.get('Origin'); const origin = req.headers.get('Origin');
if (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); 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); res.headers.set('Access-Control-Allow-Origin', origin);
} }
} }

View File

@ -1,4 +1,8 @@
/* eslint-disable turbo/no-undeclared-env-vars */ /* 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 = () => { export const getBaseUrl = () => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
return ''; return '';
@ -8,8 +12,8 @@ export const getBaseUrl = () => {
return `https://${process.env.VERCEL_URL}`; return `https://${process.env.VERCEL_URL}`;
} }
if (process.env.NEXT_PUBLIC_WEBAPP_URL) { if (NEXT_PUBLIC_WEBAPP_URL) {
return process.env.NEXT_PUBLIC_WEBAPP_URL; return NEXT_PUBLIC_WEBAPP_URL;
} }
return `http://localhost:${process.env.PORT ?? 3000}`; return `http://localhost:${process.env.PORT ?? 3000}`;

View File

@ -1,4 +1,5 @@
import { base64 } from '@scure/base'; import { base64 } from '@scure/base';
import { env } from 'next-runtime-env';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { DocumentDataType } from '@documenso/prisma/client'; import { DocumentDataType } from '@documenso/prisma/client';
@ -11,8 +12,10 @@ type File = {
arrayBuffer: () => Promise<ArrayBuffer>; arrayBuffer: () => Promise<ArrayBuffer>;
}; };
const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');
export const putFile = async (file: File) => { 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)) .with('s3', async () => putFileInS3(file))
.otherwise(async () => putFileInDatabase(file)); .otherwise(async () => putFileInDatabase(file));

View File

@ -11,12 +11,15 @@ import {
} from '@aws-sdk/client-s3'; } from '@aws-sdk/client-s3';
import slugify from '@sindresorhus/slugify'; import slugify from '@sindresorhus/slugify';
import { type JWT, getToken } from 'next-auth/jwt'; import { type JWT, getToken } from 'next-auth/jwt';
import { env } from 'next-runtime-env';
import path from 'node:path'; import path from 'node:path';
import { APP_BASE_URL } from '../../constants/app'; import { APP_BASE_URL } from '../../constants/app';
import { ONE_HOUR, ONE_SECOND } from '../../constants/time'; import { ONE_HOUR, ONE_SECOND } from '../../constants/time';
import { alphaid } from '../id'; import { alphaid } from '../id';
const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');
export const getPresignPostUrl = async (fileName: string, contentType: string) => { export const getPresignPostUrl = async (fileName: string, contentType: string) => {
const client = getS3Client(); const client = getS3Client();
@ -117,7 +120,7 @@ export const deleteS3File = async (key: string) => {
}; };
const getS3Client = () => { const getS3Client = () => {
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') { if (NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
throw new Error('Invalid upload transport'); throw new Error('Invalid upload transport');
} }

View File

@ -1,4 +1,5 @@
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { env } from 'next-runtime-env';
import { ErrorCode } from '@documenso/lib/next-auth/error-codes'; import { ErrorCode } from '@documenso/lib/next-auth/error-codes';
import { compareSync } from '@documenso/lib/server-only/auth/hash'; 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 { authenticatedProcedure, procedure, router } from '../trpc';
import { ZSignUpMutationSchema, ZVerifyPasswordMutationSchema } from './schema'; import { ZSignUpMutationSchema, ZVerifyPasswordMutationSchema } from './schema';
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
export const authRouter = router({ export const authRouter = router({
signup: procedure.input(ZSignUpMutationSchema).mutation(async ({ input }) => { signup: procedure.input(ZSignUpMutationSchema).mutation(async ({ input }) => {
try { try {
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === 'true') { if (NEXT_PUBLIC_DISABLE_SIGNUP === 'true') {
throw new TRPCError({ throw new TRPCError({
code: 'BAD_REQUEST', code: 'BAD_REQUEST',
message: 'Signups are disabled.', message: 'Signups are disabled.',

View File

@ -1,5 +1,6 @@
import { createElement } from 'react'; import { createElement } from 'react';
import { env } from 'next-runtime-env';
import { PDFDocument } from 'pdf-lib'; import { PDFDocument } from 'pdf-lib';
import { mailer } from '@documenso/email/mailer'; import { mailer } from '@documenso/email/mailer';
@ -24,6 +25,8 @@ import { procedure, router } from '../trpc';
import { mapField } from './helper'; import { mapField } from './helper';
import { ZCreateSinglePlayerDocumentMutationSchema } from './schema'; import { ZCreateSinglePlayerDocumentMutationSchema } from './schema';
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
export const singleplayerRouter = router({ export const singleplayerRouter = router({
createSinglePlayerDocument: procedure createSinglePlayerDocument: procedure
.input(ZCreateSinglePlayerDocumentMutationSchema) .input(ZCreateSinglePlayerDocumentMutationSchema)
@ -148,7 +151,7 @@ export const singleplayerRouter = router({
const template = createElement(DocumentSelfSignedEmailTemplate, { const template = createElement(DocumentSelfSignedEmailTemplate, {
documentName: documentName, 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([ const [html, text] = await Promise.all([

View File

@ -4,6 +4,7 @@ import type { HTMLAttributes } from 'react';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Copy, Sparkles } from 'lucide-react'; import { Copy, Sparkles } from 'lucide-react';
import { env } from 'next-runtime-env';
import { FaXTwitter } from 'react-icons/fa6'; import { FaXTwitter } from 'react-icons/fa6';
import { useCopyShareLink } from '@documenso/lib/client-only/hooks/use-copy-share-link'; import { useCopyShareLink } from '@documenso/lib/client-only/hooks/use-copy-share-link';
@ -38,6 +39,7 @@ export const DocumentShareButton = ({
className, className,
trigger, trigger,
}: DocumentShareButtonProps) => { }: DocumentShareButtonProps) => {
const NEXT_PUBLIC_WEBAPP_URL = env('NEXT_PUBLIC_WEBAPP_URL');
const { toast } = useToast(); const { toast } = useToast();
const { copyShareLink, createAndCopyShareLink, isCopyingShareLink } = useCopyShareLink({ const { copyShareLink, createAndCopyShareLink, isCopyingShareLink } = useCopyShareLink({
@ -68,7 +70,7 @@ export const DocumentShareButton = ({
const onCopyClick = async () => { const onCopyClick = async () => {
if (shareLink) { if (shareLink) {
await copyShareLink(`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}`); await copyShareLink(`${NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}`);
} else { } else {
await createAndCopyShareLink({ await createAndCopyShareLink({
token, token,
@ -92,7 +94,7 @@ export const DocumentShareButton = ({
} }
// Ensuring we've prewarmed the opengraph image for the Twitter // 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 // We don't care about the response, so we can use no-cors
mode: 'no-cors', mode: 'no-cors',
}); });
@ -100,7 +102,7 @@ export const DocumentShareButton = ({
window.open( window.open(
generateTwitterIntent( generateTwitterIntent(
`I just ${token ? 'signed' : 'sent'} a document in style with @documenso. Check it out!`, `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', '_blank',
); );
@ -148,7 +150,7 @@ export const DocumentShareButton = ({
'animate-pulse': !shareLink?.slug, 'animate-pulse': !shareLink?.slug,
})} })}
> >
{process.env.NEXT_PUBLIC_WEBAPP_URL}/share/{shareLink?.slug || '...'} {NEXT_PUBLIC_WEBAPP_URL}/share/{shareLink?.slug || '...'}
</span> </span>
<div <div
className={cn( className={cn(
@ -160,7 +162,7 @@ export const DocumentShareButton = ({
> >
{shareLink?.slug && ( {shareLink?.slug && (
<img <img
src={`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}/opengraph`} src={`${NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}/opengraph`}
alt="sharing link" alt="sharing link"
className="h-full w-full object-cover" className="h-full w-full object-cover"
/> />