feat: billing

This commit is contained in:
David Nguyen
2025-05-19 12:38:50 +10:00
parent 7abfc9e271
commit 2805478e0d
221 changed files with 8436 additions and 5847 deletions

View File

@ -9,6 +9,7 @@ import { InfoIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { DOCUMENT_SIGNATURE_TYPES } from '@documenso/lib/constants/document';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
@ -66,7 +67,6 @@ export type AddSettingsFormProps = {
documentFlow: DocumentFlowStep;
recipients: Recipient[];
fields: Field[];
isDocumentEnterprise: boolean;
isDocumentPdfLoaded: boolean;
document: TDocument;
currentTeamMemberRole?: TeamMemberRole;
@ -77,7 +77,6 @@ export const AddSettingsFormPartial = ({
documentFlow,
recipients,
fields,
isDocumentEnterprise,
isDocumentPdfLoaded,
document,
currentTeamMemberRole,
@ -85,6 +84,8 @@ export const AddSettingsFormPartial = ({
}: AddSettingsFormProps) => {
const { t } = useLingui();
const organisation = useCurrentOrganisation();
const { documentAuthOption } = extractDocumentAuthMethods({
documentAuth: document.authOptions,
});
@ -268,7 +269,7 @@ export const AddSettingsFormPartial = ({
/>
)}
{isDocumentEnterprise && (
{organisation.organisationClaim.flags.cfr21 && (
<FormField
control={form.control}
name="globalActionAuth"

View File

@ -14,6 +14,7 @@ import { useFieldArray, useForm } from 'react-hook-form';
import { prop, sortBy } from 'remeda';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
import { nanoid } from '@documenso/lib/universal/id';
@ -50,7 +51,6 @@ export type AddSignersFormProps = {
fields: Field[];
signingOrder?: DocumentSigningOrder | null;
allowDictateNextSigner?: boolean;
isDocumentEnterprise: boolean;
onSubmit: (_data: TAddSignersFormSchema) => void;
isDocumentPdfLoaded: boolean;
};
@ -61,7 +61,6 @@ export const AddSignersFormPartial = ({
fields,
signingOrder,
allowDictateNextSigner,
isDocumentEnterprise,
onSubmit,
isDocumentPdfLoaded,
}: AddSignersFormProps) => {
@ -75,6 +74,8 @@ export const AddSignersFormPartial = ({
const { currentStep, totalSteps, previousStep } = useStep();
const organisation = useCurrentOrganisation();
const defaultRecipients = [
{
formId: initialId,
@ -623,36 +624,37 @@ export const AddSignersFormPartial = ({
)}
/>
{showAdvancedSettings && isDocumentEnterprise && (
<FormField
control={form.control}
name={`signers.${index}.actionAuth`}
render={({ field }) => (
<FormItem
className={cn('col-span-8', {
'mb-6':
form.formState.errors.signers?.[index] &&
!form.formState.errors.signers[index]?.actionAuth,
'col-span-10': isSigningOrderSequential,
})}
>
<FormControl>
<RecipientActionAuthSelect
{...field}
onValueChange={field.onChange}
disabled={
snapshot.isDragging ||
isSubmitting ||
!canRecipientBeModified(signer.nativeId)
}
/>
</FormControl>
{showAdvancedSettings &&
organisation.organisationClaim.flags.cfr21 && (
<FormField
control={form.control}
name={`signers.${index}.actionAuth`}
render={({ field }) => (
<FormItem
className={cn('col-span-8', {
'mb-6':
form.formState.errors.signers?.[index] &&
!form.formState.errors.signers[index]?.actionAuth,
'col-span-10': isSigningOrderSequential,
})}
>
<FormControl>
<RecipientActionAuthSelect
{...field}
onValueChange={field.onChange}
disabled={
snapshot.isDragging ||
isSubmitting ||
!canRecipientBeModified(signer.nativeId)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<FormMessage />
</FormItem>
)}
/>
)}
<div className="col-span-2 flex gap-x-2">
<FormField
@ -750,7 +752,7 @@ export const AddSignersFormPartial = ({
</Button>
</div>
{!alwaysShowAdvancedSettings && isDocumentEnterprise && (
{!alwaysShowAdvancedSettings && organisation.organisationClaim.flags.cfr21 && (
<div className="mt-4 flex flex-row items-center">
<Checkbox
id="showAdvancedRecipientSettings"

View File

@ -0,0 +1,55 @@
import * as React from 'react';
import { cva } from 'class-variance-authority';
import { Loader } from 'lucide-react';
import { cn } from '../lib/utils';
const spinnerVariants = cva('text-muted-foreground animate-spin', {
variants: {
size: {
default: 'h-6 w-6',
sm: 'h-4 w-4',
lg: 'h-8 w-8',
},
},
defaultVariants: {
size: 'default',
},
});
type SpinnerSize = 'default' | 'sm' | 'lg';
export interface SpinnerProps extends Omit<React.ComponentPropsWithoutRef<typeof Loader>, 'size'> {
size?: SpinnerSize;
}
const Spinner = React.forwardRef<SVGSVGElement, SpinnerProps>(
({ className, size = 'default', ...props }, ref) => {
return <Loader ref={ref} className={cn(spinnerVariants({ size }), className)} {...props} />;
},
);
Spinner.displayName = 'Spinner';
export interface SpinnerBoxProps extends React.HTMLAttributes<HTMLDivElement> {
spinnerProps?: SpinnerProps;
}
const SpinnerBox = React.forwardRef<HTMLDivElement, SpinnerBoxProps>(
({ className, spinnerProps, ...props }, ref) => {
return (
<div
ref={ref}
className={cn('flex items-center justify-center rounded-lg', className)}
{...props}
>
<Spinner {...spinnerProps} />
</div>
);
},
);
SpinnerBox.displayName = 'SpinnerBox';
export { Spinner, SpinnerBox, spinnerVariants };

View File

@ -12,6 +12,7 @@ import { motion } from 'framer-motion';
import { GripVerticalIcon, HelpCircle, Link2Icon, Plus, Trash } from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
import { nanoid } from '@documenso/lib/universal/id';
@ -49,14 +50,12 @@ export type AddTemplatePlaceholderRecipientsFormProps = {
signingOrder?: DocumentSigningOrder | null;
allowDictateNextSigner?: boolean;
templateDirectLink?: TemplateDirectLink | null;
isEnterprise: boolean;
onSubmit: (_data: TAddTemplatePlacholderRecipientsFormSchema) => void;
isDocumentPdfLoaded: boolean;
};
export const AddTemplatePlaceholderRecipientsFormPartial = ({
documentFlow,
isEnterprise,
recipients,
templateDirectLink,
fields,
@ -71,6 +70,8 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
const { _ } = useLingui();
const { user } = useSession();
const organisation = useCurrentOrganisation();
const [placeholderRecipientCount, setPlaceholderRecipientCount] = useState(() =>
recipients.length > 1 ? recipients.length + 1 : 2,
);
@ -636,29 +637,30 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
)}
/>
{showAdvancedSettings && isEnterprise && (
<FormField
control={form.control}
name={`signers.${index}.actionAuth`}
render={({ field }) => (
<FormItem
className={cn('col-span-8', {
'col-span-10': isSigningOrderSequential,
})}
>
<FormControl>
<RecipientActionAuthSelect
{...field}
onValueChange={field.onChange}
disabled={isSubmitting}
/>
</FormControl>
{showAdvancedSettings &&
organisation.organisationClaim.flags.cfr21 && (
<FormField
control={form.control}
name={`signers.${index}.actionAuth`}
render={({ field }) => (
<FormItem
className={cn('col-span-8', {
'col-span-10': isSigningOrderSequential,
})}
>
<FormControl>
<RecipientActionAuthSelect
{...field}
onValueChange={field.onChange}
disabled={isSubmitting}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<FormMessage />
</FormItem>
)}
/>
)}
<div className="col-span-2 flex gap-x-2">
<FormField
@ -760,7 +762,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
</Button>
</div>
{!alwaysShowAdvancedSettings && isEnterprise && (
{!alwaysShowAdvancedSettings && organisation.organisationClaim.flags.cfr21 && (
<div className="mt-4 flex flex-row items-center">
<Checkbox
id="showAdvancedRecipientSettings"

View File

@ -9,6 +9,7 @@ import { InfoIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import {
DOCUMENT_DISTRIBUTION_METHODS,
@ -74,7 +75,6 @@ export type AddTemplateSettingsFormProps = {
documentFlow: DocumentFlowStep;
recipients: Recipient[];
fields: Field[];
isEnterprise: boolean;
isDocumentPdfLoaded: boolean;
template: TTemplate;
currentTeamMemberRole?: TeamMemberRole;
@ -85,7 +85,6 @@ export const AddTemplateSettingsFormPartial = ({
documentFlow,
recipients,
fields,
isEnterprise,
isDocumentPdfLoaded,
template,
currentTeamMemberRole,
@ -93,6 +92,8 @@ export const AddTemplateSettingsFormPartial = ({
}: AddTemplateSettingsFormProps) => {
const { t, i18n } = useLingui();
const organisation = useCurrentOrganisation();
const { documentAuthOption } = extractDocumentAuthMethods({
documentAuth: template.authOptions,
});
@ -360,7 +361,7 @@ export const AddTemplateSettingsFormPartial = ({
)}
/>
{isEnterprise && (
{organisation.organisationClaim.flags.cfr21 && (
<FormField
control={form.control}
name="globalActionAuth"