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 } =
trpc.document.setPasswordForDocument.useMutation();
const documentFlow: Record<EditDocumentStep, DocumentFlowStep> = {
const documentFlow: Record<EditDocumentStep, DcumentFlowStep> = {
title: {
title: 'Add Title',
description: 'Add the title to the document.',

View File

@ -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`,
});
};

View File

@ -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`,
});
};

View File

@ -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),

View File

@ -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');
}

View File

@ -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 (
<div>
<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} />
{process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && (
{NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && (
<p className="text-muted-foreground mt-6 text-center text-sm">
Don't have an account?{' '}
<Link href="/signup" className="text-primary duration-200 hover:opacity-70">

View File

@ -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');
}

View File

@ -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
<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="manifest" href="/site.webmanifest" />
<PublicEnvScript />
</head>
<Suspense>

View File

@ -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.',

View File

@ -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());
};

16
package-lock.json generated
View File

@ -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",

View File

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

View File

@ -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<string, string>;
@ -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: {

View File

@ -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();

View File

@ -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 (
<>
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
@ -24,7 +28,7 @@ export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordPro
<Section className="mb-6 mt-8 text-center">
<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"
href={`${process.env.NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000'}/signin`}
href={`${NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000'}/signin`}
>
Sign In
</Button>

View File

@ -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;

View File

@ -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<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,
} as const;
@ -24,7 +29,7 @@ export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
* 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) {

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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({

View File

@ -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,

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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}`;

View File

@ -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<ArrayBuffer>;
};
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));

View File

@ -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');
}

View File

@ -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.',

View File

@ -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([

View File

@ -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 || '...'}
</span>
<div
className={cn(
@ -160,7 +162,7 @@ export const DocumentShareButton = ({
>
{shareLink?.slug && (
<img
src={`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}/opengraph`}
src={`${NEXT_PUBLIC_WEBAPP_URL}/share/${shareLink.slug}/opengraph`}
alt="sharing link"
className="h-full w-full object-cover"
/>