diff --git a/.env.example b/.env.example index 7e2b8cb34..29fffa4e9 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ +# The license key to enable enterprise features for self hosters +NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY= + # [[AUTH]] NEXTAUTH_SECRET="secret" diff --git a/.gitignore b/.gitignore index e85bd9780..961230226 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,7 @@ CLAUDE.md # scripts scripts/output* + +# license +.documenso-license.json +.documenso-license-backup.json diff --git a/apps/documentation/pages/users/licenses/enterprise-edition.mdx b/apps/documentation/pages/users/licenses/enterprise-edition.mdx index b1f6a77b2..76ed9a63b 100644 --- a/apps/documentation/pages/users/licenses/enterprise-edition.mdx +++ b/apps/documentation/pages/users/licenses/enterprise-edition.mdx @@ -7,20 +7,51 @@ import { Callout } from 'nextra/components'; # Enterprise Edition -The Documenso Enterprise Edition is our license for self-hosters that need the full range of support and compliance. Everything in the EE folder and all features listed [here](https://github.com/documenso/documenso/blob/main/packages/ee/FEATURES) can be used after acquiring a paid license. - -## Includes - -- Self-Host Documenso in any context. -- Premium Support via Slack, Discord and Email. -- Flexible Licensing (e.g. MIT) for deeper custom integration (if needed). -- Access to all Enterprise-grade compliance and administration features. - -## Limitations - -The Enterprise Edition currently has no limitations except custom contract terms. - The Enterprise Edition requires a paid subscription. [Contact us for a quote](https://documen.so/enterprise). + +The Documenso Enterprise Edition is our license for self-hosters that need the full range of support and compliance. + +The following features are included in the Enterprise Edition: + +{/* Keep this synced with the packages/ee/FEATURES file */} + +- The Stripe Billing Module +- Organisation Authentication Portal +- Document Action Reauthentication (Passkeys and 2FA) +- 21 CFR +- Email domains +- Embed authoring +- Embed authoring white label + +In addition, you will receive: + +- Premium Support via Slack, Discord and Email. +- Flexible Licensing (e.g. MIT) for deeper custom integration (if needed). +- Access to Enterprise-grade compliance and administration features. +- Permission to self-Host Documenso in any context. + +The Enterprise Edition currently has no limitations except custom contract terms. + +## Getting a License + +To acquire an Enterprise Edition license, please [contact our sales team](https://documen.so/enterprise) for a quote. Our team will work with you to understand your requirements and provide a license that fits your needs. + +## Using Your License + +Once you have acquired an Enterprise Edition license: + +1. Access your license key at [license.documenso.com](https://license.documenso.com) +2. Set the `NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY` environment variable in your Documenso instance with your license key + +```bash +NEXT_PRIVATE_DOCUMENSO_LICENSE_KEY="your-license-key-here" +``` + +3. You can verify your license status in the Admin Panel under the Stats section. + +![Admin License Status](/images/admin-license-status.webp) + +Your license will be verified on startup and periodically to ensure continued access to Enterprise features. diff --git a/apps/documentation/public/images/admin-license-status.webp b/apps/documentation/public/images/admin-license-status.webp new file mode 100644 index 000000000..2dad3b2da Binary files /dev/null and b/apps/documentation/public/images/admin-license-status.webp differ diff --git a/apps/remix/app/components/dialogs/claim-create-dialog.tsx b/apps/remix/app/components/dialogs/claim-create-dialog.tsx index c3564e7e0..589484693 100644 --- a/apps/remix/app/components/dialogs/claim-create-dialog.tsx +++ b/apps/remix/app/components/dialogs/claim-create-dialog.tsx @@ -3,6 +3,7 @@ import { useState } from 'react'; import { Trans, useLingui } from '@lingui/react/macro'; import type { z } from 'zod'; +import type { TLicenseClaim } from '@documenso/lib/types/license'; import { generateDefaultSubscriptionClaim } from '@documenso/lib/utils/organisations-claims'; import { trpc } from '@documenso/trpc/react'; import type { ZCreateSubscriptionClaimRequestSchema } from '@documenso/trpc/server/admin-router/create-subscription-claim.types'; @@ -22,7 +23,11 @@ import { SubscriptionClaimForm } from '../forms/subscription-claim-form'; export type CreateClaimFormValues = z.infer; -export const ClaimCreateDialog = () => { +type ClaimCreateDialogProps = { + licenseFlags?: TLicenseClaim; +}; + +export const ClaimCreateDialog = ({ licenseFlags }: ClaimCreateDialogProps) => { const { t } = useLingui(); const { toast } = useToast(); @@ -67,6 +72,7 @@ export const ClaimCreateDialog = () => { ...generateDefaultSubscriptionClaim(), }} onFormSubmit={createClaim} + licenseFlags={licenseFlags} formSubmitTrigger={ + + + Sync license from server + + + + ); +}; diff --git a/apps/remix/app/components/general/admin-license-status-banner.tsx b/apps/remix/app/components/general/admin-license-status-banner.tsx new file mode 100644 index 000000000..690f0bdfd --- /dev/null +++ b/apps/remix/app/components/general/admin-license-status-banner.tsx @@ -0,0 +1,78 @@ +import { Trans } from '@lingui/react/macro'; +import { AlertTriangleIcon, KeyRoundIcon } from 'lucide-react'; +import { Link } from 'react-router'; +import { match } from 'ts-pattern'; + +import type { TCachedLicense } from '@documenso/lib/types/license'; +import { cn } from '@documenso/ui/lib/utils'; +import { Button } from '@documenso/ui/primitives/button'; + +export type AdminLicenseStatusBannerProps = { + license: TCachedLicense | null; +}; + +export const AdminLicenseStatusBanner = ({ license }: AdminLicenseStatusBannerProps) => { + const licenseStatus = license?.derivedStatus; + + if (!license || licenseStatus === 'ACTIVE') { + return null; + } + + return ( +
+
+
+ + + {match(licenseStatus) + .with('PAST_DUE', () => ( + + License Payment Overdue - Please update your payment to avoid service disruptions. + + )) + .with('EXPIRED', () => ( + + License Expired - Please renew your license to continue using enterprise features. + + )) + .with('UNAUTHORIZED', () => + license ? ( + + Invalid License Type - Your Documenso instance is using features that are not part + of your license. + + ) : ( + + Missing License - Your Documenso instance is using features that require a + license. + + ), + ) + .otherwise(() => null)} +
+ + +
+
+ ); +}; diff --git a/apps/remix/app/components/general/metric-card.tsx b/apps/remix/app/components/general/metric-card.tsx index 67ecf17aa..559b049f9 100644 --- a/apps/remix/app/components/general/metric-card.tsx +++ b/apps/remix/app/components/general/metric-card.tsx @@ -5,15 +5,16 @@ import { cn } from '@documenso/ui/lib/utils'; export type CardMetricProps = { icon?: LucideIcon; title: string; - value: string | number; + value?: string | number; className?: string; + children?: React.ReactNode; }; -export const CardMetric = ({ icon: Icon, title, value, className }: CardMetricProps) => { +export const CardMetric = ({ icon: Icon, title, value, className, children }: CardMetricProps) => { return (
@@ -21,7 +22,7 @@ export const CardMetric = ({ icon: Icon, title, value, className }: CardMetricPr
{Icon && (
- +
)} @@ -30,9 +31,11 @@ export const CardMetric = ({ icon: Icon, title, value, className }: CardMetricPr
-

- {typeof value === 'number' ? value.toLocaleString('en-US') : value} -

+ {children || ( +

+ {typeof value === 'number' ? value.toLocaleString('en-US') : value} +

+ )}
); diff --git a/apps/remix/app/components/tables/admin-claims-table.tsx b/apps/remix/app/components/tables/admin-claims-table.tsx index 425472d0a..cf6d82aac 100644 --- a/apps/remix/app/components/tables/admin-claims-table.tsx +++ b/apps/remix/app/components/tables/admin-claims-table.tsx @@ -6,6 +6,7 @@ import { EditIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react'; import { Link, useSearchParams } from 'react-router'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import type { TLicenseClaim } from '@documenso/lib/types/license'; import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params'; import { SUBSCRIPTION_CLAIM_FEATURE_FLAGS } from '@documenso/lib/types/subscription'; import { trpc } from '@documenso/trpc/react'; @@ -27,7 +28,11 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; import { ClaimDeleteDialog } from '../dialogs/claim-delete-dialog'; import { ClaimUpdateDialog } from '../dialogs/claim-update-dialog'; -export const AdminClaimsTable = () => { +type AdminClaimsTableProps = { + licenseFlags?: TLicenseClaim; +}; + +export const AdminClaimsTable = ({ licenseFlags }: AdminClaimsTableProps) => { const { t } = useLingui(); const { toast } = useToast(); @@ -97,11 +102,11 @@ export const AdminClaimsTable = () => { ); if (flags.length === 0) { - return

{t`None`}

; + return

{t`None`}

; } return ( -