mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 20:42:34 +10:00
Compare commits
1 Commits
v1.5.2-rc.
...
feat/runti
| Author | SHA1 | Date | |
|---|---|---|---|
| 1cd60e1abb |
@ -8,10 +8,13 @@ import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-se
|
|||||||
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
|
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
|
||||||
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
|
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
|
||||||
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
|
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
|
||||||
|
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
|
||||||
|
|
||||||
export const createBillingPortal = async () => {
|
export const createBillingPortal = async () => {
|
||||||
const { user } = await getRequiredServerComponentSession();
|
const { user } = await getRequiredServerComponentSession();
|
||||||
|
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
const existingSubscription = await getSubscriptionByUserId({ userId: user.id });
|
const existingSubscription = await getSubscriptionByUserId({ userId: user.id });
|
||||||
|
|
||||||
let stripeCustomer: Stripe.Customer | null = null;
|
let stripeCustomer: Stripe.Customer | null = null;
|
||||||
@ -43,6 +46,6 @@ export const createBillingPortal = async () => {
|
|||||||
|
|
||||||
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`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-se
|
|||||||
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
|
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
|
||||||
import { Stripe } from '@documenso/lib/server-only/stripe';
|
import { Stripe } from '@documenso/lib/server-only/stripe';
|
||||||
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
|
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
|
||||||
|
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
|
||||||
|
|
||||||
export type CreateCheckoutOptions = {
|
export type CreateCheckoutOptions = {
|
||||||
priceId: string;
|
priceId: string;
|
||||||
@ -18,6 +19,8 @@ export type CreateCheckoutOptions = {
|
|||||||
export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
|
export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
|
||||||
const { user } = await getRequiredServerComponentSession();
|
const { user } = await getRequiredServerComponentSession();
|
||||||
|
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
const existingSubscription = await getSubscriptionByUserId({ userId: user.id });
|
const existingSubscription = await getSubscriptionByUserId({ userId: user.id });
|
||||||
|
|
||||||
let stripeCustomer: Stripe.Customer | null = null;
|
let stripeCustomer: Stripe.Customer | null = null;
|
||||||
@ -32,7 +35,7 @@ export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
|
|||||||
|
|
||||||
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`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +56,6 @@ export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => {
|
|||||||
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`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import { Metadata } from 'next';
|
|||||||
import { headers } from 'next/headers';
|
import { headers } from 'next/headers';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
import { APP_BASE_URL } from '@documenso/lib/constants/app';
|
import { appBaseUrl } from '@documenso/lib/constants/app';
|
||||||
|
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
|
||||||
|
|
||||||
type SharePageProps = {
|
type SharePageProps = {
|
||||||
params: { slug: string };
|
params: { slug: string };
|
||||||
@ -16,12 +17,12 @@ export function generateMetadata({ params: { slug } }: SharePageProps) {
|
|||||||
title: 'Documenso - Join the open source signing revolution',
|
title: 'Documenso - Join the open source signing revolution',
|
||||||
description: 'I just signed with Documenso!',
|
description: 'I just signed with Documenso!',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
images: [`${APP_BASE_URL}/share/${slug}/opengraph`],
|
images: [`${appBaseUrl()}/share/${slug}/opengraph`],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
site: '@documenso',
|
site: '@documenso',
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
images: [`${APP_BASE_URL}/share/${slug}/opengraph`],
|
images: [`${appBaseUrl()}/share/${slug}/opengraph`],
|
||||||
description: 'I just signed with Documenso!',
|
description: 'I just signed with Documenso!',
|
||||||
},
|
},
|
||||||
} satisfies Metadata;
|
} satisfies Metadata;
|
||||||
@ -30,10 +31,12 @@ export function generateMetadata({ params: { slug } }: SharePageProps) {
|
|||||||
export default function SharePage() {
|
export default function SharePage() {
|
||||||
const userAgent = headers().get('User-Agent') ?? '';
|
const userAgent = headers().get('User-Agent') ?? '';
|
||||||
|
|
||||||
|
const { NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
// 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
|
||||||
if (/bot|facebookexternalhit|WhatsApp|google|bing|duckduckbot|MetaInspector/i.test(userAgent)) {
|
if (/bot|facebookexternalhit|WhatsApp|google|bing|duckduckbot|MetaInspector/i.test(userAgent)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001');
|
redirect(NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { FeatureFlagProvider } from '@documenso/lib/client-only/providers/featur
|
|||||||
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';
|
||||||
import { getLocale } from '@documenso/lib/server-only/headers/get-locale';
|
import { getLocale } from '@documenso/lib/server-only/headers/get-locale';
|
||||||
|
import { RuntimeEnvProvider } from '@documenso/lib/universal/runtime-env';
|
||||||
|
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-runtime-env';
|
||||||
import { TrpcProvider } from '@documenso/trpc/react';
|
import { TrpcProvider } from '@documenso/trpc/react';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Toaster } from '@documenso/ui/primitives/toaster';
|
import { Toaster } from '@documenso/ui/primitives/toaster';
|
||||||
@ -17,31 +19,39 @@ import { PostHogPageview } from '~/providers/posthog';
|
|||||||
|
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
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' });
|
||||||
|
|
||||||
export const metadata = {
|
// We do this so NEXT_PUBLIC variables will be evaluated at runtime.
|
||||||
title: 'Documenso - The Open Source DocuSign Alternative',
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
description:
|
export const generateMetadata = async () => {
|
||||||
'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.',
|
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||||
keywords:
|
|
||||||
'Documenso, open source, DocuSign alternative, document signing, open signing infrastructure, open-source community, fast signing, beautiful signing, smart templates',
|
return {
|
||||||
authors: { name: 'Documenso, Inc.' },
|
|
||||||
robots: 'index, follow',
|
|
||||||
openGraph: {
|
|
||||||
title: 'Documenso - The Open Source DocuSign Alternative',
|
title: 'Documenso - The Open Source DocuSign Alternative',
|
||||||
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',
|
keywords:
|
||||||
images: [`${process.env.NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
|
'Documenso, open source, DocuSign alternative, document signing, open signing infrastructure, open-source community, fast signing, beautiful signing, smart templates',
|
||||||
},
|
authors: { name: 'Documenso, Inc.' },
|
||||||
twitter: {
|
robots: 'index, follow',
|
||||||
site: '@documenso',
|
openGraph: {
|
||||||
card: 'summary_large_image',
|
title: 'Documenso - The Open Source DocuSign Alternative',
|
||||||
images: [`${process.env.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.',
|
type: 'website',
|
||||||
},
|
images: [`${NEXT_PUBLIC_WEBAPP_URL}/opengraph-image.jpg`],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
site: '@documenso',
|
||||||
|
card: 'summary_large_image',
|
||||||
|
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.',
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
@ -67,19 +77,21 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<LocaleProvider locale={locale}>
|
<RuntimeEnvProvider>
|
||||||
<FeatureFlagProvider initialFlags={flags}>
|
<LocaleProvider locale={locale}>
|
||||||
<PlausibleProvider>
|
<FeatureFlagProvider initialFlags={flags}>
|
||||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
<PlausibleProvider>
|
||||||
<TooltipProvider>
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
<TrpcProvider>{children}</TrpcProvider>
|
<TooltipProvider>
|
||||||
</TooltipProvider>
|
<TrpcProvider>{children}</TrpcProvider>
|
||||||
</ThemeProvider>
|
</TooltipProvider>
|
||||||
</PlausibleProvider>
|
</ThemeProvider>
|
||||||
|
</PlausibleProvider>
|
||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</FeatureFlagProvider>
|
</FeatureFlagProvider>
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
|
</RuntimeEnvProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { getRuntimeEnv } from '@documenso/lib/universal/runtime-env/get-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 } = getRuntimeEnv();
|
||||||
|
|
||||||
|
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());
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,7 +10,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: {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { getRuntimeEnv } from '../universal/runtime-env/get-runtime-env';
|
||||||
|
|
||||||
export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing';
|
export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing';
|
||||||
export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web';
|
export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web';
|
||||||
|
|
||||||
@ -6,3 +8,21 @@ 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
|
||||||
? process.env.NEXT_PUBLIC_WEBAPP_URL
|
? process.env.NEXT_PUBLIC_WEBAPP_URL
|
||||||
: process.env.NEXT_PUBLIC_MARKETING_URL;
|
: process.env.NEXT_PUBLIC_MARKETING_URL;
|
||||||
|
|
||||||
|
export const appBaseUrl = () => {
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL, NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
|
if (IS_APP_WEB) {
|
||||||
|
return NEXT_PUBLIC_WEBAPP_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_APP_MARKETING) {
|
||||||
|
return NEXT_PUBLIC_MARKETING_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return window.location.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000';
|
||||||
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { APP_BASE_URL } from './app';
|
import { appBaseUrl } from './app';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The flag name for global session recording feature flag.
|
* The flag name for global session recording feature flag.
|
||||||
@ -25,7 +25,7 @@ export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
|
|||||||
*/
|
*/
|
||||||
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 = process.env.NEXT_PUBLIC_POSTHOG_KEY;
|
||||||
const postHogHost = `${APP_BASE_URL}/ingest`;
|
const postHogHost = `${appBaseUrl()}/ingest`;
|
||||||
|
|
||||||
if (!postHogKey || !postHogHost) {
|
if (!postHogKey || !postHogHost) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { APP_BASE_URL } from './app';
|
|
||||||
|
|
||||||
export const DEFAULT_STANDARD_FONT_SIZE = 15;
|
export const DEFAULT_STANDARD_FONT_SIZE = 15;
|
||||||
export const DEFAULT_HANDWRITING_FONT_SIZE = 50;
|
export const DEFAULT_HANDWRITING_FONT_SIZE = 50;
|
||||||
|
|
||||||
export const MIN_STANDARD_FONT_SIZE = 8;
|
export const MIN_STANDARD_FONT_SIZE = 8;
|
||||||
export const MIN_HANDWRITING_FONT_SIZE = 20;
|
export const MIN_HANDWRITING_FONT_SIZE = 20;
|
||||||
|
|
||||||
export const CAVEAT_FONT_PATH = `${APP_BASE_URL}/fonts/caveat.ttf`;
|
export const CAVEAT_FONT_PATH = `/fonts/caveat.ttf`;
|
||||||
|
|||||||
@ -5,11 +5,15 @@ import { render } from '@documenso/email/render';
|
|||||||
import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password';
|
import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||||
|
|
||||||
export interface SendForgotPasswordOptions {
|
export interface SendForgotPasswordOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) => {
|
export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions) => {
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@ -5,18 +5,22 @@ import { render } from '@documenso/email/render';
|
|||||||
import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password';
|
import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||||
|
|
||||||
export interface SendResetPasswordOptions {
|
export interface SendResetPasswordOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => {
|
export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) => {
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { render } from '@documenso/email/render';
|
|||||||
import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed';
|
import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||||
import { getFile } from '../../universal/upload/get-file';
|
import { getFile } from '../../universal/upload/get-file';
|
||||||
|
|
||||||
export interface SendDocumentOptions {
|
export interface SendDocumentOptions {
|
||||||
@ -12,6 +13,8 @@ export interface SendDocumentOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => {
|
export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => {
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
const document = await prisma.document.findUnique({
|
const document = await prisma.document.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
@ -36,12 +39,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({
|
||||||
|
|||||||
@ -8,12 +8,16 @@ import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-em
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||||
|
|
||||||
export type SendDocumentOptions = {
|
export type SendDocumentOptions = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
userId: number;
|
userId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => {
|
export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => {
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@ -5,12 +5,16 @@ import { render } from '@documenso/email/render';
|
|||||||
import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending';
|
import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||||
|
|
||||||
export interface SendPendingEmailOptions {
|
export interface SendPendingEmailOptions {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
recipientId: number;
|
recipientId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingEmailOptions) => {
|
export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingEmailOptions) => {
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@ -5,12 +5,15 @@ import { getToken } from 'next-auth/jwt';
|
|||||||
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';
|
||||||
|
|
||||||
|
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||||
import { extractDistinctUserId, mapJwtToFlagProperties } from './get';
|
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, NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
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 +41,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { JWT, getToken } from 'next-auth/jwt';
|
|||||||
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';
|
||||||
|
|
||||||
|
import { getRuntimeEnv } from '../../universal/runtime-env/get-runtime-env';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate a single feature flag based on the current user if possible.
|
* Evaluate a single feature flag based on the current user if possible.
|
||||||
*
|
*
|
||||||
@ -13,6 +15,8 @@ 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, NEXT_PUBLIC_MARKETING_URL } = getRuntimeEnv();
|
||||||
|
|
||||||
const { searchParams } = new URL(req.url ?? '');
|
const { searchParams } = new URL(req.url ?? '');
|
||||||
const flag = searchParams.get('flag');
|
const flag = searchParams.get('flag');
|
||||||
|
|
||||||
@ -57,11 +61,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,9 +12,11 @@ import { FieldType } from '@documenso/prisma/client';
|
|||||||
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
|
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
|
||||||
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||||
|
|
||||||
|
import { appBaseUrl } from '../../constants/app';
|
||||||
|
|
||||||
export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignature) => {
|
export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignature) => {
|
||||||
// Fetch the font file from the public URL.
|
// Fetch the font file from the public URL.
|
||||||
const fontResponse = await fetch(CAVEAT_FONT_PATH);
|
const fontResponse = await fetch(new URL(CAVEAT_FONT_PATH, appBaseUrl()));
|
||||||
const fontCaveat = await fontResponse.arrayBuffer();
|
const fontCaveat = await fontResponse.arrayBuffer();
|
||||||
|
|
||||||
const isSignatureField = isSignatureFieldType(field.type);
|
const isSignatureField = isSignatureFieldType(field.type);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import fontkit from '@pdf-lib/fontkit';
|
import fontkit from '@pdf-lib/fontkit';
|
||||||
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||||
|
|
||||||
|
import { appBaseUrl } from '../../constants/app';
|
||||||
import { CAVEAT_FONT_PATH } from '../../constants/pdf';
|
import { CAVEAT_FONT_PATH } from '../../constants/pdf';
|
||||||
|
|
||||||
export async function insertTextInPDF(
|
export async function insertTextInPDF(
|
||||||
@ -12,7 +13,7 @@ export async function insertTextInPDF(
|
|||||||
useHandwritingFont = true,
|
useHandwritingFont = true,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// Fetch the font file from the public URL.
|
// Fetch the font file from the public URL.
|
||||||
const fontResponse = await fetch(CAVEAT_FONT_PATH);
|
const fontResponse = await fetch(new URL(CAVEAT_FONT_PATH, appBaseUrl()));
|
||||||
const fontCaveat = await fontResponse.arrayBuffer();
|
const fontCaveat = await fontResponse.arrayBuffer();
|
||||||
|
|
||||||
const pdfDoc = await PDFDocument.load(pdfAsBase64);
|
const pdfDoc = await PDFDocument.load(pdfAsBase64);
|
||||||
|
|||||||
3
packages/lib/types/pick-starts-with.ts
Normal file
3
packages/lib/types/pick-starts-with.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export type PickStartsWith<T extends object, S extends string> = {
|
||||||
|
[K in keyof T as K extends `${S}${string}` ? K : never]: T[K];
|
||||||
|
};
|
||||||
@ -4,7 +4,7 @@ import {
|
|||||||
TFeatureFlagValue,
|
TFeatureFlagValue,
|
||||||
ZFeatureFlagValueSchema,
|
ZFeatureFlagValueSchema,
|
||||||
} from '@documenso/lib/client-only/providers/feature-flag.types';
|
} from '@documenso/lib/client-only/providers/feature-flag.types';
|
||||||
import { APP_BASE_URL } from '@documenso/lib/constants/app';
|
import { appBaseUrl } from '@documenso/lib/constants/app';
|
||||||
import { LOCAL_FEATURE_FLAGS, isFeatureFlagEnabled } from '@documenso/lib/constants/feature-flags';
|
import { LOCAL_FEATURE_FLAGS, isFeatureFlagEnabled } from '@documenso/lib/constants/feature-flags';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,7 +24,7 @@ export const getFlag = async (
|
|||||||
return LOCAL_FEATURE_FLAGS[flag] ?? true;
|
return LOCAL_FEATURE_FLAGS[flag] ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(`${APP_BASE_URL}/api/feature-flag/get`);
|
const url = new URL(`${appBaseUrl()}/api/feature-flag/get`);
|
||||||
url.searchParams.set('flag', flag);
|
url.searchParams.set('flag', flag);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
@ -57,7 +57,7 @@ export const getAllFlags = async (
|
|||||||
return LOCAL_FEATURE_FLAGS;
|
return LOCAL_FEATURE_FLAGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(`${APP_BASE_URL}/api/feature-flag/all`);
|
const url = new URL(`${appBaseUrl()}/api/feature-flag/all`);
|
||||||
|
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -82,7 +82,7 @@ export const getAllAnonymousFlags = async (): Promise<Record<string, TFeatureFla
|
|||||||
return LOCAL_FEATURE_FLAGS;
|
return LOCAL_FEATURE_FLAGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(`${APP_BASE_URL}/api/feature-flag/all`);
|
const url = new URL(`${appBaseUrl()}/api/feature-flag/all`);
|
||||||
|
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
next: {
|
next: {
|
||||||
|
|||||||
26
packages/lib/universal/runtime-env/client.tsx
Normal file
26
packages/lib/universal/runtime-env/client.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
|
import { PublicEnv } from './types';
|
||||||
|
|
||||||
|
export type RuntimeEnvClientProviderProps = {
|
||||||
|
value: PublicEnv;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const RuntimeEnvContext = React.createContext<PublicEnv | null>(null);
|
||||||
|
|
||||||
|
export const useRuntimeEnv = () => {
|
||||||
|
const context = useContext(RuntimeEnvContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useRuntimeEnv must be used within a RuntimeEnvProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RuntimeEnvClientProvider = ({ value, children }: RuntimeEnvClientProviderProps) => {
|
||||||
|
return <RuntimeEnvContext.Provider value={value}>{children}</RuntimeEnvContext.Provider>;
|
||||||
|
};
|
||||||
22
packages/lib/universal/runtime-env/get-runtime-env.ts
Normal file
22
packages/lib/universal/runtime-env/get-runtime-env.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { PublicEnv } from './types';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__unstable_runtimeEnv: PublicEnv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRuntimeEnv = () => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
return Object.entries(process.env)
|
||||||
|
.filter(([key]) => key.startsWith('NEXT_PUBLIC_'))
|
||||||
|
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) as PublicEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined' && window.__unstable_runtimeEnv) {
|
||||||
|
return window.__unstable_runtimeEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('RuntimeEnv is not available');
|
||||||
|
};
|
||||||
1
packages/lib/universal/runtime-env/index.ts
Normal file
1
packages/lib/universal/runtime-env/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { RuntimeEnvProvider, type RuntimeEnvProviderProps } from './server';
|
||||||
29
packages/lib/universal/runtime-env/server.tsx
Normal file
29
packages/lib/universal/runtime-env/server.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { RuntimeEnvClientProvider } from './client';
|
||||||
|
import { PublicEnv } from './types';
|
||||||
|
|
||||||
|
export type RuntimeEnvProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RuntimeEnvProvider = ({ children }: RuntimeEnvProviderProps) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const publicEnv = Object.entries(process.env)
|
||||||
|
.filter(([key]) => key.startsWith('NEXT_PUBLIC_'))
|
||||||
|
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) as PublicEnv;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RuntimeEnvClientProvider value={publicEnv}>
|
||||||
|
{children}
|
||||||
|
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `window.__unstable_runtimeEnv = ${JSON.stringify(publicEnv)}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RuntimeEnvClientProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
3
packages/lib/universal/runtime-env/types.ts
Normal file
3
packages/lib/universal/runtime-env/types.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { PickStartsWith } from '../../types/pick-starts-with';
|
||||||
|
|
||||||
|
export type PublicEnv = PickStartsWith<typeof process.env, 'NEXT_PUBLIC_'>;
|
||||||
@ -4,6 +4,7 @@ import { match } from 'ts-pattern';
|
|||||||
import { DocumentDataType } from '@documenso/prisma/client';
|
import { DocumentDataType } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { createDocumentData } from '../../server-only/document-data/create-document-data';
|
import { createDocumentData } from '../../server-only/document-data/create-document-data';
|
||||||
|
import { getRuntimeEnv } from '../runtime-env/get-runtime-env';
|
||||||
|
|
||||||
type File = {
|
type File = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -12,7 +13,9 @@ type File = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const putFile = async (file: File) => {
|
export const putFile = async (file: File) => {
|
||||||
const { type, data } = await match(process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT)
|
const { NEXT_PUBLIC_UPLOAD_TRANSPORT } = getRuntimeEnv();
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import path from 'node:path';
|
|||||||
import { ONE_HOUR, ONE_SECOND } from '../../constants/time';
|
import { ONE_HOUR, ONE_SECOND } from '../../constants/time';
|
||||||
import { getServerComponentSession } from '../../next-auth/get-server-session';
|
import { getServerComponentSession } from '../../next-auth/get-server-session';
|
||||||
import { alphaid } from '../id';
|
import { alphaid } from '../id';
|
||||||
|
import { getRuntimeEnv } from '../runtime-env/get-runtime-env';
|
||||||
|
|
||||||
export const getPresignPostUrl = async (fileName: string, contentType: string) => {
|
export const getPresignPostUrl = async (fileName: string, contentType: string) => {
|
||||||
const client = getS3Client();
|
const client = getS3Client();
|
||||||
@ -103,7 +104,9 @@ export const deleteS3File = async (key: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getS3Client = () => {
|
const getS3Client = () => {
|
||||||
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
|
const { NEXT_PUBLIC_UPLOAD_TRANSPORT } = getRuntimeEnv();
|
||||||
|
|
||||||
|
if (NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
|
||||||
throw new Error('Invalid upload transport');
|
throw new Error('Invalid upload transport');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
packages/lib/universal/use-base-url.ts
Normal file
20
packages/lib/universal/use-base-url.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useRuntimeEnv } from './runtime-env/client';
|
||||||
|
|
||||||
|
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||||
|
export const useBaseUrl = () => {
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL } = useRuntimeEnv();
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.VERCEL_URL) {
|
||||||
|
return `https://${process.env.VERCEL_URL}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NEXT_PUBLIC_WEBAPP_URL) {
|
||||||
|
return NEXT_PUBLIC_WEBAPP_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||||
|
};
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
TOAST_DOCUMENT_SHARE_SUCCESS,
|
TOAST_DOCUMENT_SHARE_SUCCESS,
|
||||||
} from '@documenso/lib/constants/toast';
|
} from '@documenso/lib/constants/toast';
|
||||||
import { generateTwitterIntent } from '@documenso/lib/universal/generate-twitter-intent';
|
import { generateTwitterIntent } from '@documenso/lib/universal/generate-twitter-intent';
|
||||||
|
import { useRuntimeEnv } from '@documenso/lib/universal/runtime-env/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@ -37,6 +38,7 @@ export const DocumentShareButton = ({
|
|||||||
trigger,
|
trigger,
|
||||||
}: DocumentShareButtonProps) => {
|
}: DocumentShareButtonProps) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { NEXT_PUBLIC_WEBAPP_URL } = useRuntimeEnv();
|
||||||
|
|
||||||
const { copyShareLink, createAndCopyShareLink, isCopyingShareLink } = useCopyShareLink({
|
const { copyShareLink, createAndCopyShareLink, isCopyingShareLink } = useCopyShareLink({
|
||||||
onSuccess: () => toast(TOAST_DOCUMENT_SHARE_SUCCESS),
|
onSuccess: () => toast(TOAST_DOCUMENT_SHARE_SUCCESS),
|
||||||
@ -64,7 +66,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,
|
||||||
@ -88,7 +90,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',
|
||||||
});
|
});
|
||||||
@ -96,7 +98,7 @@ export const DocumentShareButton = ({
|
|||||||
window.open(
|
window.open(
|
||||||
generateTwitterIntent(
|
generateTwitterIntent(
|
||||||
`I just ${token ? 'signed' : 'sent'} a document with @documenso. Check it out!`,
|
`I just ${token ? 'signed' : 'sent'} a document with @documenso. Check it out!`,
|
||||||
`${process.env.NEXT_PUBLIC_WEBAPP_URL}/share/${slug}`,
|
`${NEXT_PUBLIC_WEBAPP_URL}/share/${slug}`,
|
||||||
),
|
),
|
||||||
'_blank',
|
'_blank',
|
||||||
);
|
);
|
||||||
@ -141,7 +143,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('bg-muted/40 mt-4 aspect-video overflow-hidden rounded-lg border', {
|
className={cn('bg-muted/40 mt-4 aspect-video overflow-hidden rounded-lg border', {
|
||||||
@ -150,7 +152,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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user