feat: migrate nextjs to rr7

This commit is contained in:
David Nguyen
2025-01-02 15:33:37 +11:00
parent 9183f668d3
commit 383b5f78f0
898 changed files with 31175 additions and 24615 deletions

View File

@ -1,4 +1,4 @@
import { type Field, FieldType } from '@documenso/prisma/client';
import { type Field, FieldType } from '@prisma/client';
import { ZFieldMetaSchema } from '../types/field-meta';

View File

@ -1,17 +1,17 @@
import { WEBAPP_BASE_URL } from '../constants/app';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
import { PASSKEY_TIMEOUT } from '../constants/auth';
/**
* Extracts common fields to identify the RP (relying party)
*/
export const getAuthenticatorOptions = () => {
const webAppBaseUrl = new URL(WEBAPP_BASE_URL);
const webAppBaseUrl = new URL(NEXT_PUBLIC_WEBAPP_URL());
const rpId = webAppBaseUrl.hostname;
return {
rpName: 'Documenso',
rpId,
origin: WEBAPP_BASE_URL,
origin: NEXT_PUBLIC_WEBAPP_URL(),
timeout: PASSKEY_TIMEOUT,
};
};

View File

@ -0,0 +1,9 @@
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
export const formatAvatarUrl = (imageId?: string | null) => {
if (!imageId) {
return undefined;
}
return `${NEXT_PUBLIC_WEBAPP_URL()}/api/avatar/${imageId}`;
};

View File

@ -1,5 +1,5 @@
import type { Subscription } from '.prisma/client';
import { SubscriptionStatus } from '.prisma/client';
import type { Subscription } from '@prisma/client';
import { SubscriptionStatus } from '@prisma/client';
/**
* Returns true if there is a subscription that is active and is one of the provided price IDs.

View File

@ -0,0 +1,17 @@
export const appLog = (context: string, ...args: Parameters<typeof console.log>) => {
// if (env('NEXT_DEBUG') === 'true') {
console.log(`[${context}]: ${args[0]}`, ...args.slice(1));
// }
};
export class AppLogger {
public context: string;
constructor(context: string) {
this.context = context;
}
public log(...args: Parameters<typeof console.log>) {
appLog(this.context, ...args);
}
}

View File

@ -1,10 +1,9 @@
import type { I18n } from '@lingui/core';
import { msg } from '@lingui/macro';
import { msg } from '@lingui/core/macro';
import type { DocumentAuditLog, DocumentMeta, Field, Recipient } from '@prisma/client';
import { RecipientRole } from '@prisma/client';
import { match } from 'ts-pattern';
import type { DocumentAuditLog, DocumentMeta, Field, Recipient } from '@documenso/prisma/client';
import { RecipientRole } from '@documenso/prisma/client';
import type {
TDocumentAuditLog,
TDocumentAuditLogDocumentMetaDiffSchema,

View File

@ -1,4 +1,4 @@
import type { Document, Recipient } from '@documenso/prisma/client';
import type { Document, Recipient } from '@prisma/client';
import type {
TDocumentAuthOptions,

View File

@ -1,4 +1,4 @@
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
export const determineDocumentVisibility = (
globalVisibility: DocumentVisibility | null | undefined,

20
packages/lib/utils/env.ts Normal file
View File

@ -0,0 +1,20 @@
/// <reference types="@documenso/tsconfig/process-env.d.ts" />
declare global {
interface Window {
__ENV__?: Record<string, string | undefined>;
}
}
type EnvironmentVariable = keyof NodeJS.ProcessEnv;
export const env = (variable: EnvironmentVariable | (string & object)): string | undefined => {
if (typeof window !== 'undefined' && typeof window.__ENV__ === 'object') {
return window.__ENV__[variable];
}
return process?.env?.[variable];
};
export const createPublicEnv = () =>
Object.fromEntries(Object.entries(process.env).filter(([key]) => key.startsWith('NEXT_PUBLIC_')));

View File

@ -1,4 +1,4 @@
import type { Field } from '@documenso/prisma/client';
import type { Field } from '@prisma/client';
/**
* Sort the fields by the Y position on the document.

View File

@ -1,17 +1,16 @@
import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies';
import type { I18n, MessageDescriptor } from '@lingui/core';
import { i18n } from '@lingui/core';
import { IS_APP_WEB, IS_APP_WEB_I18N_ENABLED } from '../constants/app';
import type { I18nLocaleData, SupportedLanguageCodes } from '../constants/i18n';
import { APP_I18N_OPTIONS } from '../constants/i18n';
import { env } from './env';
export async function dynamicActivate(i18nInstance: I18n, locale: string) {
const extension = process.env.NODE_ENV === 'development' ? 'po' : 'js';
export async function dynamicActivate(locale: string) {
const extension = env('NODE_ENV') === 'development' ? 'po' : 'mjs';
const { messages } = await import(`../translations/${locale}/web.${extension}`);
i18nInstance.loadAndActivate({ locale, messages });
i18n.loadAndActivate({ locale, messages });
}
const parseLanguageFromLocale = (locale: string): SupportedLanguageCodes | null => {
@ -28,25 +27,6 @@ const parseLanguageFromLocale = (locale: string): SupportedLanguageCodes | null
return foundSupportedLanguage;
};
/**
* Extract the language if supported from the cookies header.
*
* Returns `null` if not supported or not found.
*/
export const extractLocaleDataFromCookies = (
cookies: ReadonlyRequestCookies,
): SupportedLanguageCodes | null => {
const preferredLocale = cookies.get('language')?.value || '';
const language = parseLanguageFromLocale(preferredLocale || '');
if (!language) {
return null;
}
return language;
};
/**
* Extracts the language from the `accept-language` header.
*/
@ -65,35 +45,24 @@ export const extractLocaleDataFromHeaders = (
type ExtractLocaleDataOptions = {
headers: Headers;
cookies: ReadonlyRequestCookies;
};
/**
* Extract the supported language from the cookies, then header if not found.
* Extract the supported language from the header.
*
* Will return the default fallback language if not found.
*/
export const extractLocaleData = ({
headers,
cookies,
}: ExtractLocaleDataOptions): I18nLocaleData => {
let lang: SupportedLanguageCodes | null = extractLocaleDataFromCookies(cookies);
export const extractLocaleData = ({ headers }: ExtractLocaleDataOptions): I18nLocaleData => {
const headerLocales = (headers.get('accept-language') ?? '').split(',');
const langHeader = extractLocaleDataFromHeaders(headers);
if (!lang && langHeader?.lang) {
lang = langHeader.lang;
}
// Override web app to be English.
if (!IS_APP_WEB_I18N_ENABLED && IS_APP_WEB) {
lang = 'en';
}
const unknownLanguages = headerLocales
.map((locale) => parseLanguageFromLocale(locale))
.filter((value): value is SupportedLanguageCodes => value !== null);
// Filter out locales that are not valid.
const locales = (langHeader?.locales ?? []).filter((locale) => {
const languages = (unknownLanguages ?? []).filter((language) => {
try {
new Intl.Locale(locale);
new Intl.Locale(language);
return true;
} catch {
return false;
@ -101,8 +70,8 @@ export const extractLocaleData = ({
});
return {
lang: lang || APP_I18N_OPTIONS.sourceLang,
locales,
lang: languages[0] || APP_I18N_OPTIONS.sourceLang,
locales: headerLocales,
};
};

View File

@ -1,7 +1,9 @@
import Honeybadger from '@honeybadger-io/js';
import { env } from './env';
export const buildLogger = () => {
if (process.env.NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY) {
if (env('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY')) {
return new HoneybadgerLogger();
}
@ -45,12 +47,12 @@ class DefaultLogger implements Logger {
class HoneybadgerLogger implements Logger {
constructor() {
if (!process.env.NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY) {
if (!env('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY')) {
throw new Error('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY is not set');
}
Honeybadger.configure({
apiKey: process.env.NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY,
apiKey: env('NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY'),
});
}

View File

@ -1,4 +1,5 @@
import type { User } from '@documenso/prisma/client';
import type { User } from '@prisma/client';
import type { DocumentWithRecipients } from '@documenso/prisma/types/document-with-recipient';
export type MaskRecipientTokensForDocumentOptions<T extends DocumentWithRecipients> = {

View File

@ -19,7 +19,7 @@ type GetRootHrefOptions = {
};
export const getRootHref = (
params: Record<string, string | string[]> | null,
params: Record<string, string | string[] | undefined> | null,
options: GetRootHrefOptions = {},
) => {
if (typeof params?.teamUrl === 'string') {

View File

@ -1,15 +1,15 @@
import { WEBAPP_BASE_URL } from '../constants/app';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
export const formatUserProfilePath = (
profileUrl: string,
options: { excludeBaseUrl?: boolean } = {},
) => {
return `${!options?.excludeBaseUrl ? WEBAPP_BASE_URL : ''}/p/${profileUrl}`;
return `${!options?.excludeBaseUrl ? NEXT_PUBLIC_WEBAPP_URL() : ''}/p/${profileUrl}`;
};
export const formatTeamProfilePath = (
profileUrl: string,
options: { excludeBaseUrl?: boolean } = {},
) => {
return `${!options?.excludeBaseUrl ? WEBAPP_BASE_URL : ''}/p/${profileUrl}`;
return `${!options?.excludeBaseUrl ? NEXT_PUBLIC_WEBAPP_URL() : ''}/p/${profileUrl}`;
};

View File

@ -1,4 +1,4 @@
import type { Recipient } from '@documenso/prisma/client';
import type { Recipient } from '@prisma/client';
export const extractInitials = (text: string) =>
text

View File

@ -1,4 +1,4 @@
import { type Field, type Recipient, RecipientRole, SigningStatus } from '@documenso/prisma/client';
import { type Field, type Recipient, RecipientRole, SigningStatus } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';

View File

@ -3,7 +3,7 @@ import { I18nProvider } from '@lingui/react';
import type { RenderOptions } from '@documenso/email/render';
import { render } from '@documenso/email/render';
import { getI18nInstance } from '../client-only/providers/i18n.server';
import { getI18nInstance } from '../client-only/providers/i18n-server';
import {
APP_I18N_OPTIONS,
type SupportedLanguageCodes,

View File

@ -1,4 +1,4 @@
import type { TeamGlobalSettings } from '@documenso/prisma/client';
import type { TeamGlobalSettings } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';

View File

@ -1,9 +1,9 @@
import { WEBAPP_BASE_URL } from '../constants/app';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
import type { TEAM_MEMBER_ROLE_MAP } from '../constants/teams';
import { TEAM_MEMBER_ROLE_HIERARCHY, TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../constants/teams';
export const formatTeamUrl = (teamUrl: string, baseUrl?: string) => {
const formattedBaseUrl = (baseUrl ?? WEBAPP_BASE_URL).replace(/https?:\/\//, '');
const formattedBaseUrl = (baseUrl ?? NEXT_PUBLIC_WEBAPP_URL()).replace(/https?:\/\//, '');
return `${formattedBaseUrl}/t/${teamUrl}`;
};

View File

@ -1,9 +1,9 @@
import type { Recipient } from '@documenso/prisma/client';
import type { Recipient } from '@prisma/client';
import { WEBAPP_BASE_URL } from '../constants/app';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
export const formatDirectTemplatePath = (token: string) => {
return `${WEBAPP_BASE_URL}/d/${token}`;
return `${NEXT_PUBLIC_WEBAPP_URL()}/d/${token}`;
};
/**