chore: merge main, resolve biome formatting conflicts

Merge origin/main into feat/document-file-conversion. Conflicts were
format-only (Tailwind class ordering, single-line vs multi-line) plus
two semantic merges:

- files.helpers.ts: combine main's pending-PDF download path with the
  branch's original-source-file (DOCX/PNG/JPEG) download path
- download-pdf.ts: combine main's versionToFilenameSuffix helper with
  the branch's server-provided Content-Disposition filename support
This commit is contained in:
ephraimduncan
2026-05-12 11:28:47 +00:00
1495 changed files with 22068 additions and 33465 deletions
+4 -5
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { ChevronDown } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -44,15 +43,15 @@ const AccordionContent = React.forwardRef<
<AccordionPrimitive.Content
ref={ref}
className={cn(
'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm transition-all',
'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
className,
)}
{...props}
>
<div className="pb-4 pt-0">{children}</div>
<div className="pt-0 pb-4">{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
+13 -27
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
import * as React from 'react';
import { cn } from '../lib/utils';
import { buttonVariants } from './button';
@@ -11,9 +10,7 @@ const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = ({ children, ...props }: AlertDialogPrimitive.AlertDialogPortalProps) => (
<AlertDialogPrimitive.Portal {...props}>
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
{children}
</div>
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">{children}</div>
</AlertDialogPrimitive.Portal>
);
@@ -25,7 +22,7 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, children: _children, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
'bg-background/80 animate-in fade-in fixed inset-0 z-50 backdrop-blur-sm transition-opacity',
'fade-in fixed inset-0 z-50 animate-in bg-background/80 backdrop-blur-sm transition-opacity',
className,
)}
{...props}
@@ -44,7 +41,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
'bg-background animate-in fade-in-90 slide-in-from-bottom-10 sm:zoom-in-90 sm:slide-in-from-bottom-0 fixed z-50 grid w-full max-w-lg scale-100 gap-4 border p-6 opacity-100 shadow-lg sm:rounded-lg md:w-full',
'fade-in-90 slide-in-from-bottom-10 sm:zoom-in-90 sm:slide-in-from-bottom-0 fixed z-50 grid w-full max-w-lg scale-100 animate-in gap-4 border bg-background p-6 opacity-100 shadow-lg sm:rounded-lg md:w-full',
className,
)}
{...props}
@@ -61,10 +58,7 @@ const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDiv
AlertDialogHeader.displayName = 'AlertDialogHeader';
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
);
AlertDialogFooter.displayName = 'AlertDialogFooter';
@@ -73,11 +67,7 @@ const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold', className)}
{...props}
/>
<AlertDialogPrimitive.Title ref={ref} className={cn('font-semibold text-lg', className)} {...props} />
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
@@ -86,11 +76,7 @@ const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
<AlertDialogPrimitive.Description ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
));
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
@@ -119,12 +105,12 @@ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
};
+10 -22
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -10,14 +9,11 @@ const alertVariants = cva(
{
variants: {
variant: {
default:
'bg-green-50 text-green-700 [&_.alert-title]:text-green-800 [&>svg]:text-green-400',
neutral:
'bg-gray-50 dark:bg-neutral-900/20 text-muted-foreground [&_.alert-title]:text-foreground',
default: 'bg-green-50 text-green-700 [&_.alert-title]:text-green-800 [&>svg]:text-green-400',
neutral: 'bg-gray-50 dark:bg-neutral-900/20 text-muted-foreground [&_.alert-title]:text-foreground',
secondary: 'bg-blue-50 text-blue-700 [&_.alert-title]:text-blue-800 [&>svg]:text-blue-400',
destructive: 'bg-red-50 text-red-700 [&_.alert-title]:text-red-800 [&>svg]:text-red-400',
warning:
'bg-yellow-50 text-yellow-700 [&_.alert-title]:text-yellow-800 [&>svg]:text-yellow-400',
warning: 'bg-yellow-50 text-yellow-700 [&_.alert-title]:text-yellow-800 [&>svg]:text-yellow-400',
},
padding: {
tighter: 'p-2',
@@ -36,31 +32,23 @@ const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, padding, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn('space-y-2', alertVariants({ variant, padding }), className)}
{...props}
/>
<div ref={ref} role="alert" className={cn('space-y-2', alertVariants({ variant, padding }), className)} {...props} />
));
Alert.displayName = 'Alert';
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5 ref={ref} className={cn('alert-title text-base font-medium', className)} {...props} />
<h5 ref={ref} className={cn('alert-title font-medium text-base', className)} {...props} />
),
);
AlertTitle.displayName = 'AlertTitle';
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('text-sm', className)} {...props} />
));
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn('text-sm', className)} {...props} />,
);
AlertDescription.displayName = 'AlertDescription';
export { Alert, AlertTitle, AlertDescription };
export { Alert, AlertDescription, AlertTitle };
+6 -18
View File
@@ -47,12 +47,7 @@ function pxToRem(px: number): number {
return px / getBaseFontSize();
}
export function AutoSizedText({
children,
className,
maxHeight,
useRem = false,
}: AutoSizedTextProps) {
export function AutoSizedText({ children, className, maxHeight, useRem = false }: AutoSizedTextProps) {
const childRef = useRef<HTMLDivElement>(null);
const fontSize = useRef<number>(0);
@@ -68,20 +63,15 @@ export function AutoSizedText({
let newFontSize: number;
const targetHeight =
maxHeight && maxHeight < parentDimensions.height ? maxHeight : parentDimensions.height;
const targetHeight = maxHeight && maxHeight < parentDimensions.height ? maxHeight : parentDimensions.height;
const isElementTooBig =
childDimensions.width > parentDimensions.width || childDimensions.height > targetHeight;
const isElementTooBig = childDimensions.width > parentDimensions.width || childDimensions.height > targetHeight;
if (isElementTooBig) {
// Scale down if element is bigger than target
newFontSize = (fontSizeLowerBound.current + fontSize.current) / 2;
fontSizeUpperBound.current = fontSize.current;
} else if (
childDimensions.width < parentDimensions.width ||
childDimensions.height < parentDimensions.height
) {
} else if (childDimensions.width < parentDimensions.width || childDimensions.height < parentDimensions.height) {
// Scale up if element is smaller than target
newFontSize = (fontSizeUpperBound.current + fontSize.current) / 2;
fontSizeLowerBound.current = fontSize.current;
@@ -120,16 +110,14 @@ export function AutoSizedText({
while (iterationCount <= ITERATION_LIMIT) {
const childDimensions = getElementDimensions(childElement);
const targetHeight =
maxHeight && maxHeight < parentDimensions.height ? maxHeight : parentDimensions.height;
const targetHeight = maxHeight && maxHeight < parentDimensions.height ? maxHeight : parentDimensions.height;
const widthDifference = parentDimensions.width - childDimensions.width;
const heightDifference = targetHeight - childDimensions.height;
const childFitsIntoParent = heightDifference >= 0 && widthDifference >= 0;
const isWithinTolerance =
Math.abs(widthDifference) <= MAXIMUM_DIFFERENCE ||
Math.abs(heightDifference) <= MAXIMUM_DIFFERENCE;
Math.abs(widthDifference) <= MAXIMUM_DIFFERENCE || Math.abs(heightDifference) <= MAXIMUM_DIFFERENCE;
if (childFitsIntoParent && isWithinTolerance) {
break;
+9 -21
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -21,11 +20,7 @@ const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
<AvatarPrimitive.Image ref={ref} className={cn('aspect-square h-full w-full', className)} {...props} />
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
@@ -36,10 +31,7 @@ const AvatarFallback = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'bg-muted flex h-full w-full items-center justify-center rounded-full',
className,
)}
className={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)}
{...props}
/>
));
@@ -69,22 +61,18 @@ const AvatarWithText = ({
textSectionClassName,
}: AvatarWithTextProps) => (
<div className={cn('flex w-full max-w-xs items-center gap-2', className)}>
<Avatar
className={cn('dark:border-border h-10 w-10 border-2 border-solid border-white', avatarClass)}
>
<Avatar className={cn('h-10 w-10 border-2 border-white border-solid dark:border-border', avatarClass)}>
{avatarSrc && <AvatarImage src={avatarSrc} />}
<AvatarFallback className="text-xs text-gray-400">{avatarFallback}</AvatarFallback>
<AvatarFallback className="text-gray-400 text-xs">{avatarFallback}</AvatarFallback>
</Avatar>
<div
className={cn('flex flex-col truncate text-left text-sm font-normal', textSectionClassName)}
>
<span className="text-foreground truncate">{primaryText}</span>
<span className="text-muted-foreground truncate text-xs">{secondaryText}</span>
<div className={cn('flex flex-col truncate text-left font-normal text-sm', textSectionClassName)}>
<span className="truncate text-foreground">{primaryText}</span>
<span className="truncate text-muted-foreground text-xs">{secondaryText}</span>
</div>
{rightSideComponent}
</div>
);
export { Avatar, AvatarImage, AvatarFallback, AvatarWithText };
export { Avatar, AvatarFallback, AvatarImage, AvatarWithText };
+25 -35
View File
@@ -1,49 +1,39 @@
import * as React from 'react';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import type * as React from 'react';
import { cn } from '../lib/utils';
const badgeVariants = cva(
'inline-flex items-center rounded-md text-xs font-medium ring-1 ring-inset w-fit',
{
variants: {
variant: {
neutral:
'bg-gray-50 text-gray-600 ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20',
destructive:
'bg-red-50 text-red-700 ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20',
warning:
'bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20',
default:
'bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20',
secondary:
'bg-blue-50 text-blue-700 ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30',
orange:
'bg-orange-50 text-orange-700 ring-orange-700/10 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/30',
},
size: {
small: 'px-1.5 py-0.5 text-xs',
default: 'px-2 py-1.5 text-xs',
large: 'px-3 py-2 text-sm',
},
const badgeVariants = cva('inline-flex items-center rounded-md text-xs font-medium ring-1 ring-inset w-fit', {
variants: {
variant: {
neutral: 'bg-gray-50 text-gray-600 ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20',
destructive: 'bg-red-50 text-red-700 ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20',
warning:
'bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20',
default:
'bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20',
secondary:
'bg-blue-50 text-blue-700 ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30',
orange:
'bg-orange-50 text-orange-700 ring-orange-700/10 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/30',
},
defaultVariants: {
variant: 'default',
size: 'default',
size: {
small: 'px-1.5 py-0.5 text-xs',
default: 'px-2 py-1.5 text-xs',
large: 'px-3 py-2 text-sm',
},
},
);
defaultVariants: {
variant: 'default',
size: 'default',
},
});
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, size, ...props }: BadgeProps) {
return (
<div role="status" className={cn(badgeVariants({ variant, size }), className)} {...props} />
);
return <div role="status" className={cn(badgeVariants({ variant, size }), className)} {...props} />;
}
export { Badge, badgeVariants };
+3 -11
View File
@@ -1,9 +1,8 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import { Loader } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -61,21 +60,14 @@ export interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, loading, ...props }, ref) => {
if (asChild) {
return (
<Slot className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
);
return <Slot className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
}
const isLoading = loading === true;
const isDisabled = props.disabled || isLoading;
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
disabled={isDisabled}
>
<button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} disabled={isDisabled}>
{isLoading && <Loader className={cn('mr-2 animate-spin', loaderVariants({ size }))} />}
{props.children}
</button>
+2 -6
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import type * as React from 'react';
import { DayPicker } from 'react-day-picker';
import { cn } from '../lib/utils';
@@ -30,10 +29,7 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C
head_cell: 'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: 'text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal aria-selected:opacity-100',
),
day: cn(buttonVariants({ variant: 'ghost' }), 'h-9 w-9 p-0 font-normal aria-selected:opacity-100'),
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
+11 -22
View File
@@ -16,10 +16,7 @@ export type CardProps = React.HTMLAttributes<HTMLDivElement> & {
};
const Card = React.forwardRef<HTMLDivElement, CardProps>(
(
{ className, children, gradient = false, degrees = 120, backdropBlur = true, ...props },
ref,
) => {
({ className, children, gradient = false, degrees = 120, backdropBlur = true, ...props }, ref) => {
return (
<div
ref={ref}
@@ -30,15 +27,14 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>(
} as React.CSSProperties
}
className={cn(
'bg-background text-foreground group relative rounded-lg border-2',
'group relative rounded-lg border-2 bg-background text-foreground',
{
'backdrop-blur-[2px]': backdropBlur,
'gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/50%)_5%,theme(colors.border/80%)_30%)]':
gradient,
'dark:gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/70%)_5%,theme(colors.border/80%)_30%)]':
gradient,
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_var(colors.primary.DEFAULT/70%)]':
true,
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_var(colors.primary.DEFAULT/70%)]': true,
'dark:shadow-[0]': true,
},
className,
@@ -63,29 +59,22 @@ CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
{...props}
/>
<h3 ref={ref} className={cn('font-semibold text-lg leading-none tracking-tight', className)} {...props} />
),
);
CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
));
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
),
);
CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
),
({ className, ...props }, ref) => <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />,
);
CardContent.displayName = 'CardContent';
@@ -98,4 +87,4 @@ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDiv
CardFooter.displayName = 'CardFooter';
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
+3 -4
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -14,13 +13,13 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'border-input bg-background ring-offset-background focus-visible:ring-ring data-[state=checked]:border-primary data-[state=checked]:bg-primary peer h-4 w-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'peer h-4 w-4 shrink-0 rounded-sm border border-input bg-background ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn('text-primary-foreground flex items-center justify-center', checkClassName)}
className={cn('flex items-center justify-center text-primary-foreground', checkClassName)}
>
<Check className="h-3 w-3 stroke-[3px]" />
</CheckboxPrimitive.Indicator>
+1 -1
View File
@@ -6,4 +6,4 @@ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
export { Collapsible, CollapsibleContent, CollapsibleTrigger };
+14 -7
View File
@@ -1,7 +1,7 @@
import type { HTMLAttributes } from 'react';
import React, { useState } from 'react';
import { useEffect, useState } from 'react';
import { HexColorInput, HexColorPicker } from 'react-colorful';
import { HexColorInput, HexColorPicker, setNonce } from 'react-colorful';
import { cn } from '../lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from './popover';
@@ -11,6 +11,7 @@ export type ColorPickerProps = {
value: string;
defaultValue?: string;
onChange: (color: string) => void;
nonce?: string;
} & HTMLAttributes<HTMLDivElement>;
export const ColorPicker = ({
@@ -19,6 +20,7 @@ export const ColorPicker = ({
value,
defaultValue = '#000000',
onChange,
nonce,
...props
}: ColorPickerProps) => {
const [color, setColor] = useState(value || defaultValue);
@@ -39,13 +41,19 @@ export const ColorPicker = ({
onChange(inputColor);
};
useEffect(() => {
if (nonce) {
setNonce(nonce);
}
}, [nonce]);
return (
<Popover>
<PopoverTrigger>
<button
type="button"
disabled={disabled}
className="bg-background h-12 w-12 rounded-md border p-1 disabled:pointer-events-none disabled:opacity-50"
className="h-12 w-12 rounded-md border bg-background p-1 disabled:pointer-events-none disabled:opacity-50"
>
<div className="h-full w-full rounded-sm" style={{ backgroundColor: color }} />
</button>
@@ -53,13 +61,11 @@ export const ColorPicker = ({
<PopoverContent className="w-auto">
<HexColorPicker
className={cn(
className,
'w-full aria-disabled:pointer-events-none aria-disabled:opacity-50',
)}
className={cn(className, 'w-full aria-disabled:pointer-events-none aria-disabled:opacity-50')}
color={color}
onChange={onColorChange}
aria-disabled={disabled}
nonce={nonce}
{...props}
/>
@@ -75,6 +81,7 @@ export const ColorPicker = ({
}
}}
disabled={disabled}
nonce={nonce}
/>
</PopoverContent>
</Popover>
+2 -5
View File
@@ -1,9 +1,8 @@
import * as React from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Check, ChevronDown } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
import { Button } from './button';
@@ -69,9 +68,7 @@ const Combobox = ({
<CommandGroup className="max-h-[250px] overflow-y-auto">
{options.map((option, index) => (
<CommandItem key={index} onSelect={() => onOptionSelected(option)}>
<Check
className={cn('mr-2 h-4 w-4', option === value ? 'opacity-100' : 'opacity-0')}
/>
<Check className={cn('mr-2 h-4 w-4', option === value ? 'opacity-100' : 'opacity-0')} />
{option}
</CommandItem>
+16 -31
View File
@@ -1,8 +1,7 @@
import * as React from 'react';
import type { DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from 'cmdk';
import { Search } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
import { Dialog, DialogContent } from './dialog';
@@ -14,7 +13,7 @@ const Command = React.forwardRef<
<CommandPrimitive
ref={ref}
className={cn(
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
className,
)}
{...props}
@@ -39,16 +38,13 @@ const CommandDialog = ({
return (
<Dialog {...props}>
<DialogContent
className={cn(
'w-11/12 items-center overflow-hidden rounded-lg p-0 shadow-2xl lg:mt-0',
dialogContentClassName,
)}
className={cn('w-11/12 items-center overflow-hidden rounded-lg p-0 shadow-2xl lg:mt-0', dialogContentClassName)}
position={position}
overlayClassName="bg-background/60"
>
<Command
{...commandProps}
className="[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-0 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-4"
className="[&_[cmdk-group-heading]]:px-0 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-4"
>
{children}
</Command>
@@ -66,7 +62,7 @@ const CommandInput = React.forwardRef<
<CommandPrimitive.Input
ref={ref}
className={cn(
'placeholder:text-foreground-muted flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50',
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-foreground-muted disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
@@ -84,10 +80,10 @@ const CommandTextInput = React.forwardRef<
<CommandPrimitive.Input
ref={ref}
className={cn(
'bg-background border-input ring-offset-background placeholder:text-muted-foreground/40 focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
{
'ring-2 !ring-red-500 transition-all': props['aria-invalid'],
'!ring-red-500 ring-2 transition-all': props['aria-invalid'],
},
)}
{...props}
@@ -113,9 +109,7 @@ CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
));
>((props, ref) => <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />);
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
@@ -126,7 +120,7 @@ const CommandGroup = React.forwardRef<
<CommandPrimitive.Group
ref={ref}
className={cn(
'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden border-b p-1 last:border-0 [&_[cmdk-group-heading]]:mt-2 [&_[cmdk-group-heading]]:px-0 [&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-normal [&_[cmdk-group-heading]]:opacity-50',
'overflow-hidden border-b p-1 text-foreground last:border-0 [&_[cmdk-group-heading]]:mt-2 [&_[cmdk-group-heading]]:px-0 [&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:font-normal [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:opacity-50',
className,
)}
{...props}
@@ -139,11 +133,7 @@ const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn('bg-border -mx-1 h-px', className)}
{...props}
/>
<CommandPrimitive.Separator ref={ref} className={cn('-mx-1 h-px bg-border', className)} {...props} />
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
@@ -155,7 +145,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item
ref={ref}
className={cn(
'hover:bg-accent hover:text-accent-foreground aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -165,12 +155,7 @@ const CommandItem = React.forwardRef<
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
return <span className={cn('ml-auto text-muted-foreground text-xs tracking-widest', className)} {...props} />;
};
CommandShortcut.displayName = 'CommandShortcut';
@@ -178,12 +163,12 @@ CommandShortcut.displayName = 'CommandShortcut';
export {
Command,
CommandDialog,
CommandInput,
CommandTextInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandShortcut,
CommandList,
CommandSeparator,
CommandShortcut,
CommandTextInput,
};
+17 -27
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -26,7 +25,7 @@ const ContextMenuSubTrigger = React.forwardRef<
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
className,
)}
@@ -46,7 +45,7 @@ const ContextMenuSubContent = React.forwardRef<
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
'bg-popover text-popover-foreground animate-in slide-in-from-left-1 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'slide-in-from-left-1 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -63,7 +62,7 @@ const ContextMenuContent = React.forwardRef<
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
'bg-popover text-popover-foreground animate-in fade-in-80 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'fade-in-80 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -82,7 +81,7 @@ const ContextMenuItem = React.forwardRef<
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
@@ -99,7 +98,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
@@ -123,7 +122,7 @@ const ContextMenuRadioItem = React.forwardRef<
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -147,7 +146,7 @@ const ContextMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn('text-foreground px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
className={cn('px-2 py-1.5 font-semibold text-foreground text-sm', inset && 'pl-8', className)}
{...props}
/>
));
@@ -158,40 +157,31 @@ const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
<ContextMenuPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-border', className)} {...props} />
));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
return <span className={cn('ml-auto text-muted-foreground text-xs tracking-widest', className)} {...props} />;
};
ContextMenuShortcut.displayName = 'ContextMenuShortcut';
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuPortal,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
ContextMenuTrigger,
};
@@ -23,22 +23,14 @@ export function DataTablePagination<TData>({
}: DataTablePaginationProps<TData>) {
return (
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-4 px-2">
<div className="flex-1 text-sm text-muted-foreground">
<div className="flex-1 text-muted-foreground text-sm">
{match(additionalInformation)
.with('SelectedCount', () => (
<span>
<Plural
value={table.getFilteredRowModel().rows.length}
one={
<Trans>
{table.getFilteredSelectedRowModel().rows.length} of # row selected.
</Trans>
}
other={
<Trans>
{table.getFilteredSelectedRowModel().rows.length} of # rows selected.
</Trans>
}
one={<Trans>{table.getFilteredSelectedRowModel().rows.length} of # row selected.</Trans>}
other={<Trans>{table.getFilteredSelectedRowModel().rows.length} of # rows selected.</Trans>}
/>
</span>
))
@@ -47,11 +39,7 @@ export function DataTablePagination<TData>({
return (
<span data-testid="data-table-count">
<Plural
value={visibleRows}
one={`Showing # result.`}
other={`Showing # results.`}
/>
<Plural value={visibleRows} one={`Showing # result.`} other={`Showing # results.`} />
</span>
);
})
@@ -60,7 +48,7 @@ export function DataTablePagination<TData>({
</div>
<div className="flex items-center gap-x-2">
<p className="whitespace-nowrap text-sm font-medium">
<p className="whitespace-nowrap font-medium text-sm">
<Trans>Rows per page</Trans>
</p>
<Select
@@ -82,7 +70,7 @@ export function DataTablePagination<TData>({
</Select>
</div>
<div className="flex flex-wrap items-center gap-x-6 gap-y-4 lg:gap-x-8">
<div className="flex items-center text-sm font-medium md:justify-center">
<div className="flex items-center font-medium text-sm md:justify-center">
<Trans>
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}
</Trans>
+4 -9
View File
@@ -1,5 +1,3 @@
import React, { useMemo } from 'react';
import { Trans } from '@lingui/react/macro';
import type {
ColumnDef,
@@ -10,6 +8,8 @@ import type {
VisibilityState,
} from '@tanstack/react-table';
import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import type React from 'react';
import { useMemo } from 'react';
import { Skeleton } from './skeleton';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './table';
@@ -128,9 +128,7 @@ export function DataTable<TData, TValue>({
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
@@ -180,10 +178,7 @@ export function DataTable<TData, TValue>({
</p>
{hasFilters && onClearFilters !== undefined && (
<button
onClick={() => onClearFilters()}
className="mt-1 text-sm text-foreground"
>
<button onClick={() => onClearFilters()} className="mt-1 text-foreground text-sm">
<Trans>Clear filters</Trans>
</button>
)}
+42 -52
View File
@@ -1,8 +1,7 @@
import * as React from 'react';
import { Trans } from '@lingui/react/macro';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -39,7 +38,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in',
'data-[state=closed]:fade-out data-[state=open]:fade-in fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out',
className,
)}
{...props}
@@ -56,41 +55,36 @@ const DialogContent = React.forwardRef<
/* Below prop is to add additional classes to the overlay */
overlayClassName?: string;
}
>(
(
{ className, children, overlayClassName, position = 'start', hideClose = false, ...props },
ref,
) => (
<DialogPortal position={position}>
<DialogOverlay className={cn(overlayClassName)} />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed z-50 grid w-full gap-4 border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0',
{
'rounded-b-xl': position === 'start',
'rounded-t-xl': position === 'end',
},
className,
)}
{...props}
>
{children}
{!hideClose && (
<DialogPrimitive.Close
data-testid="btn-dialog-close"
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<X className="h-4 w-4" />
<span className="sr-only">
<Trans>Close</Trans>
</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
),
);
>(({ className, children, overlayClassName, position = 'start', hideClose = false, ...props }, ref) => (
<DialogPortal position={position}>
<DialogOverlay className={cn(overlayClassName)} />
<DialogPrimitive.Content
ref={ref}
className={cn(
'data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0 fixed z-50 grid w-full animate-in gap-4 border bg-background p-6 shadow-lg sm:max-w-lg sm:rounded-lg',
{
'rounded-b-xl': position === 'start',
'rounded-t-xl': position === 'end',
},
className,
)}
{...props}
>
{children}
{!hideClose && (
<DialogPrimitive.Close
data-testid="btn-dialog-close"
className="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<X className="h-4 w-4" />
<span className="sr-only">
<Trans>Close</Trans>
</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
@@ -118,7 +112,7 @@ const DialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn('truncate text-lg font-semibold tracking-tight', className)}
className={cn('truncate font-semibold text-lg tracking-tight', className)}
{...props}
/>
));
@@ -129,24 +123,20 @@ const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
<DialogPrimitive.Description ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogOverlay,
DialogTitle,
DialogDescription,
DialogPortal,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};
+10 -21
View File
@@ -1,3 +1,7 @@
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { ALLOWED_UPLOAD_MIME_TYPES } from '@documenso/lib/constants/upload';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
@@ -8,11 +12,6 @@ import type { FileRejection } from 'react-dropzone';
import { useDropzone } from 'react-dropzone';
import { Link } from 'react-router';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { ALLOWED_UPLOAD_MIME_TYPES } from '@documenso/lib/constants/upload';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import {
DocumentDropzoneCardCenterVariants,
DocumentDropzoneCardLeftVariants,
@@ -70,19 +69,12 @@ export const DocumentDropzone = ({
});
const heading = {
document: disabled
? disabledHeading || msg`You have reached your document limit.`
: msg`Add a document`,
document: disabled ? disabledHeading || msg`You have reached your document limit.` : msg`Add a document`,
template: msg`Upload Template Document`,
};
return (
<motion.div
variants={DocumentDropzoneContainerVariants}
initial="initial"
animate="animate"
whileHover="hover"
>
<motion.div variants={DocumentDropzoneContainerVariants} initial="initial" animate="animate" whileHover="hover">
<Card
role="button"
className={cn(
@@ -100,7 +92,7 @@ export const DocumentDropzone = ({
// Disabled State
<div className="flex">
<motion.div
className="group-hover:bg-destructive/2 a z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 dark:bg-muted/80"
className="a z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 group-hover:bg-destructive/2 dark:bg-muted/80"
variants={DocumentDropzoneDisabledCardLeftVariants}
>
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
@@ -117,7 +109,7 @@ export const DocumentDropzone = ({
/>
</motion.div>
<motion.div
className="group-hover:bg-destructive/2 z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 dark:bg-muted/80"
className="z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 group-hover:bg-destructive/2 dark:bg-muted/80"
variants={DocumentDropzoneDisabledCardRightVariants}
>
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
@@ -140,10 +132,7 @@ export const DocumentDropzone = ({
className="z-20 flex aspect-[3/4] w-24 flex-col items-center justify-center gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-documenso/80 dark:bg-muted/80"
variants={DocumentDropzoneCardCenterVariants}
>
<Plus
strokeWidth="2px"
className="h-12 w-12 text-muted-foreground/20 group-hover:text-documenso"
/>
<Plus strokeWidth="2px" className="h-12 w-12 text-muted-foreground/20 group-hover:text-documenso" />
</motion.div>
<motion.div
className="z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-documenso/80 dark:bg-muted/80"
@@ -160,7 +149,7 @@ export const DocumentDropzone = ({
<p className="mt-6 font-medium text-foreground">{_(heading[type])}</p>
<p className="mt-1 text-center text-sm text-muted-foreground/80">
<p className="mt-1 text-center text-muted-foreground/80 text-sm">
{_(disabled ? disabledMessage : msg`Drag & drop PDF, DOCX, or images here.`)}
</p>
@@ -1,33 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { FieldType, Prisma, RecipientRole, SendStatus } from '@prisma/client';
import {
CalendarDays,
CheckSquare,
ChevronDown,
Contact,
Disc,
Hash,
Mail,
Type,
User,
} from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useHotkeys } from 'react-hotkeys-hook';
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
import { PDF_VIEWER_PAGE_SELECTOR, getPdfPagesCount } from '@documenso/lib/constants/pdf-viewer';
import {
type TFieldMetaSchema as FieldMeta,
ZFieldMetaSchema,
} from '@documenso/lib/types/field-meta';
import { getPdfPagesCount, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { type TFieldMetaSchema as FieldMeta, ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { nanoid } from '@documenso/lib/universal/id';
import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers';
@@ -38,6 +13,16 @@ import {
canRecipientFieldsBeModified,
getRecipientsWithMissingFields,
} from '@documenso/lib/utils/recipients';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { FieldType, Prisma, RecipientRole, SendStatus } from '@prisma/client';
import { CalendarDays, CheckSquare, ChevronDown, Contact, Disc, Hash, Mail, Type, User } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useHotkeys } from 'react-hotkeys-hook';
import { FieldToolTip } from '../../components/field/field-tooltip';
import { getRecipientColorStyles } from '../../lib/recipient-colors';
@@ -111,8 +96,7 @@ export const AddFieldsFormPartial = ({
const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
const { currentStep, totalSteps, previousStep } = useStep();
const canRenderBackButtonAsRemove =
currentStep === 1 && typeof documentFlow.onBackStep === 'function' && canGoBack;
const canRenderBackButtonAsRemove = currentStep === 1 && typeof documentFlow.onBackStep === 'function' && canGoBack;
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
const [currentField, setCurrentField] = useState<FieldFormType>();
const [activeFieldId, setActiveFieldId] = useState<string | null>(null);
@@ -128,8 +112,7 @@ export const AddFieldsFormPartial = ({
pageY: Number(field.positionY),
pageWidth: Number(field.width),
pageHeight: Number(field.height),
signerEmail:
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
signerEmail: recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
recipientId: field.recipientId,
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
})),
@@ -174,12 +157,8 @@ export const AddFieldsFormPartial = ({
const [selectedField, setSelectedField] = useState<FieldType | null>(null);
const [selectedSigner, setSelectedSigner] = useState<TRecipientLite | null>(null);
const [lastActiveField, setLastActiveField] = useState<TAddFieldsFormSchema['fields'][0] | null>(
null,
);
const [fieldClipboard, setFieldClipboard] = useState<TAddFieldsFormSchema['fields'][0] | null>(
null,
);
const [lastActiveField, setLastActiveField] = useState<TAddFieldsFormSchema['fields'][0] | null>(null);
const [fieldClipboard, setFieldClipboard] = useState<TAddFieldsFormSchema['fields'][0] | null>(null);
const selectedSignerIndex = recipients.findIndex((r) => r.id === selectedSigner?.id);
const selectedSignerStyles = getRecipientColorStyles(selectedSignerIndex);
@@ -214,15 +193,12 @@ export const AddFieldsFormPartial = ({
[localFields],
);
const hasErrors =
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
const hasErrors = emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
const fieldsWithError = useMemo(() => {
const fields = localFields.filter((field) => {
const hasError =
((field.type === FieldType.CHECKBOX ||
field.type === FieldType.RADIO ||
field.type === FieldType.DROPDOWN) &&
((field.type === FieldType.CHECKBOX || field.type === FieldType.RADIO || field.type === FieldType.DROPDOWN) &&
field.fieldMeta === undefined) ||
(field.fieldMeta && 'values' in field.fieldMeta && field?.fieldMeta?.values?.length === 0);
@@ -271,12 +247,7 @@ export const AddFieldsFormPartial = ({
const onMouseMove = useCallback(
(event: MouseEvent) => {
setIsFieldWithinBounds(
isWithinPageBounds(
event,
PDF_VIEWER_PAGE_SELECTOR,
fieldBounds.current.width,
fieldBounds.current.height,
),
isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height),
);
setCoords({
@@ -297,12 +268,7 @@ export const AddFieldsFormPartial = ({
if (
!$page ||
!isWithinPageBounds(
event,
PDF_VIEWER_PAGE_SELECTOR,
fieldBounds.current.width,
fieldBounds.current.height,
)
!isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height)
) {
setSelectedField(null);
return;
@@ -365,12 +331,7 @@ export const AddFieldsFormPartial = ({
return;
}
const {
x: pageX,
y: pageY,
width: pageWidth,
height: pageHeight,
} = getFieldPosition($page, node);
const { x: pageX, y: pageY, width: pageWidth, height: pageHeight } = getFieldPosition($page, node);
update(index, {
...field,
@@ -526,13 +487,11 @@ export const AddFieldsFormPartial = ({
useEffect(() => {
const recipientsByRoleToDisplay = recipients.filter(
(recipient) =>
recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
(recipient) => recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
);
setSelectedSigner(
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ??
recipientsByRoleToDisplay[0],
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ?? recipientsByRoleToDisplay[0],
);
}, [recipients]);
@@ -595,10 +554,7 @@ export const AddFieldsFormPartial = ({
{showAdvancedSettings && currentField ? (
<FieldAdvancedSettings
title={msg`Advanced settings`}
description={msg`Configure the ${parseMessageDescriptor(
_,
FRIENDLY_FIELD_TYPE[currentField.type],
)} field`}
description={msg`Configure the ${parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[currentField.type])} field`}
field={currentField}
fields={localFields}
onAdvancedSettings={handleAdvancedSettings}
@@ -614,17 +570,14 @@ export const AddFieldsFormPartial = ({
/>
) : (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
<div className="flex flex-col">
{selectedField && (
<div
className={cn(
'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size]',
'pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size] dark:text-muted-background',
selectedSignerStyles?.base,
{
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
@@ -658,8 +611,7 @@ export const AddFieldsFormPartial = ({
recipientIndex={recipientIndex === -1 ? 0 : recipientIndex}
field={field}
disabled={
selectedSigner?.email !== field.signerEmail ||
!canRecipientBeModified(selectedSigner, fields)
selectedSigner?.email !== field.signerEmail || !canRecipientBeModified(selectedSigner, fields)
}
minHeight={MIN_HEIGHT_PX}
minWidth={MIN_WIDTH_PX}
@@ -704,7 +656,7 @@ export const AddFieldsFormPartial = ({
selectedRecipient={selectedSigner}
onSelectedRecipientChange={setSelectedSigner}
recipients={recipients}
className="mb-12 mt-2"
className="mt-2 mb-12"
/>
)}
@@ -726,7 +678,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 font-signature text-lg font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal font-signature text-lg text-muted-foreground group-data-[selected]:text-foreground',
)}
>
<Trans>Signature</Trans>
@@ -750,7 +702,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Contact className="h-4 w-4" />
@@ -775,7 +727,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Mail className="h-4 w-4" />
@@ -800,7 +752,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<User className="h-4 w-4" />
@@ -825,7 +777,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<CalendarDays className="h-4 w-4" />
@@ -850,7 +802,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Type className="h-4 w-4" />
@@ -875,7 +827,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Hash className="h-4 w-4" />
@@ -900,7 +852,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Disc className="h-4 w-4" />
@@ -925,7 +877,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<CheckSquare className="h-4 w-4" />
@@ -950,7 +902,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<ChevronDown className="h-4 w-4" />
@@ -968,14 +920,10 @@ export const AddFieldsFormPartial = ({
{hasErrors && (
<div className="mt-4">
<ul>
<li className="text-sm text-red-500">
<li className="text-red-500 text-sm">
<Trans>
To proceed further, please set at least one value for the{' '}
{emptyCheckboxFields.length > 0
? 'Checkbox'
: emptyRadioFields.length > 0
? 'Radio'
: 'Select'}{' '}
{emptyCheckboxFields.length > 0 ? 'Checkbox' : emptyRadioFields.length > 0 ? 'Radio' : 'Select'}{' '}
field.
</Trans>
</li>
@@ -987,8 +935,7 @@ export const AddFieldsFormPartial = ({
<Alert variant="warning">
<AlertDescription>
<Trans>
This recipient can no longer be modified as they have signed a field, or completed
the document.
This recipient can no longer be modified as they have signed a field, or completed the document.
</Trans>
</AlertDescription>
</Alert>
@@ -1,8 +1,7 @@
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { FieldType } from '@prisma/client';
import { z } from 'zod';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
export const ZAddFieldsFormSchema = z.object({
fields: z.array(
z.object({
@@ -1,18 +1,3 @@
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import {
DocumentStatus,
DocumentVisibility,
type Field,
SendStatus,
TeamMemberRole,
} from '@prisma/client';
import { InfoIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
@@ -39,21 +24,16 @@ import {
DocumentVisibilitySelect,
DocumentVisibilityTooltip,
} from '@documenso/ui/components/document/document-visibility-select';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@documenso/ui/primitives/accordion';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@documenso/ui/primitives/accordion';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentStatus, DocumentVisibility, type Field, SendStatus, TeamMemberRole } from '@prisma/client';
import { InfoIcon } from 'lucide-react';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { DocumentSignatureSettingsTooltip } from '../../components/document/document-signature-settings-tooltip';
import { Combobox } from '../combobox';
@@ -112,11 +92,10 @@ export const AddSettingsFormPartial = ({
meta: {
timezone:
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ??
DEFAULT_DOCUMENT_TIME_ZONE,
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ?? DEFAULT_DOCUMENT_TIME_ZONE,
dateFormat:
DATE_FORMATS.find((format) => format.value === document.documentMeta?.dateFormat)
?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT,
DATE_FORMATS.find((format) => format.value === document.documentMeta?.dateFormat)?.value ??
DEFAULT_DOCUMENT_DATE_FORMAT,
redirectUrl: document.documentMeta?.redirectUrl ?? '',
language: document.documentMeta?.language ?? 'en',
signatureTypes: extractTeamSignatureSettings(document.documentMeta),
@@ -126,9 +105,7 @@ export const AddSettingsFormPartial = ({
const { stepIndex, currentStep, totalSteps, previousStep } = useStep();
const documentHasBeenSent = recipients.some(
(recipient) => recipient.sendStatus === SendStatus.SENT,
);
const documentHasBeenSent = recipients.some((recipient) => recipient.sendStatus === SendStatus.SENT);
const canUpdateVisibility = match(currentTeamMemberRole)
.with(TeamMemberRole.ADMIN, () => true)
@@ -149,11 +126,7 @@ export const AddSettingsFormPartial = ({
// We almost always want to set the timezone to the user's local timezone to avoid confusion
// when the document is signed.
useEffect(() => {
if (
!form.formState.touchedFields.meta?.timezone &&
!documentHasBeenSent &&
!document.documentMeta?.timezone
) {
if (!form.formState.touchedFields.meta?.timezone && !documentHasBeenSent && !document.documentMeta?.timezone) {
form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
}
}, [
@@ -188,10 +161,7 @@ export const AddSettingsFormPartial = ({
return (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
{isDocumentPdfLoaded && (
@@ -203,10 +173,7 @@ export const AddSettingsFormPartial = ({
)}
<Form {...form}>
<fieldset
className="flex h-full flex-col space-y-6"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-6" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="title"
@@ -244,9 +211,8 @@ export const AddSettingsFormPartial = ({
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
<Trans>
Controls the language for the document, including the language to be used
for email notifications, and the final certificate that is generated and
attached to the document.
Controls the language for the document, including the language to be used for email
notifications, and the final certificate that is generated and attached to the document.
</Trans>
</TooltipContent>
</Tooltip>
@@ -365,7 +331,7 @@ export const AddSettingsFormPartial = ({
<Trans>Advanced Options</Trans>
</AccordionTrigger>
<AccordionContent className="-mx-1 px-1 pt-2 text-sm leading-relaxed text-muted-foreground">
<AccordionContent className="-mx-1 px-1 pt-2 text-muted-foreground text-sm leading-relaxed">
<div className="flex flex-col space-y-6">
<FormField
control={form.control}
@@ -381,8 +347,8 @@ export const AddSettingsFormPartial = ({
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
Add an external ID to the document. This can be used to identify
the document in external systems.
Add an external ID to the document. This can be used to identify the document in
external systems.
</Trans>
</TooltipContent>
</Tooltip>
@@ -507,9 +473,7 @@ export const AddSettingsFormPartial = ({
</TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
Add a URL to redirect the user to once the document is signed
</Trans>
<Trans>Add a URL to redirect the user to once the document is signed</Trans>
</TooltipContent>
</Tooltip>
</FormLabel>
@@ -1,26 +1,16 @@
import { msg } from '@lingui/core/macro';
import { DocumentVisibility } from '@prisma/client';
import { z } from 'zod';
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaTimezoneSchema,
} from '@documenso/lib/types/document-meta';
import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { ZDocumentMetaDateFormatSchema, ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { msg } from '@lingui/core/macro';
import { DocumentVisibility } from '@prisma/client';
import { z } from 'zod';
export const ZAddSettingsFormSchema = z.object({
title: z
.string()
.trim()
.min(1, { message: msg`Title cannot be empty`.id }),
title: z.string().trim().min(1, { message: msg`Title cannot be empty`.id }),
externalId: z.string().optional(),
visibility: z.nativeEnum(DocumentVisibility).optional(),
globalAccessAuth: z
@@ -36,8 +26,7 @@ export const ZAddSettingsFormSchema = z.object({
.string()
.optional()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
}),
language: z
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
@@ -1,18 +1,3 @@
import { useCallback, useId, useMemo, useRef, useState } from 'react';
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@prisma/client';
import { motion } from 'framer-motion';
import { GripVerticalIcon, HelpCircle, Plus, Trash } from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import { prop, sortBy } from 'remeda';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
@@ -27,11 +12,21 @@ import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animat
import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select';
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
import { cn } from '@documenso/ui/lib/utils';
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@prisma/client';
import { motion } from 'framer-motion';
import { GripVerticalIcon, HelpCircle, Plus, Trash } from 'lucide-react';
import { useCallback, useId, useMemo, useRef, useState } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { prop, sortBy } from 'remeda';
import {
DocumentReadOnlyFields,
mapFieldsWithRecipients,
} from '../../components/document/document-read-only-fields';
import { DocumentReadOnlyFields, mapFieldsWithRecipients } from '../../components/document/document-read-only-fields';
import type { RecipientAutoCompleteOption } from '../../components/recipient/recipient-autocomplete-input';
import { RecipientAutoCompleteInput } from '../../components/recipient/recipient-autocomplete-input';
import { Button } from '../button';
@@ -130,8 +125,7 @@ export const AddSignersFormPartial = ({
email: recipient.email,
role: recipient.role,
signingOrder: recipient.signingOrder ?? index + 1,
actionAuth:
ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined,
actionAuth: ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined,
})),
[prop('signingOrder'), 'asc'],
[prop('nativeId'), 'asc'],
@@ -147,14 +141,10 @@ export const AddSignersFormPartial = ({
const recipientHasAuthOptions = recipients.find((recipient) => {
const recipientAuthOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions);
return (
recipientAuthOptions.accessAuth.length > 0 || recipientAuthOptions.actionAuth.length > 0
);
return recipientAuthOptions.accessAuth.length > 0 || recipientAuthOptions.actionAuth.length > 0;
});
const formHasActionAuth = form
.getValues('signers')
.find((signer) => signer.actionAuth.length > 0);
const formHasActionAuth = form.getValues('signers').find((signer) => signer.actionAuth.length > 0);
return recipientHasAuthOptions !== undefined || formHasActionAuth !== undefined;
}, [recipients, form]);
@@ -193,10 +183,7 @@ export const AddSignersFormPartial = ({
name: 'signers',
});
const emptySigners = useCallback(
() => form.getValues('signers').filter((signer) => signer.email === ''),
[form],
);
const emptySigners = useCallback(() => form.getValues('signers').filter((signer) => signer.email === ''), [form]);
const { scheduleSave } = useAutoSave(onAutoSave);
@@ -220,9 +207,7 @@ export const AddSignersFormPartial = ({
const updatedSigners = currentSigners.map((signer) => {
// Find the matching recipient from the response using nativeId
const matchingRecipient = response.recipients.find(
(recipient) => recipient.id === signer.nativeId,
);
const matchingRecipient = response.recipients.find((recipient) => recipient.id === signer.nativeId);
if (matchingRecipient) {
// Update the signer with the server-returned data, especially the ID
@@ -233,9 +218,7 @@ export const AddSignersFormPartial = ({
} else if (!signer.nativeId) {
// For new signers without nativeId, match by email and update with server ID
// Bug: Multiple recipients with the same email will be matched incorrectly here.
const newRecipient = response.recipients.find(
(recipient) => recipient.email === signer.email,
);
const newRecipient = response.recipients.find((recipient) => recipient.email === signer.email);
if (newRecipient) {
return {
...signer,
@@ -258,9 +241,7 @@ export const AddSignersFormPartial = ({
(signer) => signer.email.toLowerCase() === user?.email?.toLowerCase(),
);
const hasDocumentBeenSent = recipients.some(
(recipient) => recipient.sendStatus === SendStatus.SENT,
);
const hasDocumentBeenSent = recipients.some((recipient) => recipient.sendStatus === SendStatus.SENT);
const canRecipientBeModified = (recipientId?: number) => {
if (recipientId === undefined) {
@@ -335,8 +316,7 @@ export const AddSignersFormPartial = ({
email: user?.email ?? '',
role: RecipientRole.SIGNER,
actionAuth: [],
signingOrder:
signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
signingOrder: signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
},
{
shouldFocus: true,
@@ -347,10 +327,7 @@ export const AddSignersFormPartial = ({
}
};
const handleRecipientAutoCompleteSelect = (
index: number,
suggestion: RecipientAutoCompleteOption,
) => {
const handleRecipientAutoCompleteSelect = (index: number, suggestion: RecipientAutoCompleteOption) => {
setValue(`signers.${index}.email`, suggestion.email, {
shouldValidate: true,
shouldDirty: true,
@@ -363,7 +340,9 @@ export const AddSignersFormPartial = ({
const onDragEnd = useCallback(
async (result: DropResult) => {
if (!result.destination) return;
if (!result.destination) {
return;
}
const items = Array.from(watchedSigners);
const [reorderedSigner] = items.splice(result.source.index, 1);
@@ -514,10 +493,7 @@ export const AddSignersFormPartial = ({
return (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
{isDocumentPdfLoaded && (
<DocumentReadOnlyFields
@@ -545,9 +521,7 @@ export const AddSignersFormPartial = ({
return;
}
field.onChange(
checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL,
);
field.onChange(checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL);
// If sequential signing is turned off, disable dictate next signer
if (!checked) {
@@ -623,8 +597,8 @@ export const AddSignersFormPartial = ({
<TooltipContent className="max-w-80 p-4">
<p>
<Trans>
When enabled, signers can choose who should sign next in the sequence
instead of following the predefined order.
When enabled, signers can choose who should sign next in the sequence instead of following
the predefined order.
</Trans>
</p>
</TooltipContent>
@@ -644,11 +618,7 @@ export const AddSignersFormPartial = ({
>
<Droppable droppableId="signers">
{(provided) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
className="flex w-full flex-col gap-y-2"
>
<div {...provided.droppableProps} ref={provided.innerRef} className="flex w-full flex-col gap-y-2">
{signers.map((signer, index) => (
<Draggable
key={`${signer.id}-${signer.signingOrder}`}
@@ -667,8 +637,7 @@ export const AddSignersFormPartial = ({
{...provided.draggableProps}
{...provided.dragHandleProps}
className={cn('py-1', {
'pointer-events-none rounded-md bg-widget-foreground pt-2':
snapshot.isDragging,
'pointer-events-none rounded-md bg-widget-foreground pt-2': snapshot.isDragging,
})}
>
<motion.fieldset
@@ -685,14 +654,11 @@ export const AddSignersFormPartial = ({
name={`signers.${index}.signingOrder`}
render={({ field }) => (
<FormItem
className={cn(
'col-span-2 mt-auto flex items-center gap-x-1 space-y-0',
{
'mb-6':
form.formState.errors.signers?.[index] &&
!form.formState.errors.signers[index]?.signingOrder,
},
)}
className={cn('col-span-2 mt-auto flex items-center gap-x-1 space-y-0', {
'mb-6':
form.formState.errors.signers?.[index] &&
!form.formState.errors.signers[index]?.signingOrder,
})}
>
<GripVerticalIcon className="h-5 w-5 flex-shrink-0 opacity-40" />
<FormControl>
@@ -758,9 +724,7 @@ export const AddSignersFormPartial = ({
!canRecipientBeModified(signer.nativeId)
}
options={recipientSuggestions}
onSelect={(suggestion) =>
handleRecipientAutoCompleteSelect(index, suggestion)
}
onSelect={(suggestion) => handleRecipientAutoCompleteSelect(index, suggestion)}
onSearchQueryChange={(query) => {
field.onChange(query);
setRecipientSearchQuery(query);
@@ -807,9 +771,7 @@ export const AddSignersFormPartial = ({
!canRecipientBeModified(signer.nativeId)
}
options={recipientSuggestions}
onSelect={(suggestion) =>
handleRecipientAutoCompleteSelect(index, suggestion)
}
onSelect={(suggestion) => handleRecipientAutoCompleteSelect(index, suggestion)}
onSearchQueryChange={(query) => {
field.onChange(query);
setRecipientSearchQuery(query);
@@ -825,37 +787,36 @@ export const AddSignersFormPartial = ({
)}
/>
{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>
{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
@@ -940,7 +901,7 @@ export const AddSignersFormPartial = ({
disabled={isSubmitting || signers.length >= remaining.recipients}
onClick={() => onAddSigner()}
>
<Plus className="-ml-1 mr-2 h-5 w-5" />
<Plus className="mr-2 -ml-1 h-5 w-5" />
<Trans>Add Signer</Trans>
</Button>
@@ -951,7 +912,7 @@ export const AddSignersFormPartial = ({
disabled={isSubmitting || isUserAlreadyARecipient}
onClick={() => onAddSelfSigner()}
>
<Plus className="-ml-1 mr-2 h-5 w-5" />
<Plus className="mr-2 -ml-1 h-5 w-5" />
<Trans>Add myself</Trans>
</Button>
</div>
@@ -965,10 +926,7 @@ export const AddSignersFormPartial = ({
onCheckedChange={(value) => setShowAdvancedSettings(Boolean(value))}
/>
<label
className="ml-2 text-sm text-muted-foreground"
htmlFor="showAdvancedRecipientSettings"
>
<label className="ml-2 text-muted-foreground text-sm" htmlFor="showAdvancedRecipientSettings">
<Trans>Show advanced settings</Trans>
</label>
</div>
@@ -1,10 +1,9 @@
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { zEmail } from '@documenso/lib/utils/zod';
import { msg } from '@lingui/core/macro';
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
import { z } from 'zod';
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { zEmail } from '@documenso/lib/utils/zod';
export const ZAddSignersFormSchema = z.object({
signers: z.array(
z.object({
@@ -1,15 +1,3 @@
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { DocumentDistributionMethod, DocumentStatus, RecipientRole } from '@prisma/client';
import { AnimatePresence, motion } from 'framer-motion';
import { InfoIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
@@ -19,29 +7,23 @@ import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { formatSigningLink } from '@documenso/lib/utils/recipients';
import { trpc } from '@documenso/trpc/react';
import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { DocumentDistributionMethod, DocumentStatus, RecipientRole } from '@prisma/client';
import { AnimatePresence, motion } from 'framer-motion';
import { InfoIcon } from 'lucide-react';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { CopyTextButton } from '../../components/common/copy-text-button';
import { DocumentEmailCheckboxes } from '../../components/document/document-email-checkboxes';
import {
DocumentReadOnlyFields,
mapFieldsWithRecipients,
} from '../../components/document/document-read-only-fields';
import { DocumentReadOnlyFields, mapFieldsWithRecipients } from '../../components/document/document-read-only-fields';
import { AvatarWithText } from '../avatar';
import { Input } from '../input';
import { useStep } from '../stepper';
@@ -70,8 +52,8 @@ export type AddSubjectFormProps = {
export const AddSubjectFormPartial = ({
documentFlow,
recipients: recipients,
fields: fields,
recipients,
fields,
document,
onSubmit,
onAutoSave,
@@ -89,8 +71,7 @@ export const AddSubjectFormPartial = ({
// emailReplyName: document.documentMeta?.emailReplyName || undefined,
subject: document.documentMeta?.subject ?? '',
message: document.documentMeta?.message ?? '',
distributionMethod:
document.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
distributionMethod: document.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
emailSettings: ZDocumentEmailSettingsSchema.parse(document?.documentMeta?.emailSettings),
},
},
@@ -106,11 +87,10 @@ export const AddSubjectFormPartial = ({
formState: { isSubmitting },
} = form;
const { data: emailData, isLoading: isLoadingEmails } =
trpc.enterprise.organisation.email.find.useQuery({
organisationId: organisation.id,
perPage: 100,
});
const { data: emailData, isLoading: isLoadingEmails } = trpc.enterprise.organisation.email.find.useQuery({
organisationId: organisation.id,
perPage: 100,
});
const emails = emailData?.data || [];
@@ -168,10 +148,7 @@ export const AddSubjectFormPartial = ({
return (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
<div className="flex flex-col">
{isDocumentPdfLoaded && (
@@ -226,9 +203,7 @@ export const AddSubjectFormPartial = ({
<Select
{...field}
value={field.value === null ? '-1' : field.value}
onValueChange={(value) =>
field.onChange(value === '-1' ? null : value)
}
onValueChange={(value) => field.onChange(value === '-1' ? null : value)}
>
<SelectTrigger loading={isLoadingEmails} className="bg-background">
<SelectValue />
@@ -259,8 +234,7 @@ export const AddSubjectFormPartial = ({
<FormItem>
<FormLabel>
<Trans>
Reply To Email{' '}
<span className="text-muted-foreground">(Optional)</span>
Reply To Email <span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
@@ -331,11 +305,7 @@ export const AddSubjectFormPartial = ({
</FormLabel>
<FormControl>
<Textarea
className="mt-2 h-16 resize-none bg-background"
{...field}
maxLength={5000}
/>
<Textarea className="mt-2 h-16 resize-none bg-background" {...field} maxLength={5000} />
</FormControl>
<FormMessage />
</FormItem>
@@ -361,15 +331,15 @@ export const AddSubjectFormPartial = ({
className="rounded-lg border"
>
{document.status === DocumentStatus.DRAFT ? (
<div className="py-16 text-center text-sm text-muted-foreground">
<div className="py-16 text-center text-muted-foreground text-sm">
<p>
<Trans>We won't send anything to notify recipients.</Trans>
</p>
<p className="mt-2">
<Trans>
We will generate signing links for you, which you can send to the recipients
through your method of choice.
We will generate signing links for you, which you can send to the recipients through your method
of choice.
</Trans>
</p>
</div>
@@ -382,17 +352,12 @@ export const AddSubjectFormPartial = ({
)}
{recipients.map((recipient) => (
<li
key={recipient.id}
className="flex items-center justify-between px-4 py-3 text-sm"
>
<li key={recipient.id} className="flex items-center justify-between px-4 py-3 text-sm">
<AvatarWithText
avatarFallback={recipient.email.slice(0, 1).toUpperCase()}
primaryText={
<p className="text-sm text-muted-foreground">{recipient.email}</p>
}
primaryText={<p className="text-muted-foreground text-sm">{recipient.email}</p>}
secondaryText={
<p className="text-xs text-muted-foreground/70">
<p className="text-muted-foreground/70 text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
}
@@ -404,9 +369,7 @@ export const AddSubjectFormPartial = ({
onCopySuccess={() => {
toast({
title: _(msg`Copied to clipboard`),
description: _(
msg`The signing link has been copied to your clipboard.`,
),
description: _(msg`The signing link has been copied to your clipboard.`),
});
}}
badgeContentUncopied={
@@ -1,8 +1,7 @@
import { DocumentDistributionMethod } from '@prisma/client';
import { z } from 'zod';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { zEmail } from '@documenso/lib/utils/zod';
import { DocumentDistributionMethod } from '@prisma/client';
import { z } from 'zod';
export const ZAddSubjectFormSchema = z.object({
meta: z.object({
@@ -11,10 +10,7 @@ export const ZAddSubjectFormSchema = z.object({
// emailReplyName: z.string().optional(),
subject: z.string(),
message: z.string(),
distributionMethod: z
.nativeEnum(DocumentDistributionMethod)
.optional()
.default(DocumentDistributionMethod.EMAIL),
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional().default(DocumentDistributionMethod.EMAIL),
emailSettings: ZDocumentEmailSettingsSchema,
}),
});
@@ -1,11 +1,10 @@
import type { HTMLAttributes } from 'react';
import React from 'react';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import type React from 'react';
import type { HTMLAttributes } from 'react';
import { cn } from '../../lib/utils';
import { Button } from '../button';
@@ -39,19 +38,16 @@ export type DocumentFlowFormContainerHeaderProps = {
description: MessageDescriptor;
};
export const DocumentFlowFormContainerHeader = ({
title,
description,
}: DocumentFlowFormContainerHeaderProps) => {
export const DocumentFlowFormContainerHeader = ({ title, description }: DocumentFlowFormContainerHeaderProps) => {
const { _ } = useLingui();
return (
<>
<h3 className="text-2xl font-semibold text-foreground">{_(title)}</h3>
<h3 className="font-semibold text-2xl text-foreground">{_(title)}</h3>
<p className="mt-2 text-sm text-muted-foreground">{_(description)}</p>
<p className="mt-2 text-muted-foreground text-sm">{_(description)}</p>
<hr className="mb-8 mt-4 border-border" />
<hr className="mt-4 mb-8 border-border" />
</>
);
};
@@ -93,13 +89,10 @@ export type DocumentFlowFormContainerStepProps = {
maxStep: number;
};
export const DocumentFlowFormContainerStep = ({
step,
maxStep,
}: DocumentFlowFormContainerStepProps) => {
export const DocumentFlowFormContainerStep = ({ step, maxStep }: DocumentFlowFormContainerStepProps) => {
return (
<div>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>
Step <span>{`${step} of ${maxStep}`}</span>
</Trans>
@@ -1,16 +1,12 @@
import { convertToLocalSystemFormat, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { DocumentMeta, Signature } from '@prisma/client';
import { FieldType } from '@prisma/client';
import { ChevronDown } from 'lucide-react';
import {
DEFAULT_DOCUMENT_DATE_FORMAT,
convertToLocalSystemFormat,
} from '@documenso/lib/constants/date-formats';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
import { cn } from '../../lib/utils';
import { Checkbox } from '../checkbox';
import { Label } from '../label';
@@ -62,7 +58,7 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
>
<div className="flex items-center">
<Checkbox className="h-3 w-3" disabled />
<Label className="ml-1.5 text-xs font-normal text-foreground opacity-50">
<Label className="ml-1.5 font-normal text-foreground text-xs opacity-50">
<Trans>Checkbox option</Trans>
</Label>
</div>
@@ -88,10 +84,7 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
/>
{item.value && (
<Label
htmlFor={`checkbox-${index}`}
className="ml-1.5 text-xs font-normal text-foreground"
>
<Label htmlFor={`checkbox-${index}`} className="ml-1.5 font-normal text-foreground text-xs">
{item.value}
</Label>
)}
@@ -120,10 +113,7 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
checked={item.value === field.customText}
/>
{item.value && (
<Label
htmlFor={`option-${index}`}
className="ml-1.5 text-xs font-normal text-foreground"
>
<Label htmlFor={`option-${index}`} className="ml-1.5 font-normal text-foreground text-xs">
{item.value}
</Label>
)}
@@ -134,13 +124,9 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
);
}
if (
field.type === FieldType.DROPDOWN &&
field.fieldMeta?.type === 'dropdown' &&
!field.inserted
) {
if (field.type === FieldType.DROPDOWN && field.fieldMeta?.type === 'dropdown' && !field.inserted) {
return (
<div className="flex flex-row items-center py-0.5 text-[clamp(0.07rem,25cqw,0.825rem)] text-sm text-field-card-foreground">
<div className="flex flex-row items-center py-0.5 text-[clamp(0.07rem,25cqw,0.825rem)] text-field-card-foreground text-sm">
<p>
<Trans>Select</Trans>
</p>
@@ -149,25 +135,16 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
);
}
if (
field.type === FieldType.SIGNATURE &&
field.signature?.signatureImageAsBase64 &&
field.inserted
) {
if (field.type === FieldType.SIGNATURE && field.signature?.signatureImageAsBase64 && field.inserted) {
return (
<img
src={field.signature.signatureImageAsBase64}
alt="Signature"
className="h-full w-full object-contain"
/>
<img src={field.signature.signatureImageAsBase64} alt="Signature" className="h-full w-full object-contain" />
);
}
const labelToDisplay = fieldMeta?.label || _(FRIENDLY_FIELD_TYPE[type]) || '';
let textToDisplay: string | undefined;
const isSignatureField =
field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
const isSignatureField = field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
if (field.type === FieldType.TEXT && field.fieldMeta?.type === 'text' && field.fieldMeta?.text) {
textToDisplay = field.fieldMeta.text;
@@ -1,16 +1,10 @@
import { forwardRef, useEffect, useState } from 'react';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { FieldType } from '@prisma/client';
import { match } from 'ts-pattern';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import {
type TBaseFieldMeta as BaseFieldMeta,
type TCheckboxFieldMeta as CheckboxFieldMeta,
type TDateFieldMeta as DateFieldMeta,
DEFAULT_DATE_OVERFLOW_MODE,
DEFAULT_EMAIL_OVERFLOW_MODE,
type TDropdownFieldMeta as DropdownFieldMeta,
type TEmailFieldMeta as EmailFieldMeta,
type TFieldMetaSchema as FieldMeta,
@@ -22,6 +16,12 @@ import {
ZFieldMetaSchema,
} from '@documenso/lib/types/field-meta';
import { useToast } from '@documenso/ui/primitives/use-toast';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { FieldType } from '@prisma/client';
import { forwardRef, useEffect, useState } from 'react';
import { match } from 'ts-pattern';
import type { FieldFormType } from './add-fields';
import {
@@ -83,12 +83,14 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
type: 'email',
fontSize: 14,
textAlign: 'left',
overflow: DEFAULT_EMAIL_OVERFLOW_MODE,
};
case FieldType.DATE:
return {
type: 'date',
fontSize: 14,
textAlign: 'left',
overflow: DEFAULT_DATE_OVERFLOW_MODE,
};
case FieldType.TEXT:
return {
@@ -148,19 +150,7 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
};
export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSettingsProps>(
(
{
title,
description,
field,
fields,
onAdvancedSettings,
isDocumentPdfLoaded = true,
onSave,
onAutoSave,
},
ref,
) => {
({ title, description, field, fields, onAdvancedSettings, isDocumentPdfLoaded = true, onSave, onAutoSave }, ref) => {
const { _ } = useLingui();
const { toast } = useToast();
@@ -186,7 +176,6 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
...parsedFieldMeta,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fieldMeta]);
const { scheduleSave } = useAutoSave(onAutoSave || (async () => {}));
@@ -209,17 +198,10 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
const handleFieldChange = (
key: FieldMetaKeys,
value:
| string
| { checked: boolean; value: string }[]
| { value: string }[]
| boolean
| number,
value: string | { checked: boolean; value: string }[] | { value: string }[] | boolean | number,
) => {
setFieldState((prevState: FieldMeta) => {
if (
['characterLimit', 'minValue', 'maxValue', 'validationLength', 'fontSize'].includes(key)
) {
if (['characterLimit', 'minValue', 'maxValue', 'validationLength', 'fontSize'].includes(key)) {
const parsedValue = Number(value);
return {
@@ -268,9 +250,7 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
key={index}
field={localField}
disabled={true}
fieldClassName={
localField.formId === field.formId ? 'ring-red-400' : 'ring-neutral-200'
}
fieldClassName={localField.formId === field.formId ? 'ring-red-400' : 'ring-neutral-200'}
/>
</span>
))}
@@ -346,7 +326,7 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
<div className="mt-4">
<ul>
{errors.map((error, index) => (
<li className="text-sm text-red-500" key={index}>
<li className="text-red-500 text-sm" key={index}>
{error}
</li>
))}
@@ -355,10 +335,7 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
)}
</DocumentFlowFormContainerContent>
<DocumentFlowFormContainerFooter
className="mt-auto"
data-testid="field-advanced-settings-footer"
>
<DocumentFlowFormContainerFooter className="mt-auto" data-testid="field-advanced-settings-footer">
<DocumentFlowFormContainerActions
goNextLabel={msg`Save`}
goBackLabel={msg`Cancel`}
@@ -1,19 +1,17 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { FieldType } from '@prisma/client';
import { CopyPlus, Settings2, SquareStack, Trash } from 'lucide-react';
import { createPortal } from 'react-dom';
import { Rnd } from 'react-rnd';
import { useSearchParams } from 'react-router';
import { useElementBounds } from '@documenso/lib/client-only/hooks/use-element-bounds';
import { useIsPageInDom } from '@documenso/lib/client-only/hooks/use-is-page-in-dom';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { ZCheckboxFieldMeta, ZRadioFieldMeta } from '@documenso/lib/types/field-meta';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { FieldType } from '@prisma/client';
import { CopyPlus, Settings2, SquareStack, Trash } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Rnd } from 'react-rnd';
import { useSearchParams } from 'react-router';
import { getRecipientColorStyles } from '../../lib/recipient-colors';
import { cn } from '../../lib/utils';
@@ -96,9 +94,7 @@ const FieldItemInner = ({
const [settingsActive, setSettingsActive] = useState(false);
const $el = useRef<HTMLDivElement>(null);
const $pageBounds = useElementBounds(
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`,
);
const $pageBounds = useElementBounds(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`);
const signerStyles = getRecipientColorStyles(recipientIndex);
@@ -275,20 +271,16 @@ const FieldItemInner = ({
onMove?.(d.node);
}}
>
{(field.type === FieldType.RADIO || field.type === FieldType.CHECKBOX) &&
field.fieldMeta?.label && (
<div
className={cn(
'absolute -top-16 left-0 right-0 rounded-md p-2 text-center text-xs text-gray-700',
{
'border border-primary bg-foreground/5': !fieldHasCheckedValues,
'border border-primary bg-documenso-200': fieldHasCheckedValues,
},
)}
>
{field.fieldMeta.label}
</div>
)}
{(field.type === FieldType.RADIO || field.type === FieldType.CHECKBOX) && field.fieldMeta?.label && (
<div
className={cn('absolute -top-16 right-0 left-0 rounded-md p-2 text-center text-gray-700 text-xs', {
'border border-primary bg-foreground/5': !fieldHasCheckedValues,
'border border-primary bg-documenso-200': fieldHasCheckedValues,
})}
>
{field.fieldMeta.label}
</div>
)}
<div
className={cn(
@@ -317,18 +309,17 @@ const FieldItemInner = ({
<FieldContent field={field} />
{/* On hover, display recipient initials on side of field. */}
<div className="absolute -right-5 top-0 z-20 hidden h-full w-5 items-center justify-center group-hover:flex">
<div className="absolute top-0 -right-5 z-20 hidden h-full w-5 items-center justify-center group-hover:flex">
<div
className={cn(
'flex h-5 w-5 flex-col items-center justify-center rounded-r-md text-[0.5rem] font-bold text-white opacity-0 transition duration-200 group-hover/field-item:opacity-100',
'flex h-5 w-5 flex-col items-center justify-center rounded-r-md font-bold text-[0.5rem] text-white opacity-0 transition duration-200 group-hover/field-item:opacity-100',
signerStyles.fieldItemInitials,
{
'!opacity-50': disabled || passive,
},
)}
>
{(field.signerEmail?.charAt(0)?.toUpperCase() ?? '') +
(field.signerEmail?.charAt(1)?.toUpperCase() ?? '')}
{(field.signerEmail?.charAt(0)?.toUpperCase() ?? '') + (field.signerEmail?.charAt(1)?.toUpperCase() ?? '')}
</div>
</div>
@@ -1,24 +1,16 @@
import { useEffect, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { type TCheckboxFieldMeta as CheckboxFieldMeta } from '@documenso/lib/types/field-meta';
import type { TCheckboxFieldMeta as CheckboxFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { useEffect, useState } from 'react';
import { checkboxValidationLength, checkboxValidationRules } from './constants';
@@ -44,19 +36,14 @@ export const CheckboxFieldAdvancedSettings = ({
const [required, setRequired] = useState(fieldState.required ?? false);
const [validationLength, setValidationLength] = useState(fieldState.validationLength ?? 0);
const [validationRule, setValidationRule] = useState(fieldState.validationRule ?? '');
const [direction, setDirection] = useState<'vertical' | 'horizontal'>(
fieldState.direction ?? 'vertical',
);
const [direction, setDirection] = useState<'vertical' | 'horizontal'>(fieldState.direction ?? 'vertical');
const handleToggleChange = (field: keyof CheckboxFieldMeta, value: string | boolean) => {
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
const required = field === 'required' ? Boolean(value) : Boolean(fieldState.required);
const validationRule =
field === 'validationRule' ? String(value) : String(fieldState.validationRule);
const validationLength =
field === 'validationLength' ? Number(value) : Number(fieldState.validationLength);
const currentDirection =
field === 'direction' && String(value) === 'horizontal' ? 'horizontal' : 'vertical';
const validationRule = field === 'validationRule' ? String(value) : String(fieldState.validationRule);
const validationLength = field === 'validationLength' ? Number(value) : Number(fieldState.validationLength);
const currentDirection = field === 'direction' && String(value) === 'horizontal' ? 'horizontal' : 'vertical';
setReadOnly(readOnly);
setRequired(required);
@@ -102,7 +89,9 @@ export const CheckboxFieldAdvancedSettings = ({
}, [values]);
const removeValue = (index: number) => {
if (values.length === 1) return;
if (values.length === 1) {
return;
}
const newValues = [...values];
newValues.splice(index, 1);
@@ -110,11 +99,7 @@ export const CheckboxFieldAdvancedSettings = ({
handleFieldChange('values', newValues);
};
const handleCheckboxValue = (
index: number,
property: 'value' | 'checked',
newValue: string | boolean,
) => {
const handleCheckboxValue = (index: number, property: 'value' | 'checked', newValue: string | boolean) => {
const newValues = [...values];
if (property === 'checked') {
@@ -139,7 +124,7 @@ export const CheckboxFieldAdvancedSettings = ({
</Label>
<Input
id="label"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={_(msg`Field label`)}
value={fieldState.label}
onChange={(e) => handleFieldChange('label', e.target.value)}
@@ -154,7 +139,7 @@ export const CheckboxFieldAdvancedSettings = ({
value={fieldState.direction ?? 'vertical'}
onValueChange={(val) => handleToggleChange('direction', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<SelectTrigger className="mt-2 w-full bg-background text-muted-foreground">
<SelectValue placeholder={_(msg`Select direction`)} />
</SelectTrigger>
<SelectContent position="popper">
@@ -173,11 +158,8 @@ export const CheckboxFieldAdvancedSettings = ({
<Label>
<Trans>Validation</Trans>
</Label>
<Select
value={fieldState.validationRule}
onValueChange={(val) => handleToggleChange('validationRule', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<Select value={fieldState.validationRule} onValueChange={(val) => handleToggleChange('validationRule', val)}>
<SelectTrigger className="mt-2 w-full bg-background text-muted-foreground">
<SelectValue placeholder={_(msg`Select at least`)} />
</SelectTrigger>
<SelectContent position="popper">
@@ -194,7 +176,7 @@ export const CheckboxFieldAdvancedSettings = ({
value={fieldState.validationLength ? String(fieldState.validationLength) : ''}
onValueChange={(val) => handleToggleChange('validationLength', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<SelectTrigger className="mt-2 w-full bg-background text-muted-foreground">
<SelectValue placeholder={_(msg`Pick a number`)} />
</SelectTrigger>
<SelectContent position="popper">
@@ -230,7 +212,7 @@ export const CheckboxFieldAdvancedSettings = ({
</div>
</div>
<Button
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 mt-2 border"
className="mt-2 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={() => setShowValidation((prev) => !prev)}
>
@@ -247,7 +229,7 @@ export const CheckboxFieldAdvancedSettings = ({
{values.map((value, index) => (
<div key={index} className="mt-2 flex items-center gap-4">
<Checkbox
className="data-[state=checked]:bg-primary border-foreground/30 h-5 w-5"
className="h-5 w-5 border-foreground/30 data-[state=checked]:bg-primary"
checked={value.checked}
onCheckedChange={(checked) => handleCheckboxValue(index, 'checked', checked)}
/>
@@ -266,7 +248,7 @@ export const CheckboxFieldAdvancedSettings = ({
</div>
))}
<Button
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 ml-9 mt-4 border"
className="mt-4 ml-9 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={addValue}
>
@@ -1,16 +1,9 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateDateFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TDateFieldMeta as DateFieldMeta } from '@documenso/lib/types/field-meta';
import { type TDateFieldMeta as DateFieldMeta, DEFAULT_DATE_OVERFLOW_MODE } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Trans, useLingui } from '@lingui/react/macro';
type DateFieldAdvancedSettingsProps = {
fieldState: DateFieldMeta;
@@ -48,6 +41,7 @@ export const DateFieldAdvancedSettings = ({
const errors = validateDateFields({
fontSize,
overflow: fieldState.overflow ?? DEFAULT_DATE_OVERFLOW_MODE,
type: 'date',
});
@@ -64,7 +58,7 @@ export const DateFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -78,11 +72,8 @@ export const DateFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,30 +1,19 @@
import { useEffect, useState } from 'react';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
import type { TDropdownFieldMeta as DropdownFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
import { type TDropdownFieldMeta as DropdownFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { useEffect, useState } from 'react';
type DropdownFieldAdvancedSettingsProps = {
fieldState: DropdownFieldMeta;
handleFieldChange: (
key: keyof DropdownFieldMeta,
value: string | { value: string }[] | boolean,
) => void;
handleFieldChange: (key: keyof DropdownFieldMeta, value: string | { value: string }[] | boolean) => void;
handleErrors: (errors: string[]) => void;
};
@@ -47,7 +36,9 @@ export const DropdownFieldAdvancedSettings = ({
};
const removeValue = (index: number) => {
if (values.length === 1) return;
if (values.length === 1) {
return;
}
const newValues = [...values];
newValues.splice(index, 1);
@@ -98,7 +89,7 @@ export const DropdownFieldAdvancedSettings = ({
}, [fieldState.defaultValue]);
return (
<div className="text-dark flex flex-col gap-4">
<div className="flex flex-col gap-4 text-dark">
<div>
<Label>
<Trans>Select default option</Trans>
@@ -168,11 +159,7 @@ export const DropdownFieldAdvancedSettings = ({
<div>
{values.map((value, index) => (
<div key={index} className="mt-2 flex items-center gap-4">
<Input
className="w-1/2"
value={value.value}
onChange={(e) => handleValueChange(index, e.target.value)}
/>
<Input className="w-1/2" value={value.value} onChange={(e) => handleValueChange(index, e.target.value)} />
<button
type="button"
className="col-span-1 mt-auto inline-flex h-10 w-10 items-center text-slate-500 hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
@@ -183,7 +170,7 @@ export const DropdownFieldAdvancedSettings = ({
</div>
))}
<Button
className="ml-9 mt-4 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
className="mt-4 ml-9 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={addValue}
>
@@ -1,16 +1,9 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateEmailFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TEmailFieldMeta as EmailFieldMeta } from '@documenso/lib/types/field-meta';
import { DEFAULT_EMAIL_OVERFLOW_MODE, type TEmailFieldMeta as EmailFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Trans, useLingui } from '@lingui/react/macro';
type EmailFieldAdvancedSettingsProps = {
fieldState: EmailFieldMeta;
@@ -30,6 +23,7 @@ export const EmailFieldAdvancedSettings = ({
const errors = validateEmailFields({
fontSize,
overflow: fieldState.overflow ?? DEFAULT_EMAIL_OVERFLOW_MODE,
type: 'email',
});
@@ -46,7 +40,7 @@ export const EmailFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -60,11 +54,8 @@ export const EmailFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,9 +1,8 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateInitialsFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TInitialsFieldMeta as InitialsFieldMeta } from '@documenso/lib/types/field-meta';
import type { TInitialsFieldMeta as InitialsFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { Trans, useLingui } from '@lingui/react/macro';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../select';
@@ -41,7 +40,7 @@ export const InitialsFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -55,11 +54,8 @@ export const InitialsFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,16 +1,9 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateNameFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TNameFieldMeta as NameFieldMeta } from '@documenso/lib/types/field-meta';
import type { TNameFieldMeta as NameFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Trans, useLingui } from '@lingui/react/macro';
type NameFieldAdvancedSettingsProps = {
fieldState: NameFieldMeta;
@@ -46,7 +39,7 @@ export const NameFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -60,11 +53,8 @@ export const NameFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,21 +1,13 @@
import { useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
import { type TNumberFieldMeta as NumberFieldMeta } from '@documenso/lib/types/field-meta';
import type { TNumberFieldMeta as NumberFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { Trans, useLingui } from '@lingui/react/macro';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { useState } from 'react';
import { numberFormatValues } from './constants';
@@ -65,7 +57,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="label"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Label`}
value={fieldState.label}
onChange={(e) => handleFieldChange('label', e.target.value)}
@@ -77,7 +69,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="placeholder"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Placeholder`}
value={fieldState.placeholder}
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
@@ -89,7 +81,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="value"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Value`}
value={fieldState.value}
onChange={(e) => handleInput('value', e.target.value)}
@@ -99,11 +91,8 @@ export const NumberFieldAdvancedSettings = ({
<Label>
<Trans>Number format</Trans>
</Label>
<Select
value={fieldState.numberFormat ?? ''}
onValueChange={(val) => handleInput('numberFormat', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<Select value={fieldState.numberFormat ?? ''} onValueChange={(val) => handleInput('numberFormat', val)}>
<SelectTrigger className="mt-2 w-full bg-background text-muted-foreground">
<SelectValue placeholder={t`Field format`} />
</SelectTrigger>
<SelectContent position="popper">
@@ -123,7 +112,7 @@ export const NumberFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -137,11 +126,8 @@ export const NumberFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -176,7 +162,7 @@ export const NumberFieldAdvancedSettings = ({
</div>
</div>
<Button
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 mt-2 border"
className="mt-2 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={() => setShowValidation((prev) => !prev)}
>
@@ -195,7 +181,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="minValue"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`E.g. 0`}
value={fieldState.minValue ?? ''}
onChange={(e) => handleInput('minValue', e.target.value)}
@@ -207,7 +193,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="maxValue"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`E.g. 100`}
value={fieldState.maxValue ?? ''}
onChange={(e) => handleInput('maxValue', e.target.value)}
@@ -1,17 +1,15 @@
import { useEffect, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
import { type TRadioFieldMeta as RadioFieldMeta } from '@documenso/lib/types/field-meta';
import type { TRadioFieldMeta as RadioFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { Switch } from '@documenso/ui/primitives/switch';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { useEffect, useState } from 'react';
export type RadioFieldAdvancedSettingsProps = {
fieldState: RadioFieldMeta;
@@ -30,9 +28,7 @@ export const RadioFieldAdvancedSettings = ({
const { _ } = useLingui();
const [showValidation, setShowValidation] = useState(false);
const [values, setValues] = useState(
fieldState.values ?? [{ id: 1, checked: false, value: _(msg`Default value`) }],
);
const [values, setValues] = useState(fieldState.values ?? [{ id: 1, checked: false, value: _(msg`Default value`) }]);
const [readOnly, setReadOnly] = useState(fieldState.readOnly ?? false);
const [required, setRequired] = useState(fieldState.required ?? false);
@@ -46,7 +42,9 @@ export const RadioFieldAdvancedSettings = ({
};
const removeValue = (id: number) => {
if (values.length === 1) return;
if (values.length === 1) {
return;
}
const newValues = values.filter((val) => val.id !== id);
setValues(newValues);
@@ -186,7 +184,7 @@ export const RadioFieldAdvancedSettings = ({
</div>
))}
<Button
className="ml-9 mt-4 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
className="mt-4 ml-9 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={addValue}
>
@@ -1,18 +1,11 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
import { type TTextFieldMeta as TextFieldMeta } from '@documenso/lib/types/field-meta';
import type { TTextFieldMeta as TextFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { Textarea } from '@documenso/ui/primitives/textarea';
import { Trans, useLingui } from '@lingui/react/macro';
type TextFieldAdvancedSettingsProps = {
fieldState: TextFieldMeta;
@@ -29,8 +22,7 @@ export const TextFieldAdvancedSettings = ({
const handleInput = (field: keyof TextFieldMeta, value: string | boolean) => {
const text = field === 'text' ? String(value) : (fieldState.text ?? '');
const limit =
field === 'characterLimit' ? Number(value) : Number(fieldState.characterLimit ?? 0);
const limit = field === 'characterLimit' ? Number(value) : Number(fieldState.characterLimit ?? 0);
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
const required = field === 'required' ? Boolean(value) : Boolean(fieldState.required);
@@ -55,7 +47,7 @@ export const TextFieldAdvancedSettings = ({
</Label>
<Input
id="label"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field label`}
value={fieldState.label}
onChange={(e) => handleFieldChange('label', e.target.value)}
@@ -67,7 +59,7 @@ export const TextFieldAdvancedSettings = ({
</Label>
<Input
id="placeholder"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field placeholder`}
value={fieldState.placeholder}
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
@@ -80,7 +72,7 @@ export const TextFieldAdvancedSettings = ({
</Label>
<Textarea
id="text"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Add text to the field`}
value={fieldState.text}
onChange={(e) => handleInput('text', e.target.value)}
@@ -95,7 +87,7 @@ export const TextFieldAdvancedSettings = ({
id="characterLimit"
type="number"
min={0}
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field character limit`}
value={fieldState.characterLimit}
onChange={(e) => handleInput('characterLimit', e.target.value)}
@@ -109,7 +101,7 @@ export const TextFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -133,7 +125,7 @@ export const TextFieldAdvancedSettings = ({
handleInput('textAlign', value);
}}
>
<SelectTrigger className="bg-background mt-2">
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,6 +1,3 @@
import { Trans } from '@lingui/react/macro';
import { DialogClose } from '@radix-ui/react-dialog';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@@ -10,16 +7,15 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Trans } from '@lingui/react/macro';
import { DialogClose } from '@radix-ui/react-dialog';
export type MissingSignatureFieldDialogProps = {
isOpen: boolean;
onOpenChange: (value: boolean) => void;
};
export const MissingSignatureFieldDialog = ({
isOpen,
onOpenChange,
}: MissingSignatureFieldDialogProps) => {
export const MissingSignatureFieldDialog = ({ isOpen, onOpenChange }: MissingSignatureFieldDialogProps) => {
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg" position="center">
@@ -30,8 +26,8 @@ export const MissingSignatureFieldDialog = ({
<DialogDescription>
<p className="mt-2">
<Trans>
Some signers have not been assigned a signature field. Please assign at least 1
signature field to each signer before proceeding.
Some signers have not been assigned a signature field. Please assign at least 1 signature field to each
signer before proceeding.
</Trans>
</p>
</DialogDescription>
@@ -1,7 +1,6 @@
import { useState } from 'react';
import { Trans } from '@lingui/react/macro';
import { Loader } from 'lucide-react';
import { useState } from 'react';
import type { ButtonProps } from '../button';
import { Button } from '../button';
@@ -19,38 +18,31 @@ export type SendDocumentActionDialogProps = ButtonProps & {
loading?: boolean;
};
export const SendDocumentActionDialog = ({
loading,
className,
...props
}: SendDocumentActionDialogProps) => {
export const SendDocumentActionDialog = ({ loading, className, ...props }: SendDocumentActionDialogProps) => {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button type="button" className={className}>
{loading && <Loader className="text-documenso mr-2 h-5 w-5 animate-spin" />}
{loading && <Loader className="mr-2 h-5 w-5 animate-spin text-documenso" />}
<Trans>Send</Trans>
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="text-center text-lg font-semibold">
<DialogTitle className="text-center font-semibold text-lg">
<Trans>Send Document</Trans>
</DialogTitle>
<DialogDescription className="text-center text-base">
<Trans>
You are about to send this document to the recipients. Are you sure you want to
continue?
</Trans>
<Trans>You are about to send this document to the recipients. Are you sure you want to continue?</Trans>
</DialogDescription>
</DialogHeader>
<DialogFooter className="mt-4 flex items-center gap-x-4">
<Button
className="dark:bg-muted dark:hover:bg-muted/80 dark:focus-visible:ring-muted/80 flex-1 border-none bg-black/5 hover:bg-black/10 focus-visible:ring-black/10"
className="flex-1 border-none bg-black/5 hover:bg-black/10 focus-visible:ring-black/10 dark:bg-muted dark:focus-visible:ring-muted/80 dark:hover:bg-muted/80"
type="button"
variant="secondary"
onClick={() => setOpen(false)}
@@ -15,19 +15,15 @@ export type SigningOrderConfirmationProps = {
onConfirm: () => void;
};
export function SigningOrderConfirmation({
open,
onOpenChange,
onConfirm,
}: SigningOrderConfirmationProps) {
export function SigningOrderConfirmation({ open, onOpenChange, onConfirm }: SigningOrderConfirmationProps) {
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Warning</AlertDialogTitle>
<AlertDialogDescription>
You have an assistant role on the signers list, removing the signing order will change
the assistant role to signer.
You have an assistant role on the signers list, removing the signing order will change the assistant role to
signer.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
@@ -1,11 +1,10 @@
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { zEmail } from '@documenso/lib/utils/zod';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { FieldType } from '@prisma/client';
import { z } from 'zod';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { zEmail } from '@documenso/lib/utils/zod';
export const ZDocumentFlowFormSchema = z.object({
title: z.string().min(1),
@@ -1,3 +1,9 @@
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { ALLOWED_UPLOAD_MIME_TYPES } from '@documenso/lib/constants/upload';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
@@ -8,13 +14,6 @@ import type { DropEvent, FileRejection } from 'react-dropzone';
import { useDropzone } from 'react-dropzone';
import { Link } from 'react-router';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { ALLOWED_UPLOAD_MIME_TYPES } from '@documenso/lib/constants/upload';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import { Button } from './button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip';
@@ -67,10 +66,8 @@ export const DocumentUploadButton = ({
});
const heading = {
[EnvelopeType.DOCUMENT]:
internalVersion === '1' ? msg`Document (Legacy)` : msg`Upload Document`,
[EnvelopeType.TEMPLATE]:
internalVersion === '1' ? msg`Template (Legacy)` : msg`Upload Template`,
[EnvelopeType.DOCUMENT]: internalVersion === '1' ? msg`Document (Legacy)` : msg`Upload Document`,
[EnvelopeType.TEMPLATE]: internalVersion === '1' ? msg`Template (Legacy)` : msg`Upload Template`,
};
if (disabled && IS_BILLING_ENABLED()) {
@@ -79,13 +76,7 @@ export const DocumentUploadButton = ({
<Tooltip>
<TooltipTrigger asChild>
<Button className="bg-warning hover:bg-warning/80" asChild>
<Link
to={
isPersonalLayoutMode
? `/settings/billing`
: `/o/${organisation.url}/settings/billing`
}
>
<Link to={isPersonalLayoutMode ? `/settings/billing` : `/o/${organisation.url}/settings/billing`}>
<Trans>Upgrade</Trans>
</Link>
</Button>
+17 -24
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -26,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'focus:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
inset && 'pl-8',
className,
)}
@@ -46,7 +45,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'bg-popover text-popover-foreground animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -64,7 +63,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -83,7 +82,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
@@ -100,7 +99,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
@@ -124,7 +123,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -148,7 +147,7 @@ const DropdownMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
className={cn('px-2 py-1.5 font-semibold text-sm', inset && 'pl-8', className)}
{...props}
/>
));
@@ -159,37 +158,31 @@ const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn('bg-muted -mx-1 my-1 h-px', className)}
{...props}
/>
<DropdownMenuPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />
);
return <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />;
};
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
DropdownMenuTrigger,
};
+2 -1
View File
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import type React from 'react';
import { useEffect, useState } from 'react';
export type ElementVisibleProps = {
target: string;
+5 -18
View File
@@ -1,16 +1,6 @@
import { Trans } from '@lingui/react/macro';
import { FieldType } from '@prisma/client';
import {
CalendarDays,
CheckSquare,
ChevronDown,
Contact,
Disc,
Hash,
Mail,
Type,
User,
} from 'lucide-react';
import { CalendarDays, CheckSquare, ChevronDown, Contact, Disc, Hash, Mail, Type, User } from 'lucide-react';
import { cn } from '../lib/utils';
import { Card, CardContent } from './card';
@@ -96,18 +86,15 @@ export const FieldSelector = ({
data-selected={selectedField === field.type ? true : undefined}
>
<Card
className={cn(
'flex w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
{
'border-primary': selectedField === field.type,
},
)}
className={cn('flex w-full cursor-pointer items-center justify-center group-disabled:opacity-50', {
'border-primary': selectedField === field.type,
})}
>
<CardContent className="relative flex items-center justify-center gap-x-2 px-6 py-4">
{Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
<span
className={cn(
'text-sm text-muted-foreground group-data-[selected]:text-foreground',
'text-muted-foreground text-sm group-data-[selected]:text-foreground',
field.type === FieldType.SIGNATURE && 'invisible',
)}
>
@@ -38,7 +38,7 @@ export const FormErrorMessage = ({ error, className }: FormErrorMessageProps) =>
opacity: 0,
y: 10,
}}
className={cn('text-xs text-red-500', className)}
className={cn('text-red-500 text-xs', className)}
>
{errorMessage}
</motion.p>
+57 -82
View File
@@ -1,9 +1,8 @@
import * as React from 'react';
import { useLingui } from '@lingui/react';
import type * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import { AnimatePresence, motion } from 'framer-motion';
import * as React from 'react';
import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
import { Controller, FormProvider, useFormContext } from 'react-hook-form';
@@ -98,97 +97,73 @@ const FormLabel = React.forwardRef<
});
FormLabel.displayName = 'FormLabel';
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
);
});
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
);
},
);
FormControl.displayName = 'FormControl';
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
});
return <p ref={ref} id={formDescriptionId} className={cn('text-muted-foreground text-sm', className)} {...props} />;
},
);
FormDescription.displayName = 'FormDescription';
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { i18n } = useLingui();
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, children, ...props }, ref) => {
const { i18n } = useLingui();
const { error, formMessageId } = useFormField();
const { error, formMessageId } = useFormField();
let body = error ? String(error?.message) : children;
let body = error ? String(error?.message) : children;
if (!body) {
return null;
}
if (!body) {
return null;
}
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
if (typeof body === 'string' && i18n.t(body)) {
body = i18n.t(body);
}
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
if (typeof body === 'string' && i18n.t(body)) {
body = i18n.t(body);
}
return (
<AnimatePresence>
<motion.div
initial={{
opacity: 0,
y: -10,
}}
animate={{
opacity: 1,
y: 0,
}}
exit={{
opacity: 0,
y: 10,
}}
>
<p
ref={ref}
id={formMessageId}
className={cn('text-xs text-red-500', className)}
{...props}
return (
<AnimatePresence>
<motion.div
initial={{
opacity: 0,
y: -10,
}}
animate={{
opacity: 1,
y: 0,
}}
exit={{
opacity: 0,
y: 10,
}}
>
{body}
</p>
</motion.div>
</AnimatePresence>
);
});
<p ref={ref} id={formMessageId} className={cn('text-red-500 text-xs', className)} {...props}>
{body}
</p>
</motion.div>
</AnimatePresence>
);
},
);
FormMessage.displayName = 'FormMessage';
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
+3 -4
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -17,7 +16,7 @@ const HoverCardContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground animate-in zoom-in-90 z-50 w-64 rounded-md border p-4 shadow-md outline-none',
'zoom-in-90 z-50 w-64 animate-in rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none',
className,
)}
{...props}
@@ -26,4 +25,4 @@ const HoverCardContent = React.forwardRef<
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent };
export { HoverCard, HoverCardContent, HoverCardTrigger };
+16 -18
View File
@@ -4,24 +4,22 @@ import { cn } from '../lib/utils';
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'bg-background border-input ring-offset-background placeholder:text-muted-foreground/40 focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-base file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className,
{
'ring-2 !ring-red-500 transition-all': props['aria-invalid'],
},
)}
ref={ref}
{...props}
/>
);
},
);
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className,
{
'!ring-red-500 ring-2 transition-all': props['aria-invalid'],
},
)}
ref={ref}
{...props}
/>
);
});
Input.displayName = 'Input';
+3 -6
View File
@@ -1,14 +1,11 @@
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '../lib/utils';
const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
);
const labelVariants = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70');
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
@@ -17,7 +14,7 @@ const Label = React.forwardRef<
>(({ className, children, required, ...props }, ref) => (
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props}>
{children}
{required && <span className="text-destructive ml-1 inline-block font-medium">*</span>}
{required && <span className="ml-1 inline-block font-medium text-destructive">*</span>}
</LabelPrimitive.Root>
));
+22 -35
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as MenubarPrimitive from '@radix-ui/react-menubar';
import { Check, ChevronRight, Circle } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -21,10 +20,7 @@ const Menubar = React.forwardRef<
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn(
'bg-background flex h-10 items-center space-x-1 rounded-md border p-1',
className,
)}
className={cn('flex h-10 items-center space-x-1 rounded-md border bg-background p-1', className)}
{...props}
/>
));
@@ -38,7 +34,7 @@ const MenubarTrigger = React.forwardRef<
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none',
'flex cursor-default select-none items-center rounded-sm px-3 py-1.5 font-medium text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
className,
)}
{...props}
@@ -56,7 +52,7 @@ const MenubarSubTrigger = React.forwardRef<
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
className,
)}
@@ -76,7 +72,7 @@ const MenubarSubContent = React.forwardRef<
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
'bg-popover text-popover-foreground animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -96,7 +92,7 @@ const MenubarContent = React.forwardRef<
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground animate-in slide-in-from-top-1 z-50 min-w-[12rem] overflow-hidden rounded-md border p-1 shadow-md',
'slide-in-from-top-1 z-50 min-w-[12rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -115,7 +111,7 @@ const MenubarItem = React.forwardRef<
<MenubarPrimitive.Item
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
@@ -132,7 +128,7 @@ const MenubarCheckboxItem = React.forwardRef<
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
@@ -156,7 +152,7 @@ const MenubarRadioItem = React.forwardRef<
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -180,7 +176,7 @@ const MenubarLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
className={cn('px-2 py-1.5 font-semibold text-sm', inset && 'pl-8', className)}
{...props}
/>
));
@@ -191,41 +187,32 @@ const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn('bg-muted -mx-1 my-1 h-px', className)}
{...props}
/>
<MenubarPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
));
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
const MenubarShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
return <span className={cn('ml-auto text-muted-foreground text-xs tracking-widest', className)} {...props} />;
};
MenubarShortcut.displayname = 'MenubarShortcut';
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarPortal,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
MenubarTrigger,
};
@@ -1,4 +1,4 @@
import * as React from 'react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import type { MessageDescriptor } from '@lingui/core';
import { t } from '@lingui/core/macro';
@@ -6,8 +6,7 @@ import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { AnimatePresence } from 'framer-motion';
import { Check, ChevronsUpDown, Loader, XIcon } from 'lucide-react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import * as React from 'react';
import { cn } from '../lib/utils';
import { Button } from './button';
@@ -141,12 +140,12 @@ export function MultiSelectCombobox<T = OptionValue>({
{/* This is placed outside the trigger since we can't have nested buttons. */}
{showClearButton && !loading && (
<div className="absolute bottom-0 right-8 top-0 flex items-center justify-center">
<div className="absolute top-0 right-8 bottom-0 flex items-center justify-center">
<button
className="flex h-4 w-4 items-center justify-center rounded-full bg-gray-300 dark:bg-neutral-700"
onClick={() => onChange([])}
>
<XIcon className="text-muted-foreground h-3.5 w-3.5" />
<XIcon className="h-3.5 w-3.5 text-muted-foreground" />
</button>
</div>
)}
@@ -162,10 +161,7 @@ export function MultiSelectCombobox<T = OptionValue>({
{options.map((option, i) => (
<CommandItem key={i} onSelect={() => handleSelect(option.value)}>
<Check
className={cn(
'mr-2 h-4 w-4',
selectedValues.includes(option.value) ? 'opacity-100' : 'opacity-0',
)}
className={cn('mr-2 h-4 w-4', selectedValues.includes(option.value) ? 'opacity-100' : 'opacity-0')}
/>
{option.label}
</CommandItem>
+62 -69
View File
@@ -1,8 +1,7 @@
import * as React from 'react';
import { useEffect } from 'react';
import { Command as CommandPrimitive, useCommandState } from 'cmdk';
import { XIcon } from 'lucide-react';
import * as React from 'react';
import { useEffect } from 'react';
import { useDebounce } from '../lib/use-debounce';
import { cn } from '../lib/utils';
@@ -126,21 +125,15 @@ function isOptionsExist(groupOption: GroupOption, targetOption: Option[]) {
return false;
}
const CommandEmpty = ({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) => {
const CommandEmpty = ({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) => {
const render = useCommandState((state) => state.filtered.count === 0);
if (!render) return null;
if (!render) {
return null;
}
return (
<div
className={cn('px-2 py-4 text-center text-sm', className)}
cmdk-empty=""
role="presentation"
{...props}
/>
<div className={cn('px-2 py-4 text-center text-sm', className)} cmdk-empty="" role="presentation" {...props} />
);
};
@@ -179,9 +172,7 @@ const MultiSelect = ({
const dropdownRef = React.useRef<HTMLDivElement>(null); // Added this
const [selected, setSelected] = React.useState<Option[]>(value || []);
const [options, setOptions] = React.useState<GroupOption>(
transToGroupOption(arrayDefaultOptions, groupBy),
);
const [options, setOptions] = React.useState<GroupOption>(transToGroupOption(arrayDefaultOptions, groupBy));
const [inputValue, setInputValue] = React.useState('');
const debouncedSearchTerm = useDebounce(inputValue, delay || 500);
@@ -270,7 +261,9 @@ const MultiSelect = ({
// eslint-disable-next-line @typescript-eslint/require-await
const exec = async () => {
if (!onSearchSync || !open) return;
if (!onSearchSync || !open) {
return;
}
if (triggerSearchOnFocus) {
doSearchSync();
@@ -296,7 +289,9 @@ const MultiSelect = ({
};
const exec = async () => {
if (!onSearch || !open) return;
if (!onSearch || !open) {
return;
}
if (triggerSearchOnFocus) {
await doSearch();
@@ -312,7 +307,9 @@ const MultiSelect = ({
}, [debouncedSearchTerm, groupBy, open, triggerSearchOnFocus]);
const CreatableItem = () => {
if (!creatable) return undefined;
if (!creatable) {
return undefined;
}
if (
isOptionsExist(options, [{ value: inputValue, label: inputValue }]) ||
selected.find((s) => s.value === inputValue)
@@ -357,7 +354,9 @@ const MultiSelect = ({
};
const EmptyItem = React.useCallback(() => {
if (!emptyIndicator) return undefined;
if (!emptyIndicator) {
return undefined;
}
// For async search that showing emptyIndicator
if (onSearch && !creatable && Object.keys(options).length === 0) {
@@ -371,10 +370,7 @@ const MultiSelect = ({
return <CommandEmpty>{emptyIndicator}</CommandEmpty>;
}, [creatable, emptyIndicator, onSearch, options]);
const selectables = React.useMemo<GroupOption>(
() => removePickedOption(options, selected),
[options, selected],
);
const selectables = React.useMemo<GroupOption>(() => removePickedOption(options, selected), [options, selected]);
/** Avoid Creatable Selector freezing or lagging when paste a long string. */
const commandFilter = React.useCallback(() => {
@@ -400,15 +396,13 @@ const MultiSelect = ({
commandProps?.onKeyDown?.(e);
}}
className={cn('h-auto overflow-visible bg-transparent', commandProps?.className)}
shouldFilter={
commandProps?.shouldFilter !== undefined ? commandProps.shouldFilter : !onSearch
} // When onSearch is provided, we don&lsquo;t want to filter the options. You can still override it.
shouldFilter={commandProps?.shouldFilter !== undefined ? commandProps.shouldFilter : !onSearch} // When onSearch is provided, we don&lsquo;t want to filter the options. You can still override it.
filter={commandFilter()}
data-testid={dataTestId}
>
<div
className={cn(
'border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 relative min-h-[38px] rounded-md border text-sm outline-none transition-[color,box-shadow] focus-within:ring-[3px]',
'relative min-h-[38px] rounded-md border border-input text-sm outline-none transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-aria-invalid:border-destructive has-disabled:opacity-50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40',
{
'p-1': selected.length !== 0,
'cursor-text': !disabled && selected.length !== 0,
@@ -417,7 +411,9 @@ const MultiSelect = ({
className,
)}
onClick={() => {
if (disabled) return;
if (disabled) {
return;
}
inputRef?.current?.focus();
}}
>
@@ -427,7 +423,7 @@ const MultiSelect = ({
<div
key={option.value}
className={cn(
'animate-fadeIn bg-background text-secondary-foreground hover:bg-background data-fixed:pe-2 relative inline-flex h-7 cursor-default items-center rounded-md border pe-7 pl-2 ps-2 text-xs font-medium transition-all disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
'relative inline-flex h-7 animate-fadeIn cursor-default items-center rounded-md border bg-background ps-2 pe-7 pl-2 font-medium text-secondary-foreground text-xs transition-all hover:bg-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 data-fixed:pe-2',
badgeClassName,
)}
data-fixed={option.fixed}
@@ -435,7 +431,7 @@ const MultiSelect = ({
>
{option.label}
<button
className="text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute -inset-y-px -end-px flex size-7 items-center justify-center rounded-e-md border border-transparent p-0 outline-none transition-[color,box-shadow] focus-visible:ring-[3px]"
className="absolute -inset-y-px -end-px flex size-7 items-center justify-center rounded-e-md border border-transparent p-0 text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleUnselect(option);
@@ -478,7 +474,7 @@ const MultiSelect = ({
}}
placeholder={hidePlaceholderWhenSelected && selected.length !== 0 ? '' : placeholder}
className={cn(
'placeholder:text-muted-foreground/70 flex-1 bg-transparent outline-none disabled:cursor-not-allowed',
'flex-1 bg-transparent outline-none placeholder:text-muted-foreground/70 disabled:cursor-not-allowed',
{
'w-full': hidePlaceholderWhenSelected,
'px-3 py-2': selected.length === 0,
@@ -494,7 +490,7 @@ const MultiSelect = ({
onChange?.(selected.filter((s) => s.fixed));
}}
className={cn(
'text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute end-0 top-0 flex size-9 items-center justify-center rounded-md border border-transparent outline-none transition-[color,box-shadow] focus-visible:ring-[3px]',
'absolute end-0 top-0 flex size-9 items-center justify-center rounded-md border border-transparent text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
(hideClearAllButton ||
disabled ||
selected.length < 1 ||
@@ -510,8 +506,8 @@ const MultiSelect = ({
<div className="relative">
<div
className={cn(
'border-input absolute top-2 z-10 w-full overflow-hidden rounded-md border',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
'absolute top-2 z-10 w-full overflow-hidden rounded-md border border-input',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=open]:animate-in',
!open && 'hidden',
)}
data-state={open ? 'open' : 'closed'}
@@ -530,7 +526,7 @@ const MultiSelect = ({
}}
>
{isLoading ? (
<>{loadingIndicator}</>
loadingIndicator
) : (
<>
{EmptyItem()}
@@ -538,38 +534,35 @@ const MultiSelect = ({
{!selectFirstItem && <CommandItem value="-" className="hidden" />}
{Object.entries(selectables).map(([key, dropdowns]) => (
<CommandGroup key={key} heading={key} className="h-full overflow-auto">
<>
{dropdowns.map((option) => {
return (
<CommandItem
key={option.value}
value={option.value}
disabled={option.disable}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onSelect={() => {
if (selected.length >= maxSelected) {
onMaxSelected?.(selected.length);
return;
}
setInputValue('');
const newOptions = [...selected, option];
setSelected(newOptions);
onChange?.(newOptions);
}}
className={cn(
'cursor-pointer',
option.disable &&
'pointer-events-none cursor-not-allowed opacity-50',
)}
>
{option.label}
</CommandItem>
);
})}
</>
{dropdowns.map((option) => {
return (
<CommandItem
key={option.value}
value={option.value}
disabled={option.disable}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onSelect={() => {
if (selected.length >= maxSelected) {
onMaxSelected?.(selected.length);
return;
}
setInputValue('');
const newOptions = [...selected, option];
setSelected(newOptions);
onChange?.(newOptions);
}}
className={cn(
'cursor-pointer',
option.disable && 'pointer-events-none cursor-not-allowed opacity-50',
)}
>
{option.label}
</CommandItem>
);
})}
</CommandGroup>
))}
</>
+11 -12
View File
@@ -1,8 +1,7 @@
import * as React from 'react';
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import { cva } from 'class-variance-authority';
import { ChevronDown } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -67,7 +66,7 @@ const NavigationMenuContent = React.forwardRef<
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
'data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 left-0 top-0 w-full md:absolute md:w-auto',
'data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out md:absolute md:w-auto',
className,
)}
{...props}
@@ -82,10 +81,10 @@ const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn('absolute left-0 top-full flex justify-center')}>
<div className={cn('absolute top-full left-0 flex justify-center')}>
<NavigationMenuPrimitive.Viewport
className={cn(
'origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow-lg md:w-[var(--radix-navigation-menu-viewport-width)]',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full origin-top-center overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=closed]:animate-out data-[state=open]:animate-in md:w-[var(--radix-navigation-menu-viewport-width)]',
className,
)}
ref={ref}
@@ -103,25 +102,25 @@ const NavigationMenuIndicator = React.forwardRef<
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
'data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden',
'data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=visible]:animate-in',
className,
)}
{...props}
>
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
));
NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
NavigationMenuViewport,
navigationMenuTriggerStyle,
};
+22 -30
View File
@@ -1,42 +1,34 @@
import * as React from 'react';
import { Eye, EyeOff } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
import { Button } from './button';
import type { InputProps } from './input';
import { Input } from './input';
const PasswordInput = React.forwardRef<HTMLInputElement, Omit<InputProps, 'type'>>(
({ className, ...props }, ref) => {
const [showPassword, setShowPassword] = React.useState(false);
const PasswordInput = React.forwardRef<HTMLInputElement, Omit<InputProps, 'type'>>(({ className, ...props }, ref) => {
const [showPassword, setShowPassword] = React.useState(false);
return (
<div className="relative">
<Input
type={showPassword ? 'text' : 'password'}
className={cn('pr-10', className)}
ref={ref}
{...props}
/>
return (
<div className="relative">
<Input type={showPassword ? 'text' : 'password'} className={cn('pr-10', className)} ref={ref} {...props} />
<Button
variant="link"
type="button"
className="absolute right-0 top-0 flex h-full items-center justify-center pr-3"
aria-label={showPassword ? 'Mask password' : 'Reveal password'}
onClick={() => setShowPassword((show) => !show)}
>
{showPassword ? (
<EyeOff aria-hidden className="text-muted-foreground h-5 w-5" />
) : (
<Eye aria-hidden className="text-muted-foreground h-5 w-5" />
)}
</Button>
</div>
);
},
);
<Button
variant="link"
type="button"
className="absolute top-0 right-0 flex h-full items-center justify-center pr-3"
aria-label={showPassword ? 'Mask password' : 'Reveal password'}
onClick={() => setShowPassword((show) => !show)}
>
{showPassword ? (
<EyeOff aria-hidden className="h-5 w-5 text-muted-foreground" />
) : (
<Eye aria-hidden className="h-5 w-5 text-muted-foreground" />
)}
</Button>
</div>
);
});
PasswordInput.displayName = 'PasswordInput';
+25 -34
View File
@@ -1,33 +1,25 @@
import * as React from 'react';
import { OTPInput, OTPInputContext } from 'input-otp';
import { Minus } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
const PinInput = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
'flex items-center gap-2 has-[:disabled]:opacity-50',
containerClassName,
)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
));
const PinInput = React.forwardRef<React.ElementRef<typeof OTPInput>, React.ComponentPropsWithoutRef<typeof OTPInput>>(
({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn('flex items-center gap-2 has-[:disabled]:opacity-50', containerClassName)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
),
);
PinInput.displayName = 'PinInput';
const PinInputGroup = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center', className)} {...props} />
));
const PinInputGroup = React.forwardRef<React.ElementRef<'div'>, React.ComponentPropsWithoutRef<'div'>>(
({ className, ...props }, ref) => <div ref={ref} className={cn('flex items-center', className)} {...props} />,
);
PinInputGroup.displayName = 'PinInputGroup';
@@ -42,8 +34,8 @@ const PinInputSlot = React.forwardRef<
<div
ref={ref}
className={cn(
'border-input relative flex h-10 w-10 items-center justify-center border-y border-r font-mono shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
isActive && 'ring-ring z-10 ring-1',
'relative flex h-10 w-10 items-center justify-center border-input border-y border-r font-mono shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
isActive && 'z-10 ring-1 ring-ring',
className,
)}
{...props}
@@ -51,7 +43,7 @@ const PinInputSlot = React.forwardRef<
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div>
)}
</div>
@@ -60,15 +52,14 @@ const PinInputSlot = React.forwardRef<
PinInputSlot.displayName = 'PinInputSlot';
const PinInputSeparator = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Minus className="h-5 w-5" />
</div>
));
const PinInputSeparator = React.forwardRef<React.ElementRef<'div'>, React.ComponentPropsWithoutRef<'div'>>(
({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Minus className="h-5 w-5" />
</div>
),
);
PinInputSeparator.displayName = 'PinInputSeparator';
export { PinInput, PinInputGroup, PinInputSlot, PinInputSeparator };
export { PinInput, PinInputGroup, PinInputSeparator, PinInputSlot };
+4 -10
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -20,7 +19,7 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none',
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 animate-in rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none',
className,
)}
{...props}
@@ -81,16 +80,11 @@ const PopoverHover = ({ trigger, children, contentProps, side = 'top' }: Popover
{trigger}
</PopoverTrigger>
<PopoverContent
side={side}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
{...contentProps}
>
<PopoverContent side={side} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...contentProps}>
{children}
</PopoverContent>
</Popover>
);
};
export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent, PopoverHover };
export { Popover, PopoverAnchor, PopoverContent, PopoverHover, PopoverTrigger };
+3 -4
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as ProgressPrimitive from '@radix-ui/react-progress';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -10,11 +9,11 @@ const Progress = React.forwardRef<
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn('bg-secondary relative h-4 w-full overflow-hidden rounded-full', className)}
className={cn('relative h-4 w-full overflow-hidden rounded-full bg-secondary', className)}
{...props}
>
<ProgressPrimitive.Indicator
className="bg-primary h-full w-full flex-1 transition-all"
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
+3 -4
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import { Circle } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -22,13 +21,13 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
'border-primary text-primary focus-visible:ring-ring aspect-square h-4 w-4 rounded-full border shadow focus:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',
'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="fill-primary h-2.5 w-2.5" />
<Circle className="h-2.5 w-2.5 fill-primary" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
+12 -28
View File
@@ -1,14 +1,12 @@
import { useCallback, useMemo, useState } from 'react';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
import { Check, ChevronsUpDown, Info } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react';
import { sortBy } from 'remeda';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { getRecipientColorStyles } from '../lib/recipient-colors';
import { cn } from '../lib/utils';
import { Button } from './button';
@@ -53,20 +51,13 @@ export const RecipientSelector = ({
const recipientsByRoleToDisplay = useMemo(() => {
return Object.entries(recipientsByRole)
.filter(
([role]) =>
role !== RecipientRole.CC &&
role !== RecipientRole.VIEWER &&
role !== RecipientRole.ASSISTANT,
([role]) => role !== RecipientRole.CC && role !== RecipientRole.VIEWER && role !== RecipientRole.ASSISTANT,
)
.map(
([role, roleRecipients]) =>
[
role,
sortBy(
roleRecipients,
[(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'],
[(r) => r.id, 'asc'],
),
sortBy(roleRecipients, [(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'], [(r) => r.id, 'asc']),
] as [RecipientRole, TRecipientLite[]],
);
}, [recipientsByRole]);
@@ -102,15 +93,12 @@ export const RecipientSelector = ({
role="combobox"
className={cn(
'justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
getRecipientColorStyles(recipients.findIndex((r) => r.id === selectedRecipient?.id))
.comboBoxTrigger,
getRecipientColorStyles(recipients.findIndex((r) => r.id === selectedRecipient?.id)).comboBoxTrigger,
className,
)}
>
{selectedRecipient && (
<span className="flex-1 truncate text-left">
{getRecipientLabel(selectedRecipient)}
</span>
<span className="flex-1 truncate text-left">{getRecipientLabel(selectedRecipient)}</span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4" />
@@ -129,15 +117,12 @@ export const RecipientSelector = ({
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
<CommandGroup key={roleIndex}>
<div className="mb-1 ml-2 mt-2 text-xs font-medium text-muted-foreground">
<div className="mt-2 mb-1 ml-2 font-medium text-muted-foreground text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
</div>
{roleRecipients.length === 0 && (
<div
key={`${role}-empty`}
className="px-4 pb-4 pt-2.5 text-center text-xs text-muted-foreground/80"
>
<div key={`${role}-empty`} className="px-4 pt-2.5 pb-4 text-center text-muted-foreground/80 text-xs">
<Trans>No recipients with this role</Trans>
</div>
)}
@@ -147,8 +132,7 @@ export const RecipientSelector = ({
key={recipient.id}
className={cn(
'px-2 last:mb-1 [&:not(:first-child)]:mt-1',
getRecipientColorStyles(recipients.findIndex((r) => r.id === recipient.id))
.comboBoxItem,
getRecipientColorStyles(recipients.findIndex((r) => r.id === recipient.id)).comboBoxItem,
{
'text-muted-foreground': recipient.sendStatus === SendStatus.SENT,
},
@@ -184,8 +168,8 @@ export const RecipientSelector = ({
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
This document has already been sent to this recipient. You can no longer
edit this recipient.
This document has already been sent to this recipient. You can no longer edit this
recipient.
</Trans>
</TooltipContent>
</Tooltip>
+4 -11
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -8,14 +7,8 @@ const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn('relative overflow-hidden', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollAreaPrimitive.Root ref={ref} className={cn('relative overflow-hidden', className)} {...props}>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">{children}</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
@@ -38,7 +31,7 @@ const ScrollBar = React.forwardRef<
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="bg-border relative flex-1 rounded-full" />
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
+7 -25
View File
@@ -1,10 +1,9 @@
import * as React from 'react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import * as SelectPrimitive from '@radix-ui/react-select';
import { AnimatePresence } from 'framer-motion';
import { Check, ChevronDown, Loader } from 'lucide-react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -56,7 +55,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-[1001] min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md animate-in fade-in-80',
'fade-in-80 relative z-[1001] min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md',
position === 'popper' && 'translate-y-1',
className,
)}
@@ -82,11 +81,7 @@ const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
{...props}
/>
<SelectPrimitive.Label ref={ref} className={cn('py-1.5 pr-2 pl-8 font-semibold text-sm', className)} {...props} />
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
@@ -98,7 +93,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -119,22 +114,9 @@ const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
<SelectPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
};
export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue };
+2 -7
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -12,11 +11,7 @@ const Separator = React.forwardRef<
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
'bg-border shrink-0',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className,
)}
className={cn('shrink-0 bg-border', orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]', className)}
{...props}
/>
));
+104 -134
View File
@@ -1,10 +1,9 @@
import * as React from 'react';
import { Trans } from '@lingui/react/macro';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import { X } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -24,9 +23,7 @@ const portalVariants = cva('fixed inset-0 z-[61] flex', {
defaultVariants: { position: 'right' },
});
interface SheetPortalProps
extends SheetPrimitive.DialogPortalProps,
VariantProps<typeof portalVariants> {}
interface SheetPortalProps extends SheetPrimitive.DialogPortalProps, VariantProps<typeof portalVariants> {}
const SheetPortal = ({ position, children, ...props }: SheetPortalProps) => (
<SheetPrimitive.Portal {...props}>
@@ -42,7 +39,7 @@ const SheetOverlay = React.forwardRef<
>(({ className, children: _children, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
'fixed inset-0 z-[61] bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in',
'data-[state=closed]:fade-out data-[state=open]:fade-in fixed inset-0 z-[61] bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out',
className,
)}
{...props}
@@ -52,93 +49,90 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
'fixed z-[61] scale-100 gap-4 bg-background p-6 opacity-100 shadow-lg border',
{
variants: {
position: {
top: 'animate-in slide-in-from-top w-full duration-300',
bottom: 'animate-in slide-in-from-bottom w-full duration-300',
left: 'animate-in slide-in-from-left h-full duration-300',
right: 'animate-in slide-in-from-right h-full duration-300',
},
size: {
content: '',
default: '',
sm: '',
lg: '',
xl: '',
full: '',
},
const sheetVariants = cva('fixed z-[61] scale-100 gap-4 bg-background p-6 opacity-100 shadow-lg border', {
variants: {
position: {
top: 'animate-in slide-in-from-top w-full duration-300',
bottom: 'animate-in slide-in-from-bottom w-full duration-300',
left: 'animate-in slide-in-from-left h-full duration-300',
right: 'animate-in slide-in-from-right h-full duration-300',
},
compoundVariants: [
{
position: ['top', 'bottom'],
size: 'content',
class: 'max-h-screen',
},
{
position: ['top', 'bottom'],
size: 'default',
class: 'h-1/3',
},
{
position: ['top', 'bottom'],
size: 'sm',
class: 'h-1/4',
},
{
position: ['top', 'bottom'],
size: 'lg',
class: 'h-1/2',
},
{
position: ['top', 'bottom'],
size: 'xl',
class: 'h-5/6',
},
{
position: ['top', 'bottom'],
size: 'full',
class: 'h-screen',
},
{
position: ['right', 'left'],
size: 'content',
class: 'max-w-screen',
},
{
position: ['right', 'left'],
size: 'default',
class: 'w-1/3',
},
{
position: ['right', 'left'],
size: 'sm',
class: 'w-1/4',
},
{
position: ['right', 'left'],
size: 'lg',
class: 'w-1/2',
},
{
position: ['right', 'left'],
size: 'xl',
class: 'w-5/6',
},
{
position: ['right', 'left'],
size: 'full',
class: 'w-screen',
},
],
defaultVariants: {
position: 'right',
size: 'default',
size: {
content: '',
default: '',
sm: '',
lg: '',
xl: '',
full: '',
},
},
);
compoundVariants: [
{
position: ['top', 'bottom'],
size: 'content',
class: 'max-h-screen',
},
{
position: ['top', 'bottom'],
size: 'default',
class: 'h-1/3',
},
{
position: ['top', 'bottom'],
size: 'sm',
class: 'h-1/4',
},
{
position: ['top', 'bottom'],
size: 'lg',
class: 'h-1/2',
},
{
position: ['top', 'bottom'],
size: 'xl',
class: 'h-5/6',
},
{
position: ['top', 'bottom'],
size: 'full',
class: 'h-screen',
},
{
position: ['right', 'left'],
size: 'content',
class: 'max-w-screen',
},
{
position: ['right', 'left'],
size: 'default',
class: 'w-1/3',
},
{
position: ['right', 'left'],
size: 'sm',
class: 'w-1/4',
},
{
position: ['right', 'left'],
size: 'lg',
class: 'w-1/2',
},
{
position: ['right', 'left'],
size: 'xl',
class: 'w-5/6',
},
{
position: ['right', 'left'],
size: 'full',
class: 'w-screen',
},
],
defaultVariants: {
position: 'right',
size: 'default',
},
});
export interface DialogContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
@@ -147,27 +141,22 @@ export interface DialogContentProps
sheetClass?: string;
}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
DialogContentProps
>(({ position, size, className, sheetClass, showOverlay = true, children, ...props }, ref) => (
<SheetPortal position={position}>
{showOverlay && <SheetOverlay className={sheetClass} />}
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ position, size }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">
<Trans>Close</Trans>
</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Content>, DialogContentProps>(
({ position, size, className, sheetClass, showOverlay = true, children, ...props }, ref) => (
<SheetPortal position={position}>
{showOverlay && <SheetOverlay className={sheetClass} />}
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ position, size }), className)} {...props}>
{children}
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">
<Trans>Close</Trans>
</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
),
);
SheetContent.displayName = SheetPrimitive.Content.displayName;
@@ -178,10 +167,7 @@ const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElemen
SheetHeader.displayName = 'SheetHeader';
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
);
SheetFooter.displayName = 'SheetFooter';
@@ -190,11 +176,7 @@ const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold text-foreground', className)}
{...props}
/>
<SheetPrimitive.Title ref={ref} className={cn('font-semibold text-foreground text-lg', className)} {...props} />
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
@@ -203,21 +185,9 @@ const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
<SheetPrimitive.Description ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetTrigger,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};
export { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger };
@@ -194,12 +194,9 @@ export class Canvas {
const smoothedPoints = this.smoothSignature(this.points);
let velocity = point.velocityFrom(lastPoint);
velocity =
this.VELOCITY_FILTER_WEIGHT * velocity +
(1 - this.VELOCITY_FILTER_WEIGHT) * this.lastVelocity;
velocity = this.VELOCITY_FILTER_WEIGHT * velocity + (1 - this.VELOCITY_FILTER_WEIGHT) * this.lastVelocity;
const newWidth =
velocity > 0 && this.lastVelocity > 0 ? this.strokeWidth(velocity) : this.minStrokeWidth();
const newWidth = velocity > 0 && this.lastVelocity > 0 ? this.strokeWidth(velocity) : this.minStrokeWidth();
this.drawSmoothSignature(smoothedPoints, newWidth);
+3 -15
View File
@@ -11,13 +11,7 @@ export type PointLike = {
};
const isTouchEvent = (
event:
| ReactMouseEvent
| ReactPointerEvent
| ReactTouchEvent
| MouseEvent
| PointerEvent
| TouchEvent,
event: ReactMouseEvent | ReactPointerEvent | ReactTouchEvent | MouseEvent | PointerEvent | TouchEvent,
): event is TouchEvent | ReactTouchEvent => {
return 'touches' in event;
};
@@ -34,7 +28,7 @@ export class Point implements PointLike {
}
public distanceTo(point: PointLike): number {
return Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2));
return Math.sqrt((point.x - this.x) ** 2 + (point.y - this.y) ** 2);
}
public equals(point: PointLike): boolean {
@@ -56,13 +50,7 @@ export class Point implements PointLike {
}
public static fromEvent(
event:
| ReactMouseEvent
| ReactPointerEvent
| ReactTouchEvent
| MouseEvent
| PointerEvent
| TouchEvent,
event: ReactMouseEvent | ReactPointerEvent | ReactTouchEvent | MouseEvent | PointerEvent | TouchEvent,
dpi = 1,
el?: HTMLElement | null,
): Point {
@@ -1,13 +1,6 @@
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Trans } from '@lingui/react/macro';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { cn } from '../../lib/utils';
export type SignaturePadColorPickerProps = {
@@ -22,7 +15,7 @@ export const SignaturePadColorPicker = ({
className,
}: SignaturePadColorPickerProps) => {
return (
<div className={cn('text-foreground absolute right-2 top-2 filter', className)}>
<div className={cn('absolute top-2 right-2 text-foreground filter', className)}>
<Select defaultValue={selectedColor} onValueChange={(value) => setSelectedColor(value)}>
<SelectTrigger className="h-auto w-auto border-none p-0.5">
<SelectValue placeholder="" />
@@ -30,29 +23,29 @@ export const SignaturePadColorPicker = ({
<SelectContent className="w-[100px]" align="end">
<SelectItem value="black">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-black shadow-sm" />
<div className="flex items-center text-[0.688rem] text-muted-foreground">
<div className="mr-1 h-4 w-4 rounded-full border-2 border-border bg-black shadow-sm" />
<Trans>Black</Trans>
</div>
</SelectItem>
<SelectItem value="red">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[red] shadow-sm" />
<div className="flex items-center text-[0.688rem] text-muted-foreground">
<div className="mr-1 h-4 w-4 rounded-full border-2 border-border bg-[red] shadow-sm" />
<Trans>Red</Trans>
</div>
</SelectItem>
<SelectItem value="blue">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[blue] shadow-sm" />
<div className="flex items-center text-[0.688rem] text-muted-foreground">
<div className="mr-1 h-4 w-4 rounded-full border-2 border-border bg-[blue] shadow-sm" />
<Trans>Blue</Trans>
</div>
</SelectItem>
<SelectItem value="green">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[green] shadow-sm" />
<div className="flex items-center text-[0.688rem] text-muted-foreground">
<div className="mr-1 h-4 w-4 rounded-full border-2 border-border bg-[green] shadow-sm" />
<Trans>Green</Trans>
</div>
</SelectItem>
@@ -1,12 +1,11 @@
import type { HTMLAttributes } from 'react';
import { useState } from 'react';
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
import { Dialog, DialogClose, DialogContent, DialogFooter } from '@documenso/ui/primitives/dialog';
import type { MessageDescriptor } from '@lingui/core';
import { Trans, useLingui } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
import { Dialog, DialogClose, DialogContent, DialogFooter } from '@documenso/ui/primitives/dialog';
import type { HTMLAttributes } from 'react';
import { useState } from 'react';
import { cn } from '../../lib/utils';
import { Button } from '../button';
@@ -139,11 +138,7 @@ export const SignaturePadDialog = ({
setShowSignatureModal(false);
}}
>
{dialogConfirmText ? (
parseMessageDescriptor(i18n._, dialogConfirmText)
) : (
<Trans>Next</Trans>
)}
{dialogConfirmText ? parseMessageDescriptor(i18n._, dialogConfirmText) : <Trans>Next</Trans>}
</Button>
</DialogFooter>
</DialogContent>
@@ -1,16 +1,12 @@
import type { MouseEvent, PointerEvent, RefObject, TouchEvent } from 'react';
import { useMemo, useRef, useState } from 'react';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
import { SIGNATURE_CANVAS_DPI, SIGNATURE_MIN_COVERAGE_THRESHOLD } from '@documenso/lib/constants/signatures';
import { Trans } from '@lingui/react/macro';
import { Undo2 } from 'lucide-react';
import type { StrokeOptions } from 'perfect-freehand';
import { getStroke } from 'perfect-freehand';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
import {
SIGNATURE_CANVAS_DPI,
SIGNATURE_MIN_COVERAGE_THRESHOLD,
} from '@documenso/lib/constants/signatures';
import type { MouseEvent, PointerEvent, RefObject, TouchEvent } from 'react';
import { useMemo, useRef, useState } from 'react';
import { cn } from '../../lib/utils';
import { getSvgPathFromStroke } from './helper';
@@ -34,7 +30,9 @@ const checkSignatureValidity = (element: RefObject<HTMLCanvasElement>) => {
const totalPixels = data.length / 4;
for (let i = 0; i < data.length; i += 4) {
if (data[i + 3] > 0) filledPixels++;
if (data[i + 3] > 0) {
filledPixels++;
}
}
const filledPercentage = filledPixels / totalPixels;
@@ -49,12 +47,7 @@ export type SignaturePadDrawProps = {
onChange: (_signatureDataUrl: string) => void;
};
export const SignaturePadDraw = ({
className,
value,
onChange,
...props
}: SignaturePadDrawProps) => {
export const SignaturePadDraw = ({ className, value, onChange, ...props }: SignaturePadDrawProps) => {
const $el = useRef<HTMLCanvasElement>(null);
const $imageData = useRef<ImageData | null>(null);
@@ -119,16 +112,12 @@ export const SignaturePadDraw = ({
ctx.fillStyle = selectedColor;
lines.forEach((line) => {
const pathData = new Path2D(
getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)),
);
const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)));
ctx.fill(pathData);
});
const pathData = new Path2D(
getSvgPathFromStroke(getStroke([...currentLine, point], perfectFreehandOptions)),
);
const pathData = new Path2D(getSvgPathFromStroke(getStroke([...currentLine, point], perfectFreehandOptions)));
ctx.fill(pathData);
}
}
@@ -163,9 +152,7 @@ export const SignaturePadDraw = ({
ctx.fillStyle = selectedColor;
newLines.forEach((line) => {
const pathData = new Path2D(
getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)),
);
const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)));
ctx.fill(pathData);
});
@@ -291,7 +278,7 @@ export const SignaturePadDraw = ({
<SignaturePadColorPicker selectedColor={selectedColor} setSelectedColor={setSelectedColor} />
<div className="absolute bottom-3 right-3 flex gap-2">
<div className="absolute right-3 bottom-3 flex gap-2">
<button
type="button"
className="rounded-full p-0 text-[0.688rem] text-muted-foreground/60 ring-offset-background hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
@@ -303,7 +290,7 @@ export const SignaturePadDraw = ({
{isSignatureValid === false && (
<div className="absolute bottom-4 left-4 flex gap-2">
<span className="text-xs text-destructive">
<span className="text-destructive text-xs">
<Trans>Signature is too small</Trans>
</span>
</div>
@@ -1,6 +1,5 @@
import { useEffect, useRef } from 'react';
import { useLingui } from '@lingui/react/macro';
import { useEffect, useRef } from 'react';
import { cn } from '../../lib/utils';
@@ -11,12 +10,7 @@ export type SignaturePadTypeProps = {
onChange: (_value: string) => void;
};
export const SignaturePadType = ({
className,
value,
defaultValue,
onChange,
}: SignaturePadTypeProps) => {
export const SignaturePadType = ({ className, value, defaultValue, onChange }: SignaturePadTypeProps) => {
const { t } = useLingui();
const $isDirty = useRef(false);
@@ -1,11 +1,9 @@
import { useRef } from 'react';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
import { Trans } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import { UploadCloudIcon } from 'lucide-react';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
import { useRef } from 'react';
import { cn } from '../../lib/utils';
@@ -71,12 +69,7 @@ export type SignaturePadUploadProps = {
onChange: (_signatureDataUrl: string) => void;
};
export const SignaturePadUpload = ({
className,
value,
onChange,
...props
}: SignaturePadUploadProps) => {
export const SignaturePadUpload = ({ className, value, onChange, ...props }: SignaturePadUploadProps) => {
const $el = useRef<HTMLCanvasElement>(null);
const $imageData = useRef<ImageData | null>(null);
const $fileInput = useRef<HTMLInputElement>(null);
@@ -85,10 +78,14 @@ export const SignaturePadUpload = ({
try {
const img = await loadImage(event.target.files?.[0]);
if (!$el.current) return;
if (!$el.current) {
return;
}
const ctx = $el.current.getContext('2d');
if (!ctx) return;
if (!ctx) {
return;
}
$imageData.current = loadImageOntoCanvas(img, $el.current, ctx);
onChange?.($el.current.toDataURL());
@@ -133,13 +130,7 @@ export const SignaturePadUpload = ({
{...props}
/>
<input
ref={$fileInput}
type="file"
accept="image/*"
className="hidden"
onChange={handleImageUpload}
/>
<input ref={$fileInput} type="file" accept="image/*" className="hidden" onChange={handleImageUpload} />
<motion.button
className="absolute inset-0 flex h-full w-full items-center justify-center"
@@ -150,10 +141,10 @@ export const SignaturePadUpload = ({
>
{!value && (
<motion.div>
<div className="text-muted-foreground flex flex-col items-center justify-center">
<div className="flex flex-col items-center justify-center text-muted-foreground">
<div className="flex flex-col items-center">
<UploadCloudIcon className="h-8 w-8" />
<span className="text-lg font-semibold">
<span className="font-semibold text-lg">
<Trans>Upload Signature</Trans>
</span>
</div>
@@ -1,13 +1,12 @@
import type { HTMLAttributes } from 'react';
import { useState } from 'react';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { isBase64Image } from '@documenso/lib/constants/signatures';
import { Trans } from '@lingui/react/macro';
import { KeyboardIcon, UploadCloudIcon } from 'lucide-react';
import type { HTMLAttributes } from 'react';
import { useState } from 'react';
import { match } from 'ts-pattern';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { isBase64Image } from '@documenso/lib/constants/signatures';
import { SignatureIcon } from '../../icons/signature';
import { cn } from '../../lib/utils';
import { SignaturePadDraw } from './signature-pad-draw';
@@ -172,22 +171,14 @@ export const SignaturePad = ({
value="draw"
className="relative flex aspect-signature-pad items-center justify-center rounded-md border border-border bg-neutral-50 text-center dark:bg-background"
>
<SignaturePadDraw
className="h-full w-full"
onChange={onDrawSignatureChange}
value={drawSignature}
/>
<SignaturePadDraw className="h-full w-full" onChange={onDrawSignatureChange} value={drawSignature} />
</TabsContent>
<TabsContent
value="text"
className="relative flex aspect-signature-pad items-center justify-center rounded-md border border-border bg-neutral-50 text-center dark:bg-background"
>
<SignaturePadType
value={typedSignature}
defaultValue={fullName}
onChange={onTypedSignatureChange}
/>
<SignaturePadType value={typedSignature} defaultValue={fullName} onChange={onTypedSignatureChange} />
</TabsContent>
<TabsContent
@@ -1,7 +1,6 @@
import { isBase64Image, SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
import { useEffect, useRef } from 'react';
import { SIGNATURE_CANVAS_DPI, isBase64Image } from '@documenso/lib/constants/signatures';
import { cn } from '../../lib/utils';
export type SignatureRenderProps = {
@@ -1,6 +1,5 @@
import * as React from 'react';
import { motion } from 'framer-motion';
import * as React from 'react';
import { cn } from '../../lib/utils';
@@ -26,14 +25,7 @@ function useTabs() {
return context;
}
export function Tabs({
defaultValue,
value,
onValueChange,
children,
className,
...props
}: TabsProps) {
export function Tabs({ defaultValue, value, onValueChange, children, className, ...props }: TabsProps) {
const [tabValue, setTabValue] = React.useState(defaultValue || '');
const handleValueChange = React.useCallback(
@@ -67,11 +59,7 @@ interface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {
export function TabsList({ children, className, ...props }: TabsListProps) {
return (
<div
className={cn('border-border flex flex-wrap border-b', className)}
role="tabslist"
{...props}
>
<div className={cn('flex flex-wrap border-border border-b', className)} role="tabslist" {...props}>
{children}
</div>
);
@@ -95,8 +83,8 @@ export function TabsTrigger({ value, icon, children, className, ...props }: Tabs
data-state={isSelected ? 'active' : 'inactive'}
onClick={() => onValueChange(value)}
className={cn(
'relative flex items-center px-4 py-3 text-sm font-medium transition-all',
'focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
'relative flex items-center px-4 py-3 font-medium text-sm transition-all',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
isSelected ? 'text-foreground' : 'text-muted-foreground hover:text-foreground',
className,
)}
@@ -107,7 +95,7 @@ export function TabsTrigger({ value, icon, children, className, ...props }: Tabs
{isSelected && (
<motion.div
layoutId="activeTabIndicator"
className="bg-foreground/40 absolute bottom-0 left-0 h-0.5 w-full rounded-full"
className="absolute bottom-0 left-0 h-0.5 w-full rounded-full bg-foreground/40"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
@@ -135,12 +123,7 @@ export function TabsContent({ value, children, className, ...props }: TabsConten
}
return (
<div
role="tabpanel"
data-state={isSelected ? 'active' : 'inactive'}
className={cn('mt-4', className)}
{...props}
>
<div role="tabpanel" data-state={isSelected ? 'active' : 'inactive'} className={cn('mt-4', className)} {...props}>
{children}
</div>
);
+1 -1
View File
@@ -1,7 +1,7 @@
import { cn } from '../lib/utils';
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('bg-muted animate-pulse rounded-md', className)} {...props} />;
return <div className={cn('animate-pulse rounded-md bg-muted', className)} {...props} />;
}
export { Skeleton };
+4 -5
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as SliderPrimitive from '@radix-ui/react-slider';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -13,10 +12,10 @@ const Slider = React.forwardRef<
className={cn('relative flex w-full touch-none select-none items-center', className)}
{...props}
>
<SliderPrimitive.Track className="bg-secondary relative h-2 w-full grow overflow-hidden rounded-full">
<SliderPrimitive.Range className="bg-primary absolute h-full" />
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="border-primary bg-background ring-offset-background focus-visible:ring-ring block h-5 w-5 rounded-full border-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
));
+11 -20
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import { cva } from 'class-variance-authority';
import { Loader } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -24,11 +23,9 @@ export interface SpinnerProps extends Omit<React.ComponentPropsWithoutRef<typeof
size?: SpinnerSize;
}
const Spinner = React.forwardRef<SVGSVGElement, SpinnerProps>(
({ className, size = 'default', ...props }, ref) => {
return <Loader ref={ref} className={cn(spinnerVariants({ size }), className)} {...props} />;
},
);
const Spinner = React.forwardRef<SVGSVGElement, SpinnerProps>(({ className, size = 'default', ...props }, ref) => {
return <Loader ref={ref} className={cn(spinnerVariants({ size }), className)} {...props} />;
});
Spinner.displayName = 'Spinner';
@@ -36,19 +33,13 @@ 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>
);
},
);
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';
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { createContext, useContext, useState } from 'react';
import type { FC } from 'react';
import React, { createContext, useContext, useState } from 'react';
type StepContextValue = {
isCompleting: boolean;
+3 -4
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as SwitchPrimitives from '@radix-ui/react-switch';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -10,7 +9,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
'focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
className,
)}
{...props}
@@ -18,7 +17,7 @@ const Switch = React.forwardRef<
>
<SwitchPrimitives.Thumb
className={cn(
'bg-background pointer-events-none block h-5 w-5 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
)}
/>
</SwitchPrimitives.Root>
+33 -51
View File
@@ -15,34 +15,25 @@ const Table = React.forwardRef<
Table.displayName = 'Table';
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
));
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />,
);
TableHeader.displayName = 'TableHeader';
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
));
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => (
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
),
);
TableBody.displayName = 'TableBody';
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn('bg-primary text-primary-foreground font-medium', className)}
{...props}
/>
));
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => (
<tfoot ref={ref} className={cn('bg-primary font-medium text-primary-foreground', className)} {...props} />
),
);
TableFooter.displayName = 'TableFooter';
@@ -50,10 +41,7 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
className,
)}
className={cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', className)}
{...props}
/>
),
@@ -61,19 +49,18 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
TableRow.displayName = 'TableRow';
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'text-muted-foreground h-12 px-4 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0',
className,
)}
{...props}
/>
));
const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
className,
)}
{...props}
/>
),
);
TableHead.displayName = 'TableHead';
@@ -85,24 +72,19 @@ const TableCell = React.forwardRef<
>(({ className, truncate = true, ...props }, ref) => (
<td
ref={ref}
className={cn(
'p-4 align-middle [&:has([role=checkbox])]:pr-0',
truncate && 'truncate',
className,
)}
className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', truncate && 'truncate', className)}
{...props}
/>
));
TableCell.displayName = 'TableCell';
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption ref={ref} className={cn('text-muted-foreground mt-4 text-sm', className)} {...props} />
));
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
({ className, ...props }, ref) => (
<caption ref={ref} className={cn('mt-4 text-muted-foreground text-sm', className)} {...props} />
),
);
TableCaption.displayName = 'TableCaption';
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
+5 -6
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -13,7 +12,7 @@ const TabsList = React.forwardRef<
<TabsPrimitive.List
ref={ref}
className={cn(
'bg-muted text-muted-foreground inline-flex h-10 items-center justify-center rounded-md p-1',
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
className,
)}
{...props}
@@ -29,7 +28,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm',
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 font-medium text-sm ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
className,
)}
{...props}
@@ -45,7 +44,7 @@ const TabsContent = React.forwardRef<
<TabsPrimitive.Content
ref={ref}
className={cn(
'ring-offset-background focus-visible:ring-ring mt-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className,
)}
{...props}
@@ -54,4 +53,4 @@ const TabsContent = React.forwardRef<
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent };
export { Tabs, TabsContent, TabsList, TabsTrigger };
@@ -1,5 +1,30 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
import { getPdfPagesCount, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
import { type TFieldMetaSchema as FieldMeta, ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { nanoid } from '@documenso/lib/universal/id';
import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers';
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@documenso/ui/primitives/command';
import {
DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent,
DocumentFlowFormContainerFooter,
DocumentFlowFormContainerHeader,
DocumentFlowFormContainerStep,
} from '@documenso/ui/primitives/document-flow/document-flow-root';
import { FieldItem } from '@documenso/ui/primitives/document-flow/field-item';
import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
@@ -18,55 +43,16 @@ import {
Type,
User,
} from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useHotkeys } from 'react-hotkeys-hook';
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
import { PDF_VIEWER_PAGE_SELECTOR, getPdfPagesCount } from '@documenso/lib/constants/pdf-viewer';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
import {
type TFieldMetaSchema as FieldMeta,
ZFieldMetaSchema,
} from '@documenso/lib/types/field-meta';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { nanoid } from '@documenso/lib/universal/id';
import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers';
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from '@documenso/ui/primitives/command';
import {
DocumentFlowFormContainerActions,
DocumentFlowFormContainerContent,
DocumentFlowFormContainerFooter,
DocumentFlowFormContainerHeader,
DocumentFlowFormContainerStep,
} from '@documenso/ui/primitives/document-flow/document-flow-root';
import { FieldItem } from '@documenso/ui/primitives/document-flow/field-item';
import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { getRecipientColorStyles } from '../../lib/recipient-colors';
import type { FieldFormType } from '../document-flow/add-fields';
import { FieldAdvancedSettings } from '../document-flow/field-item-advanced-settings';
import { Form } from '../form/form';
import { useStep } from '../stepper';
import {
type TAddTemplateFieldsFormSchema,
ZAddTemplateFieldsFormSchema,
} from './add-template-fields.types';
import { type TAddTemplateFieldsFormSchema, ZAddTemplateFieldsFormSchema } from './add-template-fields.types';
const MIN_HEIGHT_PX = 12;
const MIN_WIDTH_PX = 36;
@@ -99,12 +85,8 @@ export const AddTemplateFieldsFormPartial = ({
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
const [currentField, setCurrentField] = useState<FieldFormType>();
const [activeFieldId, setActiveFieldId] = useState<string | null>(null);
const [lastActiveField, setLastActiveField] = useState<
TAddTemplateFieldsFormSchema['fields'][0] | null
>(null);
const [fieldClipboard, setFieldClipboard] = useState<
TAddTemplateFieldsFormSchema['fields'][0] | null
>(null);
const [lastActiveField, setLastActiveField] = useState<TAddTemplateFieldsFormSchema['fields'][0] | null>(null);
const [fieldClipboard, setFieldClipboard] = useState<TAddTemplateFieldsFormSchema['fields'][0] | null>(null);
const form = useForm<TAddTemplateFieldsFormSchema>({
defaultValues: {
@@ -118,10 +100,8 @@ export const AddTemplateFieldsFormPartial = ({
pageWidth: Number(field.width),
pageHeight: Number(field.height),
recipientId: field.recipientId ?? -1,
signerEmail:
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
signerToken:
recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
signerEmail: recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
signerToken: recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
})),
},
@@ -223,15 +203,7 @@ export const AddTemplateFieldsFormPartial = ({
});
}
},
[
append,
lastActiveField,
selectedSigner?.email,
selectedSigner?.id,
selectedSigner?.token,
toast,
handleAutoSave,
],
[append, lastActiveField, selectedSigner?.email, selectedSigner?.id, selectedSigner?.token, toast, handleAutoSave],
);
const onFieldPaste = useCallback(
@@ -255,14 +227,7 @@ export const AddTemplateFieldsFormPartial = ({
void handleAutoSave();
}
},
[
append,
fieldClipboard,
selectedSigner?.email,
selectedSigner?.id,
selectedSigner?.token,
handleAutoSave,
],
[append, fieldClipboard, selectedSigner?.email, selectedSigner?.id, selectedSigner?.token, handleAutoSave],
);
useHotkeys(['ctrl+c', 'meta+c'], (evt) => onFieldCopy(evt));
@@ -298,8 +263,7 @@ export const AddTemplateFieldsFormPartial = ({
[localFields],
);
const hasErrors =
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
const hasErrors = emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
const [isFieldWithinBounds, setIsFieldWithinBounds] = useState(false);
const [coords, setCoords] = useState({
@@ -315,12 +279,7 @@ export const AddTemplateFieldsFormPartial = ({
const onMouseMove = useCallback(
(event: MouseEvent) => {
setIsFieldWithinBounds(
isWithinPageBounds(
event,
PDF_VIEWER_PAGE_SELECTOR,
fieldBounds.current.width,
fieldBounds.current.height,
),
isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height),
);
setCoords({
@@ -341,12 +300,7 @@ export const AddTemplateFieldsFormPartial = ({
if (
!$page ||
!isWithinPageBounds(
event,
PDF_VIEWER_PAGE_SELECTOR,
fieldBounds.current.width,
fieldBounds.current.height,
)
!isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height)
) {
setSelectedField(null);
return;
@@ -406,12 +360,7 @@ export const AddTemplateFieldsFormPartial = ({
return;
}
const {
x: pageX,
y: pageY,
width: pageWidth,
height: pageHeight,
} = getFieldPosition($page, node);
const { x: pageX, y: pageY, width: pageWidth, height: pageHeight } = getFieldPosition($page, node);
update(index, {
...field,
@@ -509,23 +458,18 @@ export const AddTemplateFieldsFormPartial = ({
useEffect(() => {
const recipientsByRoleToDisplay = recipients.filter(
(recipient) =>
recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
(recipient) => recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
);
setSelectedSigner(
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ??
recipientsByRoleToDisplay[0],
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ?? recipientsByRoleToDisplay[0],
);
}, [recipients]);
const recipientsByRoleToDisplay = useMemo(() => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return (Object.entries(recipientsByRole) as [RecipientRole, TRecipientLite[]][]).filter(
([role]) =>
role !== RecipientRole.CC &&
role !== RecipientRole.VIEWER &&
role !== RecipientRole.ASSISTANT,
([role]) => role !== RecipientRole.CC && role !== RecipientRole.VIEWER && role !== RecipientRole.ASSISTANT,
);
}, [recipientsByRole]);
@@ -558,10 +502,7 @@ export const AddTemplateFieldsFormPartial = ({
{showAdvancedSettings && currentField ? (
<FieldAdvancedSettings
title={msg`Advanced settings`}
description={msg`Configure the ${parseMessageDescriptor(
_,
FRIENDLY_FIELD_TYPE[currentField.type],
)} field`}
description={msg`Configure the ${parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[currentField.type])} field`}
field={currentField}
fields={localFields}
onAdvancedSettings={handleAdvancedSettings}
@@ -573,16 +514,13 @@ export const AddTemplateFieldsFormPartial = ({
/>
) : (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
<div className="flex flex-col">
{selectedField && (
<div
className={cn(
'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size]',
'pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size] dark:text-muted-background',
selectedSignerStyles?.base,
{
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
@@ -651,26 +589,22 @@ export const AddTemplateFieldsFormPartial = ({
variant="outline"
role="combobox"
className={cn(
'mb-12 mt-2 justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
'mt-2 mb-12 justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
selectedSignerStyles?.comboBoxTrigger,
)}
>
{selectedSigner?.email &&
!isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
<span className="flex-1 truncate text-left">
{selectedSigner?.name} ({selectedSigner?.email})
</span>
)}
{selectedSigner?.email && !isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
<span className="flex-1 truncate text-left">
{selectedSigner?.name} ({selectedSigner?.email})
</span>
)}
{selectedSigner?.email &&
isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
<span className="flex-1 truncate text-left">{selectedSigner?.name}</span>
)}
{selectedSigner?.email && isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
<span className="flex-1 truncate text-left">{selectedSigner?.name}</span>
)}
{!selectedSigner?.email && (
<span className="gradie flex-1 truncate text-left">
No recipient selected
</span>
<span className="gradie flex-1 truncate text-left">No recipient selected</span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4" />
@@ -690,14 +624,14 @@ export const AddTemplateFieldsFormPartial = ({
{/* Note: This is duplicated in `add-fields.tsx` */}
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
<CommandGroup key={roleIndex}>
<div className="mb-1 ml-2 mt-2 text-xs font-medium text-muted-foreground">
<div className="mt-2 mb-1 ml-2 font-medium text-muted-foreground text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
</div>
{roleRecipients.length === 0 && (
<div
key={`${role}-empty`}
className="px-4 pb-4 pt-2.5 text-center text-xs text-muted-foreground/80"
className="px-4 pt-2.5 pb-4 text-center text-muted-foreground/80 text-xs"
>
<Trans>No recipients with this role</Trans>
</div>
@@ -708,9 +642,7 @@ export const AddTemplateFieldsFormPartial = ({
key={recipient.id}
className={cn(
'px-2 last:mb-1 [&:not(:first-child)]:mt-1',
getRecipientColorStyles(
recipients.findIndex((r) => r.id === recipient.id),
)?.comboBoxItem,
getRecipientColorStyles(recipients.findIndex((r) => r.id === recipient.id))?.comboBoxItem,
)}
onSelect={() => {
setSelectedSigner(recipient);
@@ -722,22 +654,19 @@ export const AddTemplateFieldsFormPartial = ({
'text-foreground/80': recipient === selectedSigner,
})}
>
{recipient.name &&
!isTemplateRecipientEmailPlaceholder(recipient.email) && (
<span title={`${recipient.name} (${recipient.email})`}>
{recipient.name} ({recipient.email})
</span>
)}
{recipient.name && !isTemplateRecipientEmailPlaceholder(recipient.email) && (
<span title={`${recipient.name} (${recipient.email})`}>
{recipient.name} ({recipient.email})
</span>
)}
{recipient.name &&
isTemplateRecipientEmailPlaceholder(recipient.email) && (
<span title={recipient.name}>{recipient.name}</span>
)}
{recipient.name && isTemplateRecipientEmailPlaceholder(recipient.email) && (
<span title={recipient.name}>{recipient.name}</span>
)}
{!recipient.name &&
!isTemplateRecipientEmailPlaceholder(recipient.email) && (
<span title={recipient.email}>{recipient.email}</span>
)}
{!recipient.name && !isTemplateRecipientEmailPlaceholder(recipient.email) && (
<span title={recipient.email}>{recipient.email}</span>
)}
</span>
</CommandItem>
))}
@@ -766,7 +695,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 font-signature text-lg font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal font-signature text-lg text-muted-foreground group-data-[selected]:text-foreground',
)}
>
<Trans>Signature</Trans>
@@ -791,7 +720,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Contact className="h-4 w-4" />
@@ -817,7 +746,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Mail className="h-4 w-4" />
@@ -843,7 +772,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<User className="h-4 w-4" />
@@ -869,7 +798,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<CalendarDays className="h-4 w-4" />
@@ -895,7 +824,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Type className="h-4 w-4" />
@@ -921,7 +850,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Hash className="h-4 w-4" />
@@ -947,7 +876,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Disc className="h-4 w-4" />
@@ -973,7 +902,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<CheckSquare className="h-4 w-4" />
@@ -999,7 +928,7 @@ export const AddTemplateFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<ChevronDown className="h-4 w-4" />
@@ -1015,14 +944,10 @@ export const AddTemplateFieldsFormPartial = ({
{hasErrors && (
<div className="mt-4">
<ul>
<li className="text-sm text-red-500">
<li className="text-red-500 text-sm">
<Trans>
To proceed further, please set at least one value for the{' '}
{emptyCheckboxFields.length > 0
? 'Checkbox'
: emptyRadioFields.length > 0
? 'Radio'
: 'Select'}{' '}
{emptyCheckboxFields.length > 0 ? 'Checkbox' : emptyRadioFields.length > 0 ? 'Radio' : 'Select'}{' '}
field.
</Trans>
</li>
@@ -1,8 +1,7 @@
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { FieldType } from '@prisma/client';
import { z } from 'zod';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
export const ZAddTemplateFieldsFormSchema = z.object({
fields: z.array(
z.object({
@@ -1,17 +1,3 @@
import { useCallback, useId, useMemo, useRef, useState } from 'react';
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { TemplateDirectLink } from '@prisma/client';
import { DocumentSigningOrder, type Field, RecipientRole } from '@prisma/client';
import { motion } from 'framer-motion';
import { GripVerticalIcon, HelpCircle, Link2Icon, Plus, Trash } from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
@@ -28,11 +14,20 @@ import { Button } from '@documenso/ui/primitives/button';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
import { Input } from '@documenso/ui/primitives/input';
import { toast } from '@documenso/ui/primitives/use-toast';
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { TemplateDirectLink } from '@prisma/client';
import { DocumentSigningOrder, type Field, RecipientRole } from '@prisma/client';
import { motion } from 'framer-motion';
import { GripVerticalIcon, HelpCircle, Link2Icon, Plus, Trash } from 'lucide-react';
import { useCallback, useId, useMemo, useRef, useState } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import {
DocumentReadOnlyFields,
mapFieldsWithRecipients,
} from '../../components/document/document-read-only-fields';
import { DocumentReadOnlyFields, mapFieldsWithRecipients } from '../../components/document/document-read-only-fields';
import { Checkbox } from '../checkbox';
import {
DocumentFlowFormContainerActions,
@@ -114,9 +109,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
}));
if (signingOrder === DocumentSigningOrder.SEQUENTIAL) {
mappedRecipients = mappedRecipients.sort(
(a, b) => (a.signingOrder ?? 0) - (b.signingOrder ?? 0),
);
mappedRecipients = mappedRecipients.sort((a, b) => (a.signingOrder ?? 0) - (b.signingOrder ?? 0));
}
return mappedRecipients;
@@ -131,10 +124,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
},
});
const emptySigners = useCallback(
() => form.getValues('signers').filter((signer) => signer.email === ''),
[form],
);
const emptySigners = useCallback(() => form.getValues('signers').filter((signer) => signer.email === ''), [form]);
const { scheduleSave } = useAutoSave(onAutoSave);
@@ -157,9 +147,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
const currentSigners = form.getValues('signers');
const updatedSigners = currentSigners.map((signer) => {
// Find the matching recipient from the response using nativeId
const matchingRecipient = response.recipients.find(
(recipient) => recipient.id === signer.nativeId,
);
const matchingRecipient = response.recipients.find((recipient) => recipient.id === signer.nativeId);
if (matchingRecipient) {
// Update the signer with the server-returned data, especially the ID
@@ -171,9 +159,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
// For new signers without nativeId, match by email and update with server ID
if (!signer.nativeId) {
const newRecipient = response.recipients.find(
(recipient) => recipient.email === signer.email,
);
const newRecipient = response.recipients.find((recipient) => recipient.email === signer.email);
if (newRecipient) {
return {
...signer,
@@ -206,14 +192,10 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
const recipientHasAuthOptions = recipients.find((recipient) => {
const recipientAuthOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions);
return (
recipientAuthOptions.accessAuth.length > 0 || recipientAuthOptions.actionAuth.length > 0
);
return recipientAuthOptions.accessAuth.length > 0 || recipientAuthOptions.actionAuth.length > 0;
});
const formHasActionAuth = form
.getValues('signers')
.find((signer) => signer.actionAuth.length > 0);
const formHasActionAuth = form.getValues('signers').find((signer) => signer.actionAuth.length > 0);
return recipientHasAuthOptions !== undefined || formHasActionAuth !== undefined;
}, [recipients, form]);
@@ -280,18 +262,15 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
void handleAutoSave();
};
const isSignerDirectRecipient = (
signer: TAddTemplatePlacholderRecipientsFormSchema['signers'][number],
): boolean => {
return (
templateDirectLink !== null &&
signer.nativeId === templateDirectLink?.directTemplateRecipientId
);
const isSignerDirectRecipient = (signer: TAddTemplatePlacholderRecipientsFormSchema['signers'][number]): boolean => {
return templateDirectLink !== null && signer.nativeId === templateDirectLink?.directTemplateRecipientId;
};
const onDragEnd = useCallback(
async (result: DropResult) => {
if (!result.destination) return;
if (!result.destination) {
return;
}
const items = Array.from(watchedSigners);
const [reorderedSigner] = items.splice(result.source.index, 1);
@@ -443,10 +422,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
return (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
{isDocumentPdfLoaded && (
<DocumentReadOnlyFields
@@ -470,17 +446,12 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
id="signingOrder"
checked={field.value === DocumentSigningOrder.SEQUENTIAL}
onCheckedChange={(checked) => {
if (
!checked &&
watchedSigners.some((s) => s.role === RecipientRole.ASSISTANT)
) {
if (!checked && watchedSigners.some((s) => s.role === RecipientRole.ASSISTANT)) {
setShowSigningOrderConfirmation(true);
return;
}
field.onChange(
checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL,
);
field.onChange(checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL);
// If sequential signing is turned off, disable dictate next signer
if (!checked) {
@@ -541,8 +512,8 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
<TooltipContent className="max-w-80 p-4">
<p>
<Trans>
When enabled, signers can choose who should sign next in the sequence
instead of following the predefined order.
When enabled, signers can choose who should sign next in the sequence instead of following
the predefined order.
</Trans>
</p>
</TooltipContent>
@@ -563,11 +534,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
>
<Droppable droppableId="signers">
{(provided) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
className="flex w-full flex-col gap-y-2"
>
<div {...provided.droppableProps} ref={provided.innerRef} className="flex w-full flex-col gap-y-2">
{/* todo */}
{signers.map((signer, index) => (
<Draggable
@@ -587,8 +554,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
{...provided.draggableProps}
{...provided.dragHandleProps}
className={cn('py-1', {
'pointer-events-none rounded-md bg-widget-foreground pt-2':
snapshot.isDragging,
'pointer-events-none rounded-md bg-widget-foreground pt-2': snapshot.isDragging,
})}
>
<motion.fieldset
@@ -625,9 +591,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
handleSigningOrderChange(index, e.target.value);
}}
disabled={
snapshot.isDragging ||
isSubmitting ||
isSignerDirectRecipient(signer)
snapshot.isDragging || isSubmitting || isSignerDirectRecipient(signer)
}
/>
</FormControl>
@@ -658,11 +622,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
type="email"
placeholder={_(msg`Email`)}
{...field}
value={
isTemplateRecipientEmailPlaceholder(field.value)
? ''
: field.value
}
value={isTemplateRecipientEmailPlaceholder(field.value) ? '' : field.value}
disabled={
field.disabled ||
isSubmitting ||
@@ -717,30 +677,29 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
)}
/>
{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>
{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
@@ -770,15 +729,14 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
<Link2Icon className="h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
<h3 className="text-lg font-semibold text-foreground">
<h3 className="font-semibold text-foreground text-lg">
<Trans>Direct link receiver</Trans>
</h3>
<p className="mt-1 text-muted-foreground">
<Trans>
This field cannot be modified or deleted. When you share
this template's direct link or add it to your public
profile, anyone who accesses it can input their name and
email, and fill in the fields assigned to them.
This field cannot be modified or deleted. When you share this template's
direct link or add it to your public profile, anyone who accesses it can input
their name and email, and fill in the fields assigned to them.
</Trans>
</p>
</TooltipContent>
@@ -824,7 +782,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
disabled={isSubmitting}
onClick={() => onAddPlaceholderRecipient()}
>
<Plus className="-ml-1 mr-2 h-5 w-5" />
<Plus className="mr-2 -ml-1 h-5 w-5" />
<Trans>Add Placeholder Recipient</Trans>
</Button>
@@ -832,13 +790,10 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
type="button"
className="bg-black/5 hover:bg-black/10 dark:bg-muted dark:hover:bg-muted/80"
variant="secondary"
disabled={
isSubmitting ||
form.getValues('signers').some((signer) => signer.email === user?.email)
}
disabled={isSubmitting || form.getValues('signers').some((signer) => signer.email === user?.email)}
onClick={() => onAddPlaceholderSelfRecipient()}
>
<Plus className="-ml-1 mr-2 h-5 w-5" />
<Plus className="mr-2 -ml-1 h-5 w-5" />
<Trans>Add Myself</Trans>
</Button>
</div>
@@ -852,10 +807,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
onCheckedChange={(value) => setShowAdvancedSettings(Boolean(value))}
/>
<label
className="ml-2 text-sm text-muted-foreground"
htmlFor="showAdvancedRecipientSettings"
>
<label className="ml-2 text-muted-foreground text-sm" htmlFor="showAdvancedRecipientSettings">
<Trans>Show advanced settings</Trans>
</label>
</div>
@@ -1,8 +1,7 @@
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
import { z } from 'zod';
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { zEmail } from '@documenso/lib/utils/zod';
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
import { z } from 'zod';
export const ZAddTemplatePlacholderRecipientsFormSchema = z
.object({
@@ -33,6 +32,4 @@ export const ZAddTemplatePlacholderRecipientsFormSchema = z
{ message: 'Signers must have unique names', path: ['signers__root'] },
);
export type TAddTemplatePlacholderRecipientsFormSchema = z.infer<
typeof ZAddTemplatePlacholderRecipientsFormSchema
>;
export type TAddTemplatePlacholderRecipientsFormSchema = z.infer<typeof ZAddTemplatePlacholderRecipientsFormSchema>;
@@ -1,20 +1,7 @@
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentVisibility, TeamMemberRole, TemplateType } from '@prisma/client';
import { DocumentDistributionMethod, type Field } from '@prisma/client';
import { InfoIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
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,
DOCUMENT_SIGNATURE_TYPES,
} from '@documenso/lib/constants/document';
import { DOCUMENT_DISTRIBUTION_METHODS, DOCUMENT_SIGNATURE_TYPES } from '@documenso/lib/constants/document';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
@@ -37,30 +24,25 @@ import {
DocumentVisibilitySelect,
DocumentVisibilityTooltip,
} from '@documenso/ui/components/document/document-visibility-select';
import { TemplateTypeSelect, TemplateTypeTooltip } from '@documenso/ui/components/template/template-type-select';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@documenso/ui/primitives/accordion';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import {
TemplateTypeSelect,
TemplateTypeTooltip,
} from '@documenso/ui/components/template/template-type-select';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@documenso/ui/primitives/accordion';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
DocumentDistributionMethod,
DocumentVisibility,
type Field,
TeamMemberRole,
TemplateType,
} from '@prisma/client';
import { InfoIcon } from 'lucide-react';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { DocumentEmailCheckboxes } from '../../components/document/document-email-checkboxes';
import {
DocumentReadOnlyFields,
mapFieldsWithRecipients,
} from '../../components/document/document-read-only-fields';
import { DocumentReadOnlyFields, mapFieldsWithRecipients } from '../../components/document/document-read-only-fields';
import { DocumentSignatureSettingsTooltip } from '../../components/document/document-signature-settings-tooltip';
import { Combobox } from '../combobox';
import {
@@ -123,10 +105,8 @@ export const AddTemplateSettingsFormPartial = ({
message: template.templateMeta?.message ?? '',
timezone: template.templateMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
dateFormat: (template.templateMeta?.dateFormat ??
DEFAULT_DOCUMENT_DATE_FORMAT) as TDocumentMetaDateFormat,
distributionMethod:
template.templateMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
dateFormat: (template.templateMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT) as TDocumentMetaDateFormat,
distributionMethod: template.templateMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
redirectUrl: template.templateMeta?.redirectUrl ?? '',
language: template.templateMeta?.language ?? 'en',
emailId: template.templateMeta?.emailId ?? null,
@@ -142,11 +122,10 @@ export const AddTemplateSettingsFormPartial = ({
const distributionMethod = form.watch('meta.distributionMethod');
const emailSettings = form.watch('meta.emailSettings');
const { data: emailData, isLoading: isLoadingEmails } =
trpc.enterprise.organisation.email.find.useQuery({
organisationId: organisation.id,
perPage: 100,
});
const { data: emailData, isLoading: isLoadingEmails } = trpc.enterprise.organisation.email.find.useQuery({
organisationId: organisation.id,
perPage: 100,
});
const emails = emailData?.data || [];
@@ -192,10 +171,7 @@ export const AddTemplateSettingsFormPartial = ({
return (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
{isDocumentPdfLoaded && (
@@ -207,10 +183,7 @@ export const AddTemplateSettingsFormPartial = ({
)}
<Form {...form}>
<fieldset
className="flex h-full flex-col space-y-6"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-6" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="title"
@@ -221,12 +194,7 @@ export const AddTemplateSettingsFormPartial = ({
</FormLabel>
<FormControl>
<Input
className="bg-background"
{...field}
maxLength={255}
onBlur={handleAutoSave}
/>
<Input className="bg-background" {...field} maxLength={255} onBlur={handleAutoSave} />
</FormControl>
<FormMessage />
</FormItem>
@@ -246,9 +214,8 @@ export const AddTemplateSettingsFormPartial = ({
</TooltipTrigger>
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
Controls the language for the document, including the language to be used
for email notifications, and the final certificate that is generated and
attached to the document.
Controls the language for the document, including the language to be used for email
notifications, and the final certificate that is generated and attached to the document.
</TooltipContent>
</Tooltip>
</FormLabel>
@@ -375,30 +342,27 @@ export const AddTemplateSettingsFormPartial = ({
<p>
<Trans>
This is how the document will reach the recipients once the document is
ready for signing.
This is how the document will reach the recipients once the document is ready for signing.
</Trans>
</p>
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
<li>
<Trans>
<strong>Email</strong> - The recipient will be emailed the document to
sign, approve, etc.
<strong>Email</strong> - The recipient will be emailed the document to sign, approve, etc.
</Trans>
</li>
<li>
<Trans>
<strong>None</strong> - We will generate links which you can send to
the recipients manually.
<strong>None</strong> - We will generate links which you can send to the recipients
manually.
</Trans>
</li>
</ul>
<Trans>
<strong>Note</strong> - If you use Links in combination with direct
templates, you will need to manually send the links to the remaining
recipients.
<strong>Note</strong> - If you use Links in combination with direct templates, you will need
to manually send the links to the remaining recipients.
</Trans>
</TooltipContent>
</Tooltip>
@@ -417,13 +381,11 @@ export const AddTemplateSettingsFormPartial = ({
</SelectTrigger>
<SelectContent position="popper">
{Object.values(DOCUMENT_DISTRIBUTION_METHODS).map(
({ value, description }) => (
<SelectItem key={value} value={value}>
{t(description)}
</SelectItem>
),
)}
{Object.values(DOCUMENT_DISTRIBUTION_METHODS).map(({ value, description }) => (
<SelectItem key={value} value={value}>
{t(description)}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
@@ -496,7 +458,7 @@ export const AddTemplateSettingsFormPartial = ({
<Trans>Email Options</Trans>
</AccordionTrigger>
<AccordionContent className="-mx-1 px-1 pt-4 text-sm leading-relaxed text-muted-foreground [&>div]:pb-0">
<AccordionContent className="-mx-1 px-1 pt-4 text-muted-foreground text-sm leading-relaxed [&>div]:pb-0">
<div className="flex flex-col space-y-6">
{organisation.organisationClaim.flags.emailDomains && (
<FormField
@@ -512,14 +474,9 @@ export const AddTemplateSettingsFormPartial = ({
<Select
{...field}
value={field.value === null ? '-1' : field.value}
onValueChange={(value) =>
field.onChange(value === '-1' ? null : value)
}
onValueChange={(value) => field.onChange(value === '-1' ? null : value)}
>
<SelectTrigger
loading={isLoadingEmails}
className="bg-background"
>
<SelectTrigger loading={isLoadingEmails} className="bg-background">
<SelectValue />
</SelectTrigger>
@@ -548,8 +505,7 @@ export const AddTemplateSettingsFormPartial = ({
<FormItem>
<FormLabel>
<Trans>
Reply To Email{' '}
<span className="text-muted-foreground">(Optional)</span>
Reply To Email <span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
@@ -636,7 +592,7 @@ export const AddTemplateSettingsFormPartial = ({
<Trans>Advanced Options</Trans>
</AccordionTrigger>
<AccordionContent className="-mx-1 px-1 pt-4 text-sm leading-relaxed text-muted-foreground">
<AccordionContent className="-mx-1 px-1 pt-4 text-muted-foreground text-sm leading-relaxed">
<div className="flex flex-col space-y-6">
<FormField
control={form.control}
@@ -652,20 +608,14 @@ export const AddTemplateSettingsFormPartial = ({
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
Add an external ID to the template. This can be used to identify
in external systems.
Add an external ID to the template. This can be used to identify in external systems.
</Trans>
</TooltipContent>
</Tooltip>
</FormLabel>
<FormControl>
<Input
className="bg-background"
{...field}
maxLength={255}
onBlur={handleAutoSave}
/>
<Input className="bg-background" {...field} maxLength={255} onBlur={handleAutoSave} />
</FormControl>
<FormMessage />
@@ -748,20 +698,13 @@ export const AddTemplateSettingsFormPartial = ({
</TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
Add a URL to redirect the user to once the document is signed
</Trans>
<Trans>Add a URL to redirect the user to once the document is signed</Trans>
</TooltipContent>
</Tooltip>
</FormLabel>
<FormControl>
<Input
className="bg-background"
{...field}
maxLength={255}
onBlur={handleAutoSave}
/>
<Input className="bg-background" {...field} maxLength={255} onBlur={handleAutoSave} />
</FormControl>
<FormMessage />
@@ -1,23 +1,15 @@
import { msg } from '@lingui/core/macro';
import { DocumentDistributionMethod } from '@prisma/client';
import { DocumentVisibility, TemplateType } from '@prisma/client';
import { z } from 'zod';
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaTimezoneSchema,
} from '@documenso/lib/types/document-meta';
import { ZDocumentMetaDateFormatSchema, ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { zEmail } from '@documenso/lib/utils/zod';
import { msg } from '@lingui/core/macro';
import { DocumentDistributionMethod, DocumentVisibility, TemplateType } from '@prisma/client';
import { z } from 'zod';
export const ZAddTemplateSettingsFormSchema = z.object({
title: z.string().trim().min(1, { message: "Title can't be empty" }),
@@ -35,16 +27,12 @@ export const ZAddTemplateSettingsFormSchema = z.object({
message: z.string(),
timezone: ZDocumentMetaTimezoneSchema.default(DEFAULT_DOCUMENT_TIME_ZONE),
dateFormat: ZDocumentMetaDateFormatSchema.default(DEFAULT_DOCUMENT_DATE_FORMAT),
distributionMethod: z
.nativeEnum(DocumentDistributionMethod)
.optional()
.default(DocumentDistributionMethod.EMAIL),
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional().default(DocumentDistributionMethod.EMAIL),
redirectUrl: z
.string()
.optional()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
}),
language: z
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
+15 -17
View File
@@ -4,23 +4,21 @@ import { cn } from '../lib/utils';
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'border-input ring-offset-background placeholder:text-muted-foreground/40 focus-visible:ring-ring flex h-20 w-full rounded-md border bg-transparent px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
{
'ring-2 !ring-red-500 transition-all': props['aria-invalid'],
},
)}
ref={ref}
{...props}
/>
);
},
);
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex h-20 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
{
'!ring-red-500 ring-2 transition-all': props['aria-invalid'],
},
)}
ref={ref}
{...props}
/>
);
});
Textarea.displayName = 'Textarea';
+8 -9
View File
@@ -1,22 +1,21 @@
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
import { motion } from 'framer-motion';
import { Monitor, MoonStar, Sun } from 'lucide-react';
import { Theme, useTheme } from 'remix-themes';
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
export const ThemeSwitcher = () => {
const [theme, setTheme] = useTheme();
const isMounted = useIsMounted();
return (
<div className="bg-muted flex items-center gap-x-1 rounded-full p-1">
<div className="flex items-center gap-x-1 rounded-full bg-muted p-1">
<button
className="text-muted-foreground relative z-10 flex h-8 w-8 items-center justify-center rounded-full"
className="relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground"
onClick={() => setTheme(Theme.LIGHT)}
>
{isMounted && theme === Theme.LIGHT && (
<motion.div
className="bg-background absolute inset-0 rounded-full mix-blend-color-burn"
className="absolute inset-0 rounded-full bg-background mix-blend-color-burn"
layoutId="selected-theme"
/>
)}
@@ -24,12 +23,12 @@ export const ThemeSwitcher = () => {
</button>
<button
className="text-muted-foreground relative z-10 flex h-8 w-8 items-center justify-center rounded-full"
className="relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground"
onClick={() => setTheme(Theme.DARK)}
>
{isMounted && theme === Theme.DARK && (
<motion.div
className="bg-background absolute inset-0 rounded-full mix-blend-exclusion"
className="absolute inset-0 rounded-full bg-background mix-blend-exclusion"
layoutId="selected-theme"
/>
)}
@@ -38,12 +37,12 @@ export const ThemeSwitcher = () => {
</button>
<button
className="text-muted-foreground relative z-10 flex h-8 w-8 items-center justify-center rounded-full"
className="relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground"
onClick={() => setTheme(null)}
>
{isMounted && theme === null && (
<motion.div
className="bg-background absolute inset-0 rounded-full mix-blend-exclusion"
className="absolute inset-0 rounded-full bg-background mix-blend-exclusion"
layoutId="selected-theme"
/>
)}
+16 -28
View File
@@ -1,9 +1,8 @@
import * as React from 'react';
import * as ToastPrimitives from '@radix-ui/react-toast';
import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import { X } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -16,7 +15,7 @@ const ToastViewport = React.forwardRef<
<ToastPrimitives.Viewport
ref={ref}
className={cn(
'fixed top-0 z-[1001] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
'fixed top-0 z-[1001] flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:right-0 sm:bottom-0 sm:flex-col md:max-w-[420px]',
className,
)}
{...props}
@@ -31,8 +30,7 @@ const toastVariants = cva(
variants: {
variant: {
default: 'bg-background border',
destructive:
'group destructive border-destructive bg-destructive text-destructive-foreground',
destructive: 'group destructive border-destructive bg-destructive text-destructive-foreground',
},
},
defaultVariants: {
@@ -45,13 +43,7 @@ const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
);
return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />;
});
Toast.displayName = ToastPrimitives.Root.displayName;
@@ -63,7 +55,7 @@ const ToastAction = React.forwardRef<
<ToastPrimitives.Action
ref={ref}
className={cn(
'ring-offset-background hover:bg-secondary focus:ring-ring group-[.destructive]:border-destructive/30 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 font-medium text-sm ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-destructive/30 group-[.destructive]:focus:ring-destructive group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground',
className,
)}
{...props}
@@ -80,7 +72,7 @@ const ToastClose = React.forwardRef<
ref={ref}
data-testid="toast-close"
className={cn(
'text-foreground/50 hover:text-foreground absolute right-2 top-2 rounded-md p-1 opacity-100 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 md:opacity-0 group-hover:md:opacity-100',
'absolute top-2 right-2 rounded-md p-1 text-foreground/50 opacity-100 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-[.destructive]:text-red-300 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 group-[.destructive]:hover:text-red-50 md:opacity-0 group-hover:md:opacity-100',
className,
)}
toast-close=""
@@ -96,7 +88,7 @@ const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title ref={ref} className={cn('text-sm font-semibold', className)} {...props} />
<ToastPrimitives.Title ref={ref} className={cn('font-semibold text-sm', className)} {...props} />
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
@@ -105,11 +97,7 @@ const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn('text-sm opacity-90', className)}
{...props}
/>
<ToastPrimitives.Description ref={ref} className={cn('text-sm opacity-90', className)} {...props} />
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
@@ -119,13 +107,13 @@ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
type ToastActionElement,
ToastClose,
ToastDescription,
type ToastProps,
ToastProvider,
ToastTitle,
ToastViewport,
};
+11 -20
View File
@@ -1,11 +1,4 @@
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from './toast';
import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from './toast';
import { useToast } from './use-toast';
export function Toaster() {
@@ -13,18 +6,16 @@ export function Toaster() {
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && <ToastDescription>{description}</ToastDescription>}
</div>
{action}
<ToastClose />
</Toast>
);
})}
{toasts.map(({ id, title, description, action, ...props }) => (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && <ToastDescription>{description}</ToastDescription>}
</div>
{action}
<ToastClose />
</Toast>
))}
<ToastViewport />
</ToastProvider>
);
+2 -7
View File
@@ -1,8 +1,7 @@
import * as React from 'react';
import * as TogglePrimitive from '@radix-ui/react-toggle';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -31,11 +30,7 @@ const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
<TogglePrimitive.Root ref={ref} className={cn(toggleVariants({ variant, size, className }))} {...props} />
));
Toggle.displayName = TogglePrimitive.Root.displayName;
+4 -7
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -11,9 +10,7 @@ const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>
>(({ type = 'button', ...props }, ref) => (
<TooltipPrimitive.Trigger ref={ref} type={type} {...props} />
));
>(({ type = 'button', ...props }, ref) => <TooltipPrimitive.Trigger ref={ref} type={type} {...props} />);
TooltipTrigger.displayName = TooltipPrimitive.Trigger.displayName;
@@ -26,7 +23,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
'bg-popover z-9999 text-popover-foreground animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md',
'fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 z-9999 animate-in overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-popover-foreground text-sm shadow-md',
className,
)}
{...props}
@@ -36,4 +33,4 @@ const TooltipContent = React.forwardRef<
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
+4 -2
View File
@@ -152,7 +152,9 @@ function toast({ ...props }: Toast) {
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss();
if (!open) {
dismiss();
}
},
},
});
@@ -184,4 +186,4 @@ function useToast() {
};
}
export { useToast, toast };
export { toast, useToast };