diff --git a/apps/remix/app/components/forms/signup.tsx b/apps/remix/app/components/forms/signup.tsx
index ea22b2789..7d086382a 100644
--- a/apps/remix/app/components/forms/signup.tsx
+++ b/apps/remix/app/components/forms/signup.tsx
@@ -1,6 +1,11 @@
import communityCardsImage from '@documenso/assets/images/community-cards.png';
import { authClient } from '@documenso/auth/client';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
+import {
+ NEXT_PUBLIC_IMPRINT_URL,
+ NEXT_PUBLIC_PRIVACY_POLICY_URL,
+ NEXT_PUBLIC_TERMS_OF_SERVICE_URL,
+} from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { ZNameSchema } from '@documenso/lib/types/name';
import { env } from '@documenso/lib/utils/env';
@@ -20,7 +25,8 @@ import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { TurnstileInstance } from '@marsidev/react-turnstile';
import { Turnstile } from '@marsidev/react-turnstile';
-import { useEffect, useRef } from 'react';
+import type { ReactNode } from 'react';
+import { Fragment, useEffect, useRef } from 'react';
import { useForm } from 'react-hook-form';
import { FaIdCardClip } from 'react-icons/fa6';
import { FcGoogle } from 'react-icons/fc';
@@ -102,6 +108,14 @@ export const SignUpForm = ({
const isSubmitting = form.formState.isSubmitting;
+ const legalLinks: { href: string | undefined; label: ReactNode }[] = [
+ { href: NEXT_PUBLIC_TERMS_OF_SERVICE_URL(), label: Terms of Service },
+ { href: NEXT_PUBLIC_PRIVACY_POLICY_URL(), label: Privacy Policy },
+ { href: NEXT_PUBLIC_IMPRINT_URL(), label: Imprint },
+ ];
+
+ const availableLegalLinks = legalLinks.filter((link): link is { href: string; label: ReactNode } => !!link.href);
+
const onFormSubmit = async ({ name, email, password, signature }: TSignUpFormSchema) => {
try {
let token: string | undefined;
@@ -406,27 +420,32 @@ export const SignUpForm = ({
)}
-
-
- By proceeding, you agree to our{' '}
-
- Terms of Service
- {' '}
- and{' '}
-
- Privacy Policy
-
- .
-
-
+ {availableLegalLinks.length > 0 && (
+
+
+ By proceeding, you agree to our{' '}
+ {availableLegalLinks.map((link, index) => (
+
+ {index > 0 && (
+ <>
+ {' '}
+ and{' '}
+ >
+ )}
+
+ {link.label}
+
+
+ ))}
+ .
+
+
+ )}
);
diff --git a/apps/remix/app/components/general/envelope-signing/envelope-signer-header.tsx b/apps/remix/app/components/general/envelope-signing/envelope-signer-header.tsx
index 88ab7f8f4..946eb6ae4 100644
--- a/apps/remix/app/components/general/envelope-signing/envelope-signer-header.tsx
+++ b/apps/remix/app/components/general/envelope-signing/envelope-signer-header.tsx
@@ -17,6 +17,7 @@ import { match } from 'ts-pattern';
import { EnvelopeDownloadDialog } from '~/components/dialogs/envelope-download-dialog';
import { useEmbedSigningContext } from '~/components/embed/embed-signing-context';
import { BrandingLogo } from '~/components/general/branding-logo';
+import { PublicLegalLinks } from '~/components/general/public-legal-links';
import { BrandingLogoIcon } from '../branding-logo-icon';
import { DocumentSigningRejectDialog } from '../document-signing/document-signing-reject-dialog';
@@ -68,8 +69,10 @@ export const EnvelopeSignerHeader = () => {
{/* Right side - Desktop content */}
-
-
+
+
+
+
diff --git a/apps/remix/app/components/general/public-legal-links.tsx b/apps/remix/app/components/general/public-legal-links.tsx
new file mode 100644
index 000000000..d21c81949
--- /dev/null
+++ b/apps/remix/app/components/general/public-legal-links.tsx
@@ -0,0 +1,60 @@
+import {
+ NEXT_PUBLIC_IMPRINT_URL,
+ NEXT_PUBLIC_PRIVACY_POLICY_URL,
+ NEXT_PUBLIC_TERMS_OF_SERVICE_URL,
+} from '@documenso/lib/constants/app';
+import { cn } from '@documenso/ui/lib/utils';
+import { Trans } from '@lingui/react/macro';
+import { Link } from 'react-router';
+
+export type PublicLegalLinksProps = {
+ className?: string;
+};
+
+export const PublicLegalLinks = ({ className }: PublicLegalLinksProps) => {
+ const termsUrl = NEXT_PUBLIC_TERMS_OF_SERVICE_URL();
+ const privacyUrl = NEXT_PUBLIC_PRIVACY_POLICY_URL();
+ const imprintUrl = NEXT_PUBLIC_IMPRINT_URL();
+
+ return (
+
+ {termsUrl && (
+
+ Terms of Service
+
+ )}
+
+ {privacyUrl && (
+
+ Privacy Policy
+
+ )}
+
+ {imprintUrl && (
+
+ Imprint
+
+ )}
+
+ );
+};
diff --git a/apps/remix/app/routes/_recipient+/_layout.tsx b/apps/remix/app/routes/_recipient+/_layout.tsx
index fde4a7998..b074e4462 100644
--- a/apps/remix/app/routes/_recipient+/_layout.tsx
+++ b/apps/remix/app/routes/_recipient+/_layout.tsx
@@ -10,6 +10,8 @@ import { useEffect } from 'react';
import { isRouteErrorResponse, Link, Outlet } from 'react-router';
import { Header as AuthenticatedHeader } from '~/components/general/app-header';
import { GenericErrorLayout } from '~/components/general/generic-error-layout';
+import { PublicLegalLinks } from '~/components/general/public-legal-links';
+
import type { Route } from './+types/_layout';
export function meta() {
@@ -35,16 +37,24 @@ export default function RecipientLayout({ matches }: Route.ComponentProps) {
);
return (
-
- {!hideHeader && sessionData?.user &&
}
+
+
+ {!hideHeader && sessionData?.user &&
}
-
-
-
+
+
+
+
+
+ {!hideHeader && (
+
+ )}
);
}
diff --git a/apps/remix/app/routes/_unauthenticated+/_layout.tsx b/apps/remix/app/routes/_unauthenticated+/_layout.tsx
index 1ad6db22e..dd080fb4b 100644
--- a/apps/remix/app/routes/_unauthenticated+/_layout.tsx
+++ b/apps/remix/app/routes/_unauthenticated+/_layout.tsx
@@ -1,10 +1,12 @@
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
import { Outlet } from 'react-router';
+import { PublicLegalLinks } from '~/components/general/public-legal-links';
+
export default function Layout() {
return (
-
-
+
+
-
+
+
);
}
diff --git a/apps/remix/app/routes/_unauthenticated+/signup.tsx b/apps/remix/app/routes/_unauthenticated+/signup.tsx
index f470004d2..6c5be3889 100644
--- a/apps/remix/app/routes/_unauthenticated+/signup.tsx
+++ b/apps/remix/app/routes/_unauthenticated+/signup.tsx
@@ -54,7 +54,7 @@ export default function SignUp({ loaderData }: Route.ComponentProps) {
return (
{
export const NEXT_PUBLIC_SIGNING_CONTACT_INFO = () =>
env('NEXT_PUBLIC_SIGNING_CONTACT_INFO') ?? NEXT_PUBLIC_WEBAPP_URL();
+export const NEXT_PUBLIC_TERMS_OF_SERVICE_URL = () =>
+ env('NEXT_PUBLIC_TERMS_OF_SERVICE_URL') ?? (IS_DOCUMENSO_CLOUD() ? 'https://documen.so/terms' : undefined);
+
+export const NEXT_PUBLIC_PRIVACY_POLICY_URL = () =>
+ env('NEXT_PUBLIC_PRIVACY_POLICY_URL') ?? (IS_DOCUMENSO_CLOUD() ? 'https://documen.so/privacy' : undefined);
+
+export const NEXT_PUBLIC_IMPRINT_URL = () => env('NEXT_PUBLIC_IMPRINT_URL');
+
export const NEXT_PRIVATE_USE_LEGACY_SIGNING_SUBFILTER = () =>
env('NEXT_PRIVATE_USE_LEGACY_SIGNING_SUBFILTER') === 'true';
diff --git a/packages/tsconfig/process-env.d.ts b/packages/tsconfig/process-env.d.ts
index 31f98cabb..f2a5d9957 100644
--- a/packages/tsconfig/process-env.d.ts
+++ b/packages/tsconfig/process-env.d.ts
@@ -133,6 +133,13 @@ declare namespace NodeJS {
NEXT_PUBLIC_TURNSTILE_SITE_KEY?: string;
NEXT_PRIVATE_TURNSTILE_SECRET_KEY?: string;
+ /**
+ * Legal & Data Protection environment variables
+ */
+ NEXT_PUBLIC_TERMS_OF_SERVICE_URL?: string;
+ NEXT_PUBLIC_PRIVACY_POLICY_URL?: string;
+ NEXT_PUBLIC_IMPRINT_URL?: string;
+
/**
* Google Vertex AI environment variables
*/