fix: lint project

This commit is contained in:
David Nguyen 2026-04-10 14:36:10 +10:00
parent b7b077f57c
commit 89eb086b13
1384 changed files with 12667 additions and 23921 deletions

View file

@ -10,4 +10,4 @@
"baseDir": "src",
"uiLibrary": "radix-ui",
"commands": {}
}
}

View file

@ -1,10 +1,4 @@
{
"title": "Concepts",
"pages": [
"document-lifecycle",
"recipient-roles",
"field-types",
"signing-workflow",
"signing-certificates"
]
"pages": ["document-lifecycle", "recipient-roles", "field-types", "signing-workflow", "signing-certificates"]
}

View file

@ -1,13 +1,4 @@
{
"title": "API Reference",
"pages": [
"documents",
"recipients",
"fields",
"templates",
"teams",
"rate-limits",
"versioning",
"developer-mode"
]
"pages": ["documents", "recipients", "fields", "templates", "teams", "rate-limits", "versioning", "developer-mode"]
}

View file

@ -1,13 +1,4 @@
{
"title": "Organisations",
"pages": [
"overview",
"create-team",
"members",
"groups",
"email-domains",
"preferences",
"single-sign-on",
"billing"
]
"pages": ["overview", "create-team", "members", "groups", "email-domains", "preferences", "single-sign-on", "billing"]
}

View file

@ -1,5 +1,5 @@
import { baseOptions } from '@/lib/layout.shared';
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { baseOptions } from '@/lib/layout.shared';
export default function Layout({ children }: LayoutProps<'/'>) {
return <HomeLayout {...baseOptions()}>{children}</HomeLayout>;

View file

@ -1,16 +1,7 @@
import { BookOpenIcon, CodeIcon, FileTextIcon, GithubIcon, ServerIcon, ShieldCheckIcon, UserIcon } from 'lucide-react';
import type { Metadata } from 'next';
import Link from 'next/link';
import {
BookOpenIcon,
CodeIcon,
FileTextIcon,
GithubIcon,
ServerIcon,
ShieldCheckIcon,
UserIcon,
} from 'lucide-react';
export const metadata: Metadata = {
title: 'Documenso Docs',
description:
@ -22,21 +13,21 @@ export default function HomePage() {
<main className="mx-auto max-w-4xl px-4 py-12">
{/* Hero */}
<div className="mb-16 pt-6 text-center">
<h1 className="mb-4 text-4xl font-bold tracking-tight">Documenso Documentation</h1>
<p className="text-fd-muted-foreground mx-auto mb-8 max-w-2xl text-lg">
The open-source document signing platform. Send documents for signatures, integrate with
your apps, or self-host with full control.
<h1 className="mb-4 font-bold text-4xl tracking-tight">Documenso Documentation</h1>
<p className="mx-auto mb-8 max-w-2xl text-fd-muted-foreground text-lg">
The open-source document signing platform. Send documents for signatures, integrate with your apps, or
self-host with full control.
</p>
<div className="flex flex-wrap justify-center gap-3">
<Link
href="/docs/users"
className="bg-documenso text-fd-primary-foreground hover:bg-documenso-dark/90 inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-medium transition-colors"
className="inline-flex items-center gap-2 rounded-lg bg-documenso px-5 py-2.5 font-medium text-fd-primary-foreground text-sm transition-colors hover:bg-documenso-dark/90"
>
Get Started
</Link>
<a
href="https://github.com/documenso/documenso"
className="bg-fd-background hover:bg-fd-accent inline-flex items-center gap-2 rounded-lg border px-5 py-2.5 text-sm font-medium transition-colors"
className="inline-flex items-center gap-2 rounded-lg border bg-fd-background px-5 py-2.5 font-medium text-sm transition-colors hover:bg-fd-accent"
>
<GithubIcon className="size-4" />
View on GitHub
@ -48,64 +39,60 @@ export default function HomePage() {
<div className="mb-16 grid gap-4 md:grid-cols-3">
<Link
href="/docs/users"
className="group bg-fd-card hover:border-fd-primary/50 relative flex flex-col rounded-xl border p-6 transition-all hover:shadow-md"
className="group relative flex flex-col rounded-xl border bg-fd-card p-6 transition-all hover:border-fd-primary/50 hover:shadow-md"
>
<div className="mb-4 flex size-12 items-center justify-center rounded-lg bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
<UserIcon className="size-6" />
</div>
<h2 className="mb-2 text-lg font-semibold">User Guide</h2>
<p className="text-fd-muted-foreground mb-4 flex-1 text-sm">
<h2 className="mb-2 font-semibold text-lg">User Guide</h2>
<p className="mb-4 flex-1 text-fd-muted-foreground text-sm">
Send documents, create templates, and manage your team using the web application.
</p>
<span className="text-fd-primary text-sm font-medium">Get started </span>
<span className="font-medium text-fd-primary text-sm">Get started </span>
</Link>
<Link
href="/docs/developers"
className="group bg-fd-card hover:border-fd-primary/50 relative flex flex-col rounded-xl border p-6 transition-all hover:shadow-md"
className="group relative flex flex-col rounded-xl border bg-fd-card p-6 transition-all hover:border-fd-primary/50 hover:shadow-md"
>
<div className="mb-4 flex size-12 items-center justify-center rounded-lg bg-blue-500/10 text-blue-600 dark:text-blue-400">
<CodeIcon className="size-6" />
</div>
<h2 className="mb-2 text-lg font-semibold">Developer Guide</h2>
<p className="text-fd-muted-foreground mb-4 flex-1 text-sm">
Integrate document signing into your applications with the REST API, webhooks, and
embedding.
<h2 className="mb-2 font-semibold text-lg">Developer Guide</h2>
<p className="mb-4 flex-1 text-fd-muted-foreground text-sm">
Integrate document signing into your applications with the REST API, webhooks, and embedding.
</p>
<span className="text-fd-primary text-sm font-medium">View API docs </span>
<span className="font-medium text-fd-primary text-sm">View API docs </span>
</Link>
<Link
href="/docs/self-hosting"
className="group bg-fd-card hover:border-fd-primary/50 relative flex flex-col rounded-xl border p-6 transition-all hover:shadow-md"
className="group relative flex flex-col rounded-xl border bg-fd-card p-6 transition-all hover:border-fd-primary/50 hover:shadow-md"
>
<div className="mb-4 flex size-12 items-center justify-center rounded-lg bg-purple-500/10 text-purple-600 dark:text-purple-400">
<ServerIcon className="size-6" />
</div>
<h2 className="mb-2 text-lg font-semibold">Self-Hosting Guide</h2>
<p className="text-fd-muted-foreground mb-4 flex-1 text-sm">
<h2 className="mb-2 font-semibold text-lg">Self-Hosting Guide</h2>
<p className="mb-4 flex-1 text-fd-muted-foreground text-sm">
Deploy your own Documenso instance with Docker, Kubernetes, or Railway.
</p>
<span className="text-fd-primary text-sm font-medium">Deploy now </span>
<span className="font-medium text-fd-primary text-sm">Deploy now </span>
</Link>
</div>
{/* Quick Start & Core Concepts */}
<div className="mb-16 grid gap-8 md:grid-cols-2">
<div className="bg-fd-card/50 rounded-xl border p-6">
<div className="rounded-xl border bg-fd-card/50 p-6">
<h3 className="mb-4 flex items-center gap-2 font-semibold">
<BookOpenIcon className="text-fd-muted-foreground size-5" />
<BookOpenIcon className="size-5 text-fd-muted-foreground" />
Quick Start
</h3>
<div className="space-y-4">
<div>
<h4 className="mb-2 text-sm font-medium">Send your first document</h4>
<ol className="text-fd-muted-foreground list-inside list-decimal space-y-1 text-sm">
<h4 className="mb-2 font-medium text-sm">Send your first document</h4>
<ol className="list-inside list-decimal space-y-1 text-fd-muted-foreground text-sm">
<li>
<Link
href="/docs/users/getting-started/create-account"
className="text-fd-primary hover:underline"
>
<Link href="/docs/users/getting-started/create-account" className="text-fd-primary hover:underline">
Create an account
</Link>
</li>
@ -120,8 +107,8 @@ export default function HomePage() {
</ol>
</div>
<div>
<h4 className="mb-2 text-sm font-medium">Integrate with the API</h4>
<ol className="text-fd-muted-foreground list-inside list-decimal space-y-1 text-sm">
<h4 className="mb-2 font-medium text-sm">Integrate with the API</h4>
<ol className="list-inside list-decimal space-y-1 text-fd-muted-foreground text-sm">
<li>
<Link
href="/docs/developers/getting-started/authentication"
@ -141,8 +128,8 @@ export default function HomePage() {
</ol>
</div>
<div>
<h4 className="mb-2 text-sm font-medium">Deploy self-hosted</h4>
<ol className="text-fd-muted-foreground list-inside list-decimal space-y-1 text-sm">
<h4 className="mb-2 font-medium text-sm">Deploy self-hosted</h4>
<ol className="list-inside list-decimal space-y-1 text-fd-muted-foreground text-sm">
<li>
<Link
href="/docs/self-hosting/getting-started/requirements"
@ -164,36 +151,36 @@ export default function HomePage() {
</div>
</div>
<div className="bg-fd-card/50 rounded-xl border p-6">
<div className="rounded-xl border bg-fd-card/50 p-6">
<h3 className="mb-4 flex items-center gap-2 font-semibold">
<BookOpenIcon className="text-fd-muted-foreground size-5" />
<BookOpenIcon className="size-5 text-fd-muted-foreground" />
Core Concepts
</h3>
<div className="grid grid-cols-2 gap-3">
<Link
href="/docs/concepts/document-lifecycle"
className="bg-fd-background hover:border-fd-primary/50 rounded-lg border p-3 text-sm transition-colors"
className="rounded-lg border bg-fd-background p-3 text-sm transition-colors hover:border-fd-primary/50"
>
<div className="mb-1 font-medium">Document Lifecycle</div>
<div className="text-fd-muted-foreground text-xs">Draft to completed</div>
</Link>
<Link
href="/docs/concepts/recipient-roles"
className="bg-fd-background hover:border-fd-primary/50 rounded-lg border p-3 text-sm transition-colors"
className="rounded-lg border bg-fd-background p-3 text-sm transition-colors hover:border-fd-primary/50"
>
<div className="mb-1 font-medium">Recipient Roles</div>
<div className="text-fd-muted-foreground text-xs">Signers and approvers</div>
</Link>
<Link
href="/docs/concepts/field-types"
className="bg-fd-background hover:border-fd-primary/50 rounded-lg border p-3 text-sm transition-colors"
className="rounded-lg border bg-fd-background p-3 text-sm transition-colors hover:border-fd-primary/50"
>
<div className="mb-1 font-medium">Field Types</div>
<div className="text-fd-muted-foreground text-xs">Signatures and inputs</div>
</Link>
<Link
href="/docs/concepts/signing-certificates"
className="bg-fd-background hover:border-fd-primary/50 rounded-lg border p-3 text-sm transition-colors"
className="rounded-lg border bg-fd-background p-3 text-sm transition-colors hover:border-fd-primary/50"
>
<div className="mb-1 font-medium">Signing Certificates</div>
<div className="text-fd-muted-foreground text-xs">Digital verification</div>
@ -206,7 +193,7 @@ export default function HomePage() {
<div className="mb-16 grid gap-4 md:grid-cols-2">
<Link
href="/docs/compliance"
className="bg-fd-card/50 hover:border-fd-primary/50 flex items-start gap-4 rounded-xl border p-5 transition-all"
className="flex items-start gap-4 rounded-xl border bg-fd-card/50 p-5 transition-all hover:border-fd-primary/50"
>
<div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-amber-500/10 text-amber-600 dark:text-amber-400">
<ShieldCheckIcon className="size-5" />
@ -221,7 +208,7 @@ export default function HomePage() {
<Link
href="/docs/policies"
className="bg-fd-card/50 hover:border-fd-primary/50 flex items-start gap-4 rounded-xl border p-5 transition-all"
className="flex items-start gap-4 rounded-xl border bg-fd-card/50 p-5 transition-all hover:border-fd-primary/50"
>
<div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-slate-500/10 text-slate-600 dark:text-slate-400">
<FileTextIcon className="size-5" />
@ -236,22 +223,22 @@ export default function HomePage() {
</div>
{/* Community CTA */}
<div className="from-fd-primary/5 to-fd-primary/10 rounded-xl border bg-gradient-to-r p-8 text-center">
<h3 className="mb-2 text-lg font-semibold">Join the Community</h3>
<p className="text-fd-muted-foreground mb-6 text-sm">
<div className="rounded-xl border bg-gradient-to-r from-fd-primary/5 to-fd-primary/10 p-8 text-center">
<h3 className="mb-2 font-semibold text-lg">Join the Community</h3>
<p className="mb-6 text-fd-muted-foreground text-sm">
Documenso is open source. Contribute, ask questions, or share feedback.
</p>
<div className="flex flex-wrap justify-center gap-3">
<a
href="https://github.com/documenso/documenso"
className="bg-fd-background hover:bg-fd-accent inline-flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-colors"
className="inline-flex items-center gap-2 rounded-lg border bg-fd-background px-4 py-2 font-medium text-sm transition-colors hover:bg-fd-accent"
>
<GithubIcon className="size-4" />
GitHub
</a>
<a
href="https://documen.so/discord"
className="bg-fd-background hover:bg-fd-accent inline-flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-colors"
className="inline-flex items-center gap-2 rounded-lg border bg-fd-background px-4 py-2 font-medium text-sm transition-colors hover:bg-fd-accent"
>
<svg className="size-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
@ -260,7 +247,7 @@ export default function HomePage() {
</a>
<a
href="https://app.documenso.com/signup"
className="bg-documenso text-fd-primary-foreground hover:bg-documenso/90 inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors"
className="inline-flex items-center gap-2 rounded-lg bg-documenso px-4 py-2 font-medium text-fd-primary-foreground text-sm transition-colors hover:bg-documenso/90"
>
Try Documenso
</a>

View file

@ -1,5 +1,5 @@
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';
import { source } from '@/lib/source';
export const { GET } = createFromSource(source, {
// https://docs.orama.com/docs/orama-js/supported-languages

View file

@ -1,10 +1,9 @@
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { LLMCopyButton, ViewOptions } from '@/components/ai/page-actions';
import { getPageImage, source } from '@/lib/source';
import { getMDXComponents } from '@/mdx-components';
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
const gitConfig = {
user: 'documenso',

View file

@ -1,16 +1,14 @@
'use client';
import { useMemo } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { cn } from '@/lib/cn';
import { baseOptions } from '@/lib/layout.shared';
import { getFilteredPageTree, source } from '@/lib/source';
import type * as PageTree from 'fumadocs-core/page-tree';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { CodeIcon, ServerIcon, UserIcon } from 'lucide-react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useMemo } from 'react';
import { cn } from '@/lib/cn';
import { baseOptions } from '@/lib/layout.shared';
import { getFilteredPageTree, source } from '@/lib/source';
const ROOT_SECTIONS = [
{
@ -44,7 +42,9 @@ function getFirstPageUrl(children: PageTree.Node[]): string | undefined {
}
if (child.type === 'folder' && child.children.length > 0) {
const url = getFirstPageUrl(child.children);
if (url) return url;
if (url) {
return url;
}
}
}
return undefined;
@ -69,13 +69,8 @@ function SectionSwitcher({ activeSection }: { activeSection: string | null }) {
>
<Icon className={cn('mt-0.5 size-4 shrink-0', isActive ? 'text-fd-primary' : '')} />
<div className="flex flex-col gap-0.5">
<span className="text-sm font-medium">{section.label}</span>
<span
className={cn(
'text-xs',
isActive ? 'text-fd-muted-foreground' : 'text-fd-muted-foreground/70',
)}
>
<span className="font-medium text-sm">{section.label}</span>
<span className={cn('text-xs', isActive ? 'text-fd-muted-foreground' : 'text-fd-muted-foreground/70')}>
{section.subtitle}
</span>
</div>

View file

@ -1,6 +1,6 @@
@import 'tailwindcss';
@import 'fumadocs-ui/css/shadcn.css';
@import 'fumadocs-ui/css/preset.css';
@import "tailwindcss";
@import "fumadocs-ui/css/shadcn.css";
@import "fumadocs-ui/css/preset.css";
@theme {
/* Brand utility colors */
@ -43,13 +43,11 @@
--sidebar-border: hsl(223.8136 0.0001% 89.8161%);
--sidebar-ring: hsl(223.8136 0% 63.0163%);
--font-sans:
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
--font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
--font-mono:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji";
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--radius: 0.5rem;
--shadow-x: 0;
--shadow-y: 1px;
@ -103,13 +101,11 @@
--sidebar-border: hsl(223.8136 0% 15.5096%);
--sidebar-ring: hsl(223.8136 0% 32.1993%);
--font-sans:
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
--font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
--font-mono:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji";
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--radius: 0.5rem;
--shadow-x: 0;
--shadow-y: 1px;

View file

@ -1,7 +1,6 @@
import { RootProvider } from 'fumadocs-ui/provider/next';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { RootProvider } from 'fumadocs-ui/provider/next';
import PlausibleProvider from 'next-plausible';
import './global.css';
@ -16,8 +15,7 @@ export const metadata: Metadata = {
template: '%s | Documenso Docs',
default: 'Documenso Docs',
},
description:
'The official documentation for Documenso, the open-source document signing platform.',
description: 'The official documentation for Documenso, the open-source document signing platform.',
openGraph: {
siteName: 'Documenso Docs',
type: 'website',

View file

@ -3,20 +3,20 @@ import Link from 'next/link';
export default function NotFound() {
return (
<main className="mx-auto flex max-w-xl flex-col items-center justify-center px-4 py-32 text-center">
<h1 className="text-4xl font-bold tracking-tight">Page not found</h1>
<p className="text-fd-muted-foreground mt-4 text-lg">
<h1 className="font-bold text-4xl tracking-tight">Page not found</h1>
<p className="mt-4 text-fd-muted-foreground text-lg">
The page you are looking for may have moved. Our documentation was recently restructured.
</p>
<div className="mt-8 flex flex-wrap justify-center gap-3">
<Link
href="/docs/users"
className="bg-documenso text-fd-primary-foreground hover:bg-documenso/90 inline-flex items-center rounded-lg px-5 py-2.5 text-sm font-medium transition-colors"
className="inline-flex items-center rounded-lg bg-documenso px-5 py-2.5 font-medium text-fd-primary-foreground text-sm transition-colors hover:bg-documenso/90"
>
Browse documentation
</Link>
<Link
href="/"
className="bg-fd-background hover:bg-fd-accent inline-flex items-center rounded-lg border px-5 py-2.5 text-sm font-medium transition-colors"
className="inline-flex items-center rounded-lg border bg-fd-background px-5 py-2.5 font-medium text-sm transition-colors hover:bg-fd-accent"
>
Go to homepage
</Link>

View file

@ -1,21 +1,16 @@
import { notFound } from 'next/navigation';
import { ImageResponse } from 'next/og';
import { getPageImage, source } from '@/lib/source';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { notFound } from 'next/navigation';
import { ImageResponse } from 'next/og';
import { getPageImage, source } from '@/lib/source';
export const revalidate = false;
const loadAssets = async () => {
const [logoBuffer, interRegularData, interSemiBoldData, interBoldData] = await Promise.all([
readFile(fileURLToPath(new URL('../../../../../public/logo.png', import.meta.url))),
readFile(
fileURLToPath(new URL('../../../../../public/fonts/inter-regular.ttf', import.meta.url)),
),
readFile(
fileURLToPath(new URL('../../../../../public/fonts/inter-semibold.ttf', import.meta.url)),
),
readFile(fileURLToPath(new URL('../../../../../public/fonts/inter-regular.ttf', import.meta.url))),
readFile(fileURLToPath(new URL('../../../../../public/fonts/inter-semibold.ttf', import.meta.url))),
readFile(fileURLToPath(new URL('../../../../../public/fonts/inter-bold.ttf', import.meta.url))),
]);
@ -40,104 +35,100 @@ export async function GET(_req: Request, { params }: RouteContext<'/og/docs/[...
const { logoSrc, fonts } = await loadAssets();
return new ImageResponse(
(
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
backgroundColor: 'white',
padding: '60px 80px',
fontFamily: 'Inter',
position: 'relative',
}}
>
{/* Green accent bar */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '6px',
backgroundColor: '#6DC947',
}}
/>
{/* Top: Logo */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={logoSrc} alt="Documenso" height="28" />
<span
style={{
color: '#D4D4D8',
fontSize: '28px',
fontWeight: 400,
}}
>
|
</span>
<span style={{ color: '#71717A', fontSize: '20px', fontWeight: 400 }}>Docs</span>
</div>
{/* Middle: Title + description */}
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
backgroundColor: 'white',
padding: '60px 80px',
fontFamily: 'Inter',
position: 'relative',
flex: 1,
justifyContent: 'center',
gap: '16px',
}}
>
{/* Green accent bar */}
<div
<h1
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '6px',
backgroundColor: '#6DC947',
}}
/>
{/* Top: Logo */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
color: '#18181B',
fontSize: page.data.title.length > 40 ? '48px' : '56px',
fontWeight: 700,
lineHeight: 1.15,
letterSpacing: '-0.025em',
margin: 0,
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={logoSrc} alt="Documenso" height="28" />
<span
{page.data.title}
</h1>
{page.data.description && (
<p
style={{
color: '#D4D4D8',
fontSize: '28px',
color: '#71717A',
fontSize: '22px',
fontWeight: 400,
}}
>
|
</span>
<span style={{ color: '#71717A', fontSize: '20px', fontWeight: 400 }}>Docs</span>
</div>
{/* Middle: Title + description */}
<div
style={{
display: 'flex',
flexDirection: 'column',
flex: 1,
justifyContent: 'center',
gap: '16px',
}}
>
<h1
style={{
color: '#18181B',
fontSize: page.data.title.length > 40 ? '48px' : '56px',
fontWeight: 700,
lineHeight: 1.15,
letterSpacing: '-0.025em',
lineHeight: 1.4,
margin: 0,
maxWidth: '900px',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
}}
>
{page.data.title}
</h1>
{page.data.description && (
<p
style={{
color: '#71717A',
fontSize: '22px',
fontWeight: 400,
lineHeight: 1.4,
margin: 0,
maxWidth: '900px',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
}}
>
{page.data.description}
</p>
)}
</div>
{/* Bottom: URL */}
<div style={{ display: 'flex', alignItems: 'center' }}>
<span style={{ color: '#A1A1AA', fontSize: '16px', fontWeight: 400 }}>
docs.documenso.com{page.url}
</span>
</div>
{page.data.description}
</p>
)}
</div>
),
{/* Bottom: URL */}
<div style={{ display: 'flex', alignItems: 'center' }}>
<span style={{ color: '#A1A1AA', fontSize: '16px', fontWeight: 400 }}>docs.documenso.com{page.url}</span>
</div>
</div>,
{
width: 1200,
height: 630,

View file

@ -1,12 +1,11 @@
'use client';
import { useMemo, useState } from 'react';
import { cn } from '@/lib/cn';
import { buttonVariants } from 'fumadocs-ui/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from 'fumadocs-ui/components/ui/popover';
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button';
import { Check, ChevronDown, Copy, ExternalLinkIcon, MessageCircleIcon } from 'lucide-react';
import { useMemo, useState } from 'react';
import { cn } from '@/lib/cn';
const cache = new Map<string, string>();
@ -21,7 +20,9 @@ export function LLMCopyButton({
const [isLoading, setLoading] = useState(false);
const [checked, onClick] = useCopyButton(async () => {
const cached = cache.get(markdownUrl);
if (cached) return navigator.clipboard.writeText(cached);
if (cached) {
return navigator.clipboard.writeText(cached);
}
setLoading(true);
@ -48,7 +49,7 @@ export function LLMCopyButton({
buttonVariants({
color: 'secondary',
size: 'sm',
className: '[&_svg]:text-fd-muted-foreground gap-2 [&_svg]:size-3.5',
className: 'gap-2 [&_svg]:size-3.5 [&_svg]:text-fd-muted-foreground',
}),
)}
onClick={onClick}
@ -74,8 +75,7 @@ export function ViewOptions({
githubUrl: string;
}) {
const items = useMemo(() => {
const fullMarkdownUrl =
typeof window !== 'undefined' ? new URL(markdownUrl, window.location.origin) : 'loading';
const fullMarkdownUrl = typeof window !== 'undefined' ? new URL(markdownUrl, window.location.origin) : 'loading';
const q = `Read ${fullMarkdownUrl}, I want to ask questions about it.`;
return [
@ -96,12 +96,7 @@ export function ViewOptions({
q,
})}`,
icon: (
<svg
role="img"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<svg role="img" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<title>OpenAI</title>
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
</svg>
@ -113,12 +108,7 @@ export function ViewOptions({
q,
})}`,
icon: (
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<svg fill="currentColor" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>Anthropic</title>
<path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z" />
</svg>
@ -146,7 +136,7 @@ export function ViewOptions({
)}
>
Open
<ChevronDown className="text-fd-muted-foreground size-3.5" />
<ChevronDown className="size-3.5 text-fd-muted-foreground" />
</PopoverTrigger>
<PopoverContent className="flex flex-col">
{items.map((item) => (
@ -155,11 +145,11 @@ export function ViewOptions({
href={item.href}
rel="noreferrer noopener"
target="_blank"
className="hover:text-fd-accent-foreground hover:bg-fd-accent inline-flex items-center gap-2 rounded-lg p-2 text-sm [&_svg]:size-4"
className="inline-flex items-center gap-2 rounded-lg p-2 text-sm hover:bg-fd-accent hover:text-fd-accent-foreground [&_svg]:size-4"
>
{item.icon}
{item.title}
<ExternalLinkIcon className="text-fd-muted-foreground ms-auto size-3.5" />
<ExternalLinkIcon className="ms-auto size-3.5 text-fd-muted-foreground" />
</a>
))}
</PopoverContent>

View file

@ -1,8 +1,7 @@
'use client';
import { useEffect, useId, useRef, useState } from 'react';
import { useTheme } from 'next-themes';
import { useEffect, useId, useRef, useState } from 'react';
export const Mermaid = ({ chart }: { chart: string }) => {
const [mounted, setMounted] = useState(false);

View file

@ -1,7 +1,7 @@
import { docs } from 'fumadocs-mdx:collections/server';
import type * as PageTree from 'fumadocs-core/page-tree';
import { type InferPageType, loader } from 'fumadocs-core/source';
import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons';
import { docs } from 'fumadocs-mdx:collections/server';
// See https://fumadocs.dev/docs/headless/source-api for more info
export const source = loader({
@ -30,9 +30,7 @@ export function getFilteredPageTree(rootName: string): PageTree.Root {
// Find the main section folder
const rootFolder = fullTree.children.find(
(child): child is PageTree.Folder =>
child.type === 'folder' &&
typeof child.name === 'string' &&
child.name.toLowerCase() === rootName.toLowerCase(),
child.type === 'folder' && typeof child.name === 'string' && child.name.toLowerCase() === rootName.toLowerCase(),
);
if (!rootFolder) {
@ -42,9 +40,7 @@ export function getFilteredPageTree(rootName: string): PageTree.Root {
// Find shared section folders
const sharedFolders = fullTree.children.filter(
(child): child is PageTree.Folder =>
child.type === 'folder' &&
typeof child.name === 'string' &&
SHARED_SECTIONS.includes(child.name.toLowerCase()),
child.type === 'folder' && typeof child.name === 'string' && SHARED_SECTIONS.includes(child.name.toLowerCase()),
);
// Create separator for main section

View file

@ -1,7 +1,7 @@
import { Mermaid } from '@/components/mdx/mermaid';
import * as TabsComponents from 'fumadocs-ui/components/tabs';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
import { Mermaid } from '@/components/mdx/mermaid';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getMDXComponents(components?: MDXComponents): any {

View file

@ -2,11 +2,7 @@
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@ -20,12 +16,8 @@
"jsx": "react-jsx",
"incremental": true,
"paths": {
"@/*": [
"./src/*"
],
"fumadocs-mdx:collections/*": [
".source/*"
]
"@/*": ["./src/*"],
"fumadocs-mdx:collections/*": [".source/*"]
},
"plugins": [
{
@ -33,14 +25,6 @@
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
"exclude": ["node_modules"]
}

View file

@ -10,10 +10,7 @@ export type TransformedData = {
const FORMAT = 'MMM yyyy';
export const addZeroMonth = (
transformedData: TransformedData,
isCumulative = false,
): TransformedData => {
export const addZeroMonth = (transformedData: TransformedData, isCumulative = false): TransformedData => {
const result: TransformedData = {
labels: [...transformedData.labels],
datasets: transformedData.datasets.map((dataset) => ({

View file

@ -60,7 +60,9 @@ async function originHeadersFromReq(req: Request, origin: StaticOrigin | OriginF
const reqOrigin = req.headers.get('Origin') || undefined;
const value = typeof origin === 'function' ? await origin(reqOrigin, req) : origin;
if (!value) return;
if (!value) {
return;
}
return getOriginHeaders(reqOrigin, value);
}
@ -85,12 +87,17 @@ export default async function cors(req: Request, res: Response, options?: CorsOp
const { headers } = res;
const originHeaders = await originHeadersFromReq(req, opts.origin ?? false);
const mergeHeaders = (v: string, k: string) => {
if (k === 'Vary') headers.append(k, v);
else headers.set(k, v);
if (k === 'Vary') {
headers.append(k, v);
} else {
headers.set(k, v);
}
};
// If there's no origin we won't touch the response
if (!originHeaders) return res;
if (!originHeaders) {
return res;
}
originHeaders.forEach(mergeHeaders);
@ -98,9 +105,7 @@ export default async function cors(req: Request, res: Response, options?: CorsOp
headers.set('Access-Control-Allow-Credentials', 'true');
}
const exposed = Array.isArray(opts.exposedHeaders)
? opts.exposedHeaders.join(',')
: opts.exposedHeaders;
const exposed = Array.isArray(opts.exposedHeaders) ? opts.exposedHeaders.join(',') : opts.exposedHeaders;
if (exposed) {
headers.set('Access-Control-Expose-Headers', exposed);
@ -120,7 +125,9 @@ export default async function cors(req: Request, res: Response, options?: CorsOp
headers.set('Access-Control-Max-Age', String(opts.maxAge));
}
if (opts.preflightContinue) return res;
if (opts.preflightContinue) {
return res;
}
headers.set('Content-Length', '0');
return new Response(null, { status: opts.optionsSuccessStatus, headers });

View file

@ -1,8 +1,7 @@
import { kyselyPrisma, sql } from '@documenso/prisma';
import { DocumentStatus, EnvelopeType } from '@prisma/client';
import { DateTime } from 'luxon';
import { kyselyPrisma, sql } from '@documenso/prisma';
import { addZeroMonth } from '../add-zero-month';
export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative' = 'count') => {
@ -30,9 +29,7 @@ export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative'
datasets: [
{
label: type === 'count' ? 'Completed Documents per Month' : 'Total Completed Documents',
data: result
.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count)))
.reverse(),
data: result.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count))).reverse(),
},
],
};
@ -40,6 +37,4 @@ export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative'
return addZeroMonth(transformedData, type === 'cumulative');
};
export type GetCompletedDocumentsMonthlyResult = Awaited<
ReturnType<typeof getCompletedDocumentsMonthly>
>;
export type GetCompletedDocumentsMonthlyResult = Awaited<ReturnType<typeof getCompletedDocumentsMonthly>>;

View file

@ -1,6 +1,5 @@
import { DateTime } from 'luxon';
import { kyselyPrisma, sql } from '@documenso/prisma';
import { DateTime } from 'luxon';
import { addZeroMonth } from '../add-zero-month';
@ -29,9 +28,7 @@ export const getSignerConversionMonthly = async (type: 'count' | 'cumulative' =
datasets: [
{
label: type === 'count' ? 'Signers That Signed Up' : 'Total Signers That Signed Up',
data: result
.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count)))
.reverse(),
data: result.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count))).reverse(),
},
],
};
@ -39,6 +36,4 @@ export const getSignerConversionMonthly = async (type: 'count' | 'cumulative' =
return addZeroMonth(transformedData, type === 'cumulative');
};
export type GetSignerConversionMonthlyResult = Awaited<
ReturnType<typeof getSignerConversionMonthly>
>;
export type GetSignerConversionMonthlyResult = Awaited<ReturnType<typeof getSignerConversionMonthly>>;

View file

@ -1,6 +1,5 @@
import { DateTime } from 'luxon';
import { kyselyPrisma, sql } from '@documenso/prisma';
import { DateTime } from 'luxon';
import { addZeroMonth } from '../add-zero-month';
@ -26,9 +25,7 @@ export const getUserMonthlyGrowth = async (type: 'count' | 'cumulative' = 'count
datasets: [
{
label: type === 'count' ? 'New Users' : 'Total Users',
data: result
.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count)))
.reverse(),
data: result.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count))).reverse(),
},
],
};

View file

@ -1,6 +1,6 @@
import { DateTime } from 'luxon';
import { type TransformedData, addZeroMonth } from './add-zero-month';
import { addZeroMonth, type TransformedData } from './add-zero-month';
type MetricKeys = {
stars: number;
@ -24,13 +24,7 @@ const FRIENDLY_METRIC_NAMES: { [key in MetricKey]: string } = {
earlyAdopters: 'Customers',
};
export function transformData({
data,
metric,
}: {
data: DataEntry;
metric: MetricKey;
}): TransformedData {
export function transformData({ data, metric }: { data: DataEntry; metric: MetricKey }): TransformedData {
try {
if (!data || Object.keys(data).length === 0) {
return {

View file

@ -1,9 +1,9 @@
@import '@documenso/ui/styles/theme.css';
@import "@documenso/ui/styles/theme.css";
/* Inter Variable Fonts */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-variablefont_opsz,wght.ttf') format('truetype-variations');
font-family: "Inter";
src: url("/fonts/inter-variablefont_opsz,wght.ttf") format("truetype-variations");
font-weight: 100 900;
font-style: normal;
font-display: swap;
@ -11,8 +11,8 @@
/* Inter Italic Variable Fonts */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-italic-variablefont_opsz,wght.ttf') format('truetype-variations');
font-family: "Inter";
src: url("/fonts/inter-italic-variablefont_opsz,wght.ttf") format("truetype-variations");
font-weight: 100 900;
font-style: italic;
font-display: swap;
@ -20,16 +20,16 @@
/* Caveat Variable Font */
@font-face {
font-family: 'Caveat';
src: url('/fonts/caveat-variablefont_wght.ttf') format('truetype-variations');
font-family: "Caveat";
src: url("/fonts/caveat-variablefont_wght.ttf") format("truetype-variations");
font-weight: 400 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Noto Sans';
src: url('/fonts/noto-sans.ttf') format('truetype-variations');
font-family: "Noto Sans";
src: url("/fonts/noto-sans.ttf") format("truetype-variations");
font-weight: 100 900;
font-style: normal;
font-display: swap;
@ -37,8 +37,8 @@
/* Korean noto sans */
@font-face {
font-family: 'Noto Sans Korean';
src: url('/fonts/noto-sans-korean.ttf') format('truetype-variations');
font-family: "Noto Sans Korean";
src: url("/fonts/noto-sans-korean.ttf") format("truetype-variations");
font-weight: 100 900;
font-style: normal;
font-display: swap;
@ -46,8 +46,8 @@
/* Japanese noto sans */
@font-face {
font-family: 'Noto Sans Japanese';
src: url('/fonts/noto-sans-japanese.ttf') format('truetype-variations');
font-family: "Noto Sans Japanese";
src: url("/fonts/noto-sans-japanese.ttf") format("truetype-variations");
font-weight: 100 900;
font-style: normal;
font-display: swap;
@ -55,8 +55,8 @@
/* Chinese noto sans */
@font-face {
font-family: 'Noto Sans Chinese';
src: url('/fonts/noto-sans-chinese.ttf') format('truetype-variations');
font-family: "Noto Sans Chinese";
src: url("/fonts/noto-sans-chinese.ttf") format("truetype-variations");
font-weight: 100 900;
font-style: normal;
font-display: swap;
@ -64,8 +64,8 @@
@layer base {
:root {
--font-sans: 'Inter';
--font-signature: 'Caveat';
--font-noto: 'Noto Sans', 'Noto Sans Korean', 'Noto Sans Japanese', 'Noto Sans Chinese';
--font-sans: "Inter";
--font-signature: "Caveat";
--font-noto: "Noto Sans", "Noto Sans Korean", "Noto Sans Japanese", "Noto Sans Chinese";
}
}

View file

@ -1,9 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { authClient } from '@documenso/auth/client';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { trpc } from '@documenso/trpc/react';
@ -21,6 +15,10 @@ import {
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
export type AccountDeleteDialogProps = {
className?: string;
@ -36,8 +34,7 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
const [enteredEmail, setEnteredEmail] = useState<string>('');
const { mutateAsync: deleteAccount, isPending: isDeletingAccount } =
trpc.profile.deleteAccount.useMutation();
const { mutateAsync: deleteAccount, isPending: isDeletingAccount } = trpc.profile.deleteAccount.useMutation();
const onDeleteAccount = async () => {
try {
@ -63,18 +60,15 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
return (
<div className={className}>
<Alert
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
variant="neutral"
>
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
<div>
<AlertTitle>
<Trans>Delete Account</Trans>
</AlertTitle>
<AlertDescription className="mr-2">
<Trans>
Delete your account and all its contents, including completed documents. This action
is irreversible and will cancel your subscription, so proceed with caution.
Delete your account and all its contents, including completed documents. This action is irreversible and
will cancel your subscription, so proceed with caution.
</Trans>
</AlertDescription>
</div>
@ -109,10 +103,8 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
<DialogDescription>
<Trans>
Documenso will delete{' '}
<span className="font-semibold">all of your documents</span>, along with all of
your completed documents, signatures, and all other resources belonging to your
Account.
Documenso will delete <span className="font-semibold">all of your documents</span>, along with all
of your completed documents, signatures, and all other resources belonging to your Account.
</Trans>
</DialogDescription>
</DialogHeader>
@ -121,9 +113,7 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
<div>
<Label>
<Trans>
Please type{' '}
<span className="text-muted-foreground font-semibold">{user.email}</span> to
confirm.
Please type <span className="font-semibold text-muted-foreground">{user.email}</span> to confirm.
</Trans>
</Label>

View file

@ -1,10 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useNavigate } from 'react-router';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
@ -19,6 +12,11 @@ import {
} from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
import { useNavigate } from 'react-router';
export type AdminDocumentDeleteDialogProps = {
envelopeId: string;
@ -32,8 +30,7 @@ export const AdminDocumentDeleteDialog = ({ envelopeId }: AdminDocumentDeleteDia
const [reason, setReason] = useState('');
const { mutateAsync: deleteDocument, isPending: isDeletingDocument } =
trpc.admin.document.delete.useMutation();
const { mutateAsync: deleteDocument, isPending: isDeletingDocument } = trpc.admin.document.delete.useMutation();
const handleDeleteDocument = async () => {
try {
@ -64,18 +61,13 @@ export const AdminDocumentDeleteDialog = ({ envelopeId }: AdminDocumentDeleteDia
return (
<div>
<div>
<Alert
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
variant="neutral"
>
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
<div>
<AlertTitle>
<Trans>Delete Document</Trans>
</AlertTitle>
<AlertDescription className="mr-2">
<Trans>
Delete the document. This action is irreversible so proceed with caution.
</Trans>
<Trans>Delete the document. This action is irreversible so proceed with caution.</Trans>
</AlertDescription>
</div>
@ -105,12 +97,7 @@ export const AdminDocumentDeleteDialog = ({ envelopeId }: AdminDocumentDeleteDia
<Trans>To confirm, please enter the reason</Trans>
</DialogDescription>
<Input
className="mt-2"
type="text"
value={reason}
onChange={(e) => setReason(e.target.value)}
/>
<Input className="mt-2" type="text" value={reason} onChange={(e) => setReason(e.target.value)} />
</div>
<DialogFooter>

View file

@ -1,13 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import type { z } from 'zod';
import { AppError } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZCreateAdminOrganisationRequestSchema } from '@documenso/trpc/server/admin-router/create-admin-organisation.types';
@ -22,16 +12,16 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import type { z } from 'zod';
export type OrganisationCreateDialogProps = {
trigger?: React.ReactNode;
@ -44,11 +34,7 @@ const ZCreateAdminOrganisationFormSchema = ZCreateAdminOrganisationRequestSchema
type TCreateOrganisationFormSchema = z.infer<typeof ZCreateAdminOrganisationFormSchema>;
export const AdminOrganisationCreateDialog = ({
trigger,
ownerUserId,
...props
}: OrganisationCreateDialogProps) => {
export const AdminOrganisationCreateDialog = ({ trigger, ownerUserId, ...props }: OrganisationCreateDialogProps) => {
const { t } = useLingui();
const { toast } = useToast();
@ -101,11 +87,7 @@ export const AdminOrganisationCreateDialog = ({
}, [open, form]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
@ -127,10 +109,7 @@ export const AdminOrganisationCreateDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@ -149,10 +128,7 @@ export const AdminOrganisationCreateDialog = ({
<Alert variant="neutral">
<AlertDescription className="mt-0">
<Trans>
You will need to configure any claims or subscription after creating this
organisation
</Trans>
<Trans>You will need to configure any claims or subscription after creating this organisation</Trans>
</AlertDescription>
</Alert>

View file

@ -1,15 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { OrganisationMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { getHighestOrganisationRoleInGroup } from '@documenso/lib/utils/organisations';
import { trpc } from '@documenso/trpc/react';
import type { TGetAdminOrganisationResponse } from '@documenso/trpc/server/admin-router/get-admin-organisation.types';
@ -23,22 +11,18 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
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 { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { OrganisationMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { match } from 'ts-pattern';
import { z } from 'zod';
export type AdminOrganisationMemberUpdateDialogProps = {
trigger?: React.ReactNode;
@ -69,9 +53,7 @@ export const AdminOrganisationMemberUpdateDialog = ({
// Determine the current role value for the form
const currentRoleValue = isOwner
? 'OWNER'
: getHighestOrganisationRoleInGroup(
organisationMember.organisationGroupMembers.map((ogm) => ogm.group),
);
: getHighestOrganisationRoleInGroup(organisationMember.organisationGroupMembers.map((ogm) => ogm.group));
const organisationMemberName = organisationMember.user.name ?? organisationMember.user.email;
const form = useForm<ZUpdateOrganisationMemberSchema>({
@ -81,8 +63,7 @@ export const AdminOrganisationMemberUpdateDialog = ({
},
});
const { mutateAsync: updateOrganisationMemberRole } =
trpc.admin.organisationMember.updateRole.useMutation();
const { mutateAsync: updateOrganisationMemberRole } = trpc.admin.organisationMember.updateRole.useMutation();
const onFormSubmit = async ({ role }: ZUpdateOrganisationMemberSchema) => {
try {
@ -135,11 +116,7 @@ export const AdminOrganisationMemberUpdateDialog = ({
}, [open, currentRoleValue, form]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger ?? (
<Button variant="secondary">
@ -156,8 +133,7 @@ export const AdminOrganisationMemberUpdateDialog = ({
<DialogDescription className="mt-4">
<Trans>
You are currently updating <span className="font-bold">{organisationMemberName}</span>
.
You are currently updating <span className="font-bold">{organisationMemberName}</span>.
</Trans>
</DialogDescription>
</DialogHeader>

View file

@ -1,8 +1,3 @@
import { useEffect, useMemo, useState } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { AppError } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
@ -15,14 +10,10 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { useEffect, useMemo, useState } from 'react';
export type AdminSwapSubscriptionDialogProps = {
open: boolean;
@ -68,8 +59,7 @@ export const AdminSwapSubscriptionDialog = ({
}
const hasActiveSubscription =
org.subscription &&
(org.subscription.status === 'ACTIVE' || org.subscription.status === 'PAST_DUE');
org.subscription && (org.subscription.status === 'ACTIVE' || org.subscription.status === 'PAST_DUE');
return !hasActiveSubscription;
});
@ -133,15 +123,14 @@ export const AdminSwapSubscriptionDialog = ({
<DialogDescription>
<Trans>
Move the subscription from "{sourceOrganisationName}" to another organisation owned by
this user.
Move the subscription from "{sourceOrganisationName}" to another organisation owned by this user.
</Trans>
</DialogDescription>
</DialogHeader>
<fieldset className="flex flex-col space-y-4" disabled={isSubmitting}>
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">
<label className="font-medium text-sm">
<Trans>Target Organisation</Trans>
</label>
@ -159,7 +148,7 @@ export const AdminSwapSubscriptionDialog = ({
</Select>
{eligibleOrgs.length === 0 && orgsData && (
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>No eligible organisations found. The target must be on the free plan.</Trans>
</p>
)}
@ -169,8 +158,8 @@ export const AdminSwapSubscriptionDialog = ({
<Alert variant="warning">
<AlertDescription className="mt-0">
<Trans>
This will move the subscription from "{sourceOrganisationName}" to "
{selectedOrg.name}". The source organisation will be reset to the free plan.
This will move the subscription from "{sourceOrganisationName}" to "{selectedOrg.name}". The source
organisation will be reset to the free plan.
</Trans>
</AlertDescription>
</Alert>
@ -181,12 +170,7 @@ export const AdminSwapSubscriptionDialog = ({
<Trans>Cancel</Trans>
</Button>
<Button
type="button"
onClick={onSubmit}
disabled={!selectedOrgId}
loading={isSubmitting}
>
<Button type="button" onClick={onSubmit} disabled={!selectedOrgId} loading={isSubmitting}>
<Trans>Move Subscription</Trans>
</Button>
</DialogFooter>

View file

@ -1,11 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useNavigate } from 'react-router';
import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
@ -22,6 +14,12 @@ import {
} from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
import { useNavigate } from 'react-router';
import { match } from 'ts-pattern';
export type AdminUserDeleteDialogProps = {
className?: string;
@ -34,8 +32,7 @@ export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialog
const navigate = useNavigate();
const [email, setEmail] = useState('');
const { mutateAsync: deleteUser, isPending: isDeletingUser } =
trpc.admin.user.delete.useMutation();
const { mutateAsync: deleteUser, isPending: isDeletingUser } = trpc.admin.user.delete.useMutation();
const onDeleteAccount = async () => {
try {
@ -69,16 +66,13 @@ export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialog
return (
<div className={className}>
<Alert
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
variant="neutral"
>
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
<div>
<AlertTitle>Delete Account</AlertTitle>
<AlertDescription className="mr-2">
<Trans>
Delete the users account and all its contents. This action is irreversible and will
cancel their subscription, so proceed with caution.
Delete the users account and all its contents. This action is irreversible and will cancel their
subscription, so proceed with caution.
</Trans>
</AlertDescription>
</div>
@ -111,12 +105,7 @@ export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialog
</Trans>
</DialogDescription>
<Input
className="mt-2"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Input className="mt-2" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<DialogFooter>

View file

@ -1,10 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
@ -21,23 +14,24 @@ import {
} from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
import { match } from 'ts-pattern';
export type AdminUserDisableDialogProps = {
className?: string;
userToDisable: TGetUserResponse;
};
export const AdminUserDisableDialog = ({
className,
userToDisable,
}: AdminUserDisableDialogProps) => {
export const AdminUserDisableDialog = ({ className, userToDisable }: AdminUserDisableDialogProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const [email, setEmail] = useState('');
const { mutateAsync: disableUser, isPending: isDisablingUser } =
trpc.admin.user.disable.useMutation();
const { mutateAsync: disableUser, isPending: isDisablingUser } = trpc.admin.user.disable.useMutation();
const onDisableAccount = async () => {
try {
@ -69,16 +63,13 @@ export const AdminUserDisableDialog = ({
return (
<div className={className}>
<Alert
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
variant="neutral"
>
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
<div>
<AlertTitle>Disable Account</AlertTitle>
<AlertDescription className="mr-2">
<Trans>
Disabling the user results in the user not being able to use the account. It also
disables all the related contents such as subscription, webhooks, teams, and API keys.
Disabling the user results in the user not being able to use the account. It also disables all the related
contents such as subscription, webhooks, teams, and API keys.
</Trans>
</AlertDescription>
</div>
@ -100,9 +91,8 @@ export const AdminUserDisableDialog = ({
<Alert variant="destructive">
<AlertDescription className="selection:bg-red-100">
<Trans>
This action is reversible, but please be careful as the account may be
affected permanently (e.g. their settings and contents not being restored
properly).
This action is reversible, but please be careful as the account may be affected permanently (e.g.
their settings and contents not being restored properly).
</Trans>
</AlertDescription>
</Alert>
@ -116,12 +106,7 @@ export const AdminUserDisableDialog = ({
</Trans>
</DialogDescription>
<Input
className="mt-2"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Input className="mt-2" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<DialogFooter>

View file

@ -1,10 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
@ -21,6 +14,11 @@ import {
} from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
import { match } from 'ts-pattern';
export type AdminUserEnableDialogProps = {
className?: string;
@ -33,8 +31,7 @@ export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnab
const [email, setEmail] = useState('');
const { mutateAsync: enableUser, isPending: isEnablingUser } =
trpc.admin.user.enable.useMutation();
const { mutateAsync: enableUser, isPending: isEnablingUser } = trpc.admin.user.enable.useMutation();
const onEnableAccount = async () => {
try {
@ -66,16 +63,13 @@ export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnab
return (
<div className={className}>
<Alert
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
variant="neutral"
>
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
<div>
<AlertTitle>Enable Account</AlertTitle>
<AlertDescription className="mr-2">
<Trans>
Enabling the account results in the user being able to use the account again, and all
the related features such as webhooks, teams, and API keys for example.
Enabling the account results in the user being able to use the account again, and all the related features
such as webhooks, teams, and API keys for example.
</Trans>
</AlertDescription>
</div>
@ -103,20 +97,11 @@ export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnab
</Trans>
</DialogDescription>
<Input
className="mt-2"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Input className="mt-2" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<DialogFooter>
<Button
onClick={onEnableAccount}
loading={isEnablingUser}
disabled={email !== userToEnable.email}
>
<Button onClick={onEnableAccount} loading={isEnablingUser} disabled={email !== userToEnable.email}>
<Trans>Enable account</Trans>
</Button>
</DialogFooter>

View file

@ -1,11 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useRevalidator } from 'react-router';
import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
@ -22,24 +14,26 @@ import {
} from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
import { useRevalidator } from 'react-router';
import { match } from 'ts-pattern';
export type AdminUserResetTwoFactorDialogProps = {
className?: string;
user: TGetUserResponse;
};
export const AdminUserResetTwoFactorDialog = ({
className,
user,
}: AdminUserResetTwoFactorDialogProps) => {
export const AdminUserResetTwoFactorDialog = ({ className, user }: AdminUserResetTwoFactorDialogProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const { revalidate } = useRevalidator();
const [email, setEmail] = useState('');
const [open, setOpen] = useState(false);
const { mutateAsync: resetTwoFactor, isPending: isResettingTwoFactor } =
trpc.admin.user.resetTwoFactor.useMutation();
const { mutateAsync: resetTwoFactor, isPending: isResettingTwoFactor } = trpc.admin.user.resetTwoFactor.useMutation();
const onResetTwoFactor = async () => {
try {
@ -64,9 +58,7 @@ export const AdminUserResetTwoFactorDialog = ({
AppErrorCode.UNAUTHORIZED,
() => msg`You are not authorized to reset two factor authentcation for this user.`,
)
.otherwise(
() => msg`An error occurred while resetting two factor authentication for the user.`,
);
.otherwise(() => msg`An error occurred while resetting two factor authentication for the user.`);
toast({
title: _(msg`Error`),
@ -87,16 +79,13 @@ export const AdminUserResetTwoFactorDialog = ({
return (
<div className={className}>
<Alert
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
variant="neutral"
>
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
<div>
<AlertTitle>Reset Two Factor Authentication</AlertTitle>
<AlertDescription className="mr-2">
<Trans>
Reset the users two factor authentication. This action is irreversible and will
disable two factor authentication for the user.
Reset the users two factor authentication. This action is irreversible and will disable two factor
authentication for the user.
</Trans>
</AlertDescription>
</div>
@ -119,8 +108,7 @@ export const AdminUserResetTwoFactorDialog = ({
<Alert variant="destructive">
<AlertDescription className="selection:bg-red-100">
<Trans>
This action is irreversible. Please ensure you have informed the user before
proceeding.
This action is irreversible. Please ensure you have informed the user before proceeding.
</Trans>
</AlertDescription>
</Alert>
@ -132,12 +120,7 @@ export const AdminUserResetTwoFactorDialog = ({
</Trans>
</DialogDescription>
<Input
className="mt-2"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Input className="mt-2" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<DialogFooter>

View file

@ -1,20 +1,11 @@
import { useState } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
import { Trans, useLingui } from '@lingui/react/macro';
import { OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { useState } from 'react';
import { useCurrentTeam } from '~/providers/team';
@ -24,11 +15,7 @@ type AiFeaturesEnableDialogProps = {
onEnabled: () => void;
};
export const AiFeaturesEnableDialog = ({
open,
onOpenChange,
onEnabled,
}: AiFeaturesEnableDialogProps) => {
export const AiFeaturesEnableDialog = ({ open, onOpenChange, onEnabled }: AiFeaturesEnableDialogProps) => {
const { t } = useLingui();
const team = useCurrentTeam();
@ -71,11 +58,7 @@ export const AiFeaturesEnableDialog = ({
onOpenChange(false);
} catch (err) {
console.error('Failed to enable AI features', err);
setError(
err instanceof Error
? err.message
: t`We couldn't enable AI features right now. Please try again.`,
);
setError(err instanceof Error ? err.message : t`We couldn't enable AI features right now. Please try again.`);
}
};
@ -89,39 +72,38 @@ export const AiFeaturesEnableDialog = ({
</DialogHeader>
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>
Turn on AI detection to automatically find recipients and fields in your documents. AI
providers do not retain your data for training.
Turn on AI detection to automatically find recipients and fields in your documents. AI providers do not
retain your data for training.
</Trans>
</p>
<Alert variant="neutral">
<AlertDescription>
<Trans>
Your document content will be sent securely to our AI provider solely for detection
and will not be stored or used for training.
Your document content will be sent securely to our AI provider solely for detection and will not be
stored or used for training.
</Trans>
</AlertDescription>
</Alert>
{canEnableAiFeatures ? (
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>
You're an admin. You can enable AI features for this team right away. Everyone on
the team will see AI detection once enabled.
You're an admin. You can enable AI features for this team right away. Everyone on the team will see AI
detection once enabled.
</Trans>
</p>
) : (
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>
AI features are disabled for your team. Please ask your team owner or organisation
owner to enable them.
AI features are disabled for your team. Please ask your team owner or organisation owner to enable them.
</Trans>
</p>
)}
{error ? <p className="text-sm text-destructive">{error}</p> : null}
{error ? <p className="text-destructive text-sm">{error}</p> : null}
</div>
<DialogFooter>

View file

@ -1,29 +1,17 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { NormalizedFieldWithContext } from '@documenso/lib/server-only/ai/envelope/detect-fields/types';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
import { Label } from '@documenso/ui/primitives/label';
import { Textarea } from '@documenso/ui/primitives/textarea';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Plural, Trans } from '@lingui/react/macro';
import { CheckIcon, FormInputIcon, ShieldCheckIcon } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { NormalizedFieldWithContext } from '@documenso/lib/server-only/ai/envelope/detect-fields/types';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Label } from '@documenso/ui/primitives/label';
import { Textarea } from '@documenso/ui/primitives/textarea';
import {
AiApiError,
type DetectFieldsProgressEvent,
detectFields,
} from '../../../server/api/ai/detect-fields.client';
import { AiApiError, type DetectFieldsProgressEvent, detectFields } from '../../../server/api/ai/detect-fields.client';
import { AnimatedDocumentScanner } from '../general/animated-document-scanner';
type DialogState = 'PROMPT' | 'PROCESSING' | 'REVIEW' | 'ERROR' | 'RATE_LIMITED';
@ -171,20 +159,17 @@ export const AiFieldDetectionDialog = ({
</DialogHeader>
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>
We'll scan your document to find form fields like signature lines, text inputs,
checkboxes, and more. Detected fields will be suggested for you to review.
We'll scan your document to find form fields like signature lines, text inputs, checkboxes, and more.
Detected fields will be suggested for you to review.
</Trans>
</p>
<Alert className="flex items-center gap-2 space-y-0" variant="neutral">
<ShieldCheckIcon className="h-5 w-5 stroke-green-600" />
<AlertDescription className="mt-0">
<Trans>
Your document is processed securely using AI services that don't retain your
data.
</Trans>
<Trans>Your document is processed securely using AI services that don't retain your data.</Trans>
</AlertDescription>
</Alert>
@ -200,7 +185,7 @@ export const AiFieldDetectionDialog = ({
rows={2}
className="resize-none"
/>
<p className="text-xs text-muted-foreground">
<p className="text-muted-foreground text-xs">
<Trans>Help the AI assign fields to the right recipients.</Trans>
</p>
</div>
@ -231,7 +216,7 @@ export const AiFieldDetectionDialog = ({
<p className="mt-8 text-muted-foreground">{_(PROCESSING_MESSAGES[messageIndex])}</p>
{progress && (
<p className="mt-2 text-xs text-muted-foreground/60">
<p className="mt-2 text-muted-foreground/60 text-xs">
<Plural
value={progress.fieldsDetected}
one={
@ -248,7 +233,7 @@ export const AiFieldDetectionDialog = ({
</p>
)}
<p className="mt-2 max-w-[40ch] text-center text-xs text-muted-foreground/60">
<p className="mt-2 max-w-[40ch] text-center text-muted-foreground/60 text-xs">
<Trans>This can take a minute or two depending on the size of your document.</Trans>
</p>
@ -278,16 +263,16 @@ export const AiFieldDetectionDialog = ({
{detectedFields.length === 0 ? (
<div className="flex flex-col items-center py-8">
<FormInputIcon className="h-12 w-12 text-muted-foreground/50" />
<p className="mt-4 text-center text-sm text-muted-foreground">
<p className="mt-4 text-center text-muted-foreground text-sm">
<Trans>No fields were detected in your document.</Trans>
</p>
<p className="mt-1 text-center text-xs text-muted-foreground/70">
<p className="mt-1 text-center text-muted-foreground/70 text-xs">
<Trans>You can add fields manually in the editor.</Trans>
</p>
</div>
) : (
<>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Plural
value={detectedFields.length}
one="We found # field in your document."
@ -299,7 +284,7 @@ export const AiFieldDetectionDialog = ({
{fieldCountsByType.map(([type, count]) => (
<li key={type} className="flex items-center justify-between px-4 py-3">
<span className="text-sm">{_(FIELD_TYPE_LABELS[type]) || type}</span>
<span className="text-sm font-medium text-muted-foreground">{count}</span>
<span className="font-medium text-muted-foreground text-sm">{count}</span>
</li>
))}
</ul>
@ -314,7 +299,7 @@ export const AiFieldDetectionDialog = ({
{detectedFields.length > 0 && (
<Button type="button" onClick={onAddFields}>
<CheckIcon className="-ml-1 mr-2 h-4 w-4" />
<CheckIcon className="mr-2 -ml-1 h-4 w-4" />
<Trans>Add fields</Trans>
</Button>
)}
@ -331,11 +316,11 @@ export const AiFieldDetectionDialog = ({
</DialogHeader>
<div className="py-4">
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>Something went wrong while detecting fields.</Trans>
</p>
{error && <p className="mt-2 text-sm text-destructive">{error}</p>}
{error && <p className="mt-2 text-destructive text-sm">{error}</p>}
</div>
<DialogFooter>
@ -358,10 +343,8 @@ export const AiFieldDetectionDialog = ({
</DialogHeader>
<div className="py-4">
<p className="text-sm text-muted-foreground">
<Trans>
You've made too many detection requests. Please wait a minute before trying again.
</Trans>
<p className="text-muted-foreground text-sm">
<Trans>You've made too many detection requests. Please wait a minute before trying again.</Trans>
</p>
</div>

View file

@ -1,22 +1,14 @@
import { useCallback, useEffect, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Plural, Trans } from '@lingui/react/macro';
import { CheckIcon, ShieldCheckIcon, UserIcon, XIcon } from 'lucide-react';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { TDetectedRecipientSchema } from '@documenso/lib/server-only/ai/envelope/detect-recipients/schema';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Plural, Trans } from '@lingui/react/macro';
import { CheckIcon, ShieldCheckIcon, UserIcon, XIcon } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import {
AiApiError,
@ -146,20 +138,17 @@ export const AiRecipientDetectionDialog = ({
</DialogHeader>
<div>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>
We'll scan your document to find signature fields and identify who needs to sign.
Detected recipients will be suggested for you to review.
We'll scan your document to find signature fields and identify who needs to sign. Detected recipients
will be suggested for you to review.
</Trans>
</p>
<Alert className="mt-4 flex items-center gap-2 space-y-0" variant="neutral">
<ShieldCheckIcon className="h-5 w-5 stroke-green-600" />
<AlertDescription className="mt-0">
<Trans>
Your document is processed securely using AI services that don't retain your
data.
</Trans>
<Trans>Your document is processed securely using AI services that don't retain your data.</Trans>
</AlertDescription>
</Alert>
</div>
@ -189,7 +178,7 @@ export const AiRecipientDetectionDialog = ({
<p className="mt-8 text-muted-foreground">{_(PROCESSING_MESSAGES[messageIndex])}</p>
{progress && (
<p className="mt-2 text-xs text-muted-foreground/60">
<p className="mt-2 text-muted-foreground/60 text-xs">
<Plural
value={progress.recipientsDetected}
one={
@ -206,7 +195,7 @@ export const AiRecipientDetectionDialog = ({
</p>
)}
<p className="mt-2 max-w-[40ch] text-center text-xs text-muted-foreground/60">
<p className="mt-2 max-w-[40ch] text-center text-muted-foreground/60 text-xs">
<Trans>This can take a minute or two depending on the size of your document.</Trans>
</p>
@ -236,16 +225,16 @@ export const AiRecipientDetectionDialog = ({
{detectedRecipients.length === 0 ? (
<div className="flex flex-col items-center py-8">
<UserIcon className="h-12 w-12 text-muted-foreground/50" />
<p className="mt-4 text-center text-sm text-muted-foreground">
<p className="mt-4 text-center text-muted-foreground text-sm">
<Trans>No recipients were detected in your document.</Trans>
</p>
<p className="mt-1 text-center text-xs text-muted-foreground/70">
<p className="mt-1 text-center text-muted-foreground/70 text-xs">
<Trans>You can add recipients manually in the editor.</Trans>
</p>
</div>
) : (
<>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Plural
value={detectedRecipients.length}
one="We found # recipient in your document."
@ -265,13 +254,13 @@ export const AiRecipientDetectionDialog = ({
: '?'
}
primaryText={
<p className="text-sm font-medium text-foreground">
<p className="font-medium text-foreground text-sm">
{recipient.name || _(msg`Unknown name`)}
</p>
}
secondaryText={
<div className="text-xs text-muted-foreground">
<p className="italic text-muted-foreground/70">
<div className="text-muted-foreground text-xs">
<p className="text-muted-foreground/70 italic">
{recipient.email || _(msg`No email detected`)}
</p>
<p>{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}</p>
@ -304,7 +293,7 @@ export const AiRecipientDetectionDialog = ({
{detectedRecipients.length > 0 && (
<Button type="button" onClick={onAddRecipients}>
<CheckIcon className="-ml-1 mr-2 h-4 w-4" />
<CheckIcon className="mr-2 -ml-1 h-4 w-4" />
<Trans>Add recipients</Trans>
</Button>
)}
@ -321,11 +310,11 @@ export const AiRecipientDetectionDialog = ({
</DialogHeader>
<div className="py-4">
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>Something went wrong while detecting recipients.</Trans>
</p>
{error && <p className="mt-2 text-sm text-destructive">{error}</p>}
{error && <p className="mt-2 text-destructive text-sm">{error}</p>}
</div>
<DialogFooter>
@ -349,10 +338,8 @@ export const AiRecipientDetectionDialog = ({
</DialogHeader>
<div className="py-4">
<p className="text-sm text-muted-foreground">
<Trans>
You've made too many detection requests. Please wait a minute before trying again.
</Trans>
<p className="text-muted-foreground text-sm">
<Trans>You've made too many detection requests. Please wait a minute before trying again.</Trans>
</p>
</div>

View file

@ -1,10 +1,3 @@
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zEmail } from '@documenso/lib/utils/zod';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -15,15 +8,13 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { DocumentSigningDisclosure } from '../general/document-signing/document-signing-disclosure';
@ -104,9 +95,8 @@ export function AssistantConfirmationDialog({
</DialogTitle>
<DialogDescription>
<Trans>
Are you sure you want to complete the document? This action cannot be undone.
Please ensure that you have completed prefilling all relevant fields before
proceeding.
Are you sure you want to complete the document? This action cannot be undone. Please ensure that you
have completed prefilling all relevant fields before proceeding.
</Trans>
</DialogDescription>
</DialogHeader>
@ -116,7 +106,7 @@ export function AssistantConfirmationDialog({
<div className="mt-4 flex flex-col gap-4">
{!isEditingNextSigner && (
<div>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>
The next recipient to sign this document will be{' '}
<span className="font-semibold">{form.watch('name')}</span> (
@ -147,11 +137,7 @@ export function AssistantConfirmationDialog({
<Trans>Name</Trans>
</FormLabel>
<FormControl>
<Input
{...field}
className="mt-2"
placeholder={t`Enter the next signer's name`}
/>
<Input {...field} className="mt-2" placeholder={t`Enter the next signer's name`} />
</FormControl>
<FormMessage />

View file

@ -1,8 +1,3 @@
import { useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import type { z } from 'zod';
import type { TLicenseClaim } from '@documenso/lib/types/license';
import { generateDefaultSubscriptionClaim } from '@documenso/lib/utils/organisations-claims';
import { trpc } from '@documenso/trpc/react';
@ -18,6 +13,9 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import type { z } from 'zod';
import { SubscriptionClaimForm } from '../forms/subscription-claim-form';
@ -75,12 +73,7 @@ export const ClaimCreateDialog = ({ licenseFlags }: ClaimCreateDialogProps) => {
licenseFlags={licenseFlags}
formSubmitTrigger={
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setOpen(false)}
disabled={isPending}
>
<Button type="button" variant="outline" onClick={() => setOpen(false)} disabled={isPending}>
<Trans>Cancel</Trans>
</Button>

View file

@ -1,7 +1,3 @@
import { useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
@ -15,6 +11,8 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
export type ClaimDeleteDialogProps = {
claimId: string;
@ -23,12 +21,7 @@ export type ClaimDeleteDialogProps = {
trigger: React.ReactNode;
};
export const ClaimDeleteDialog = ({
claimId,
claimName,
claimLocked,
trigger,
}: ClaimDeleteDialogProps) => {
export const ClaimDeleteDialog = ({ claimId, claimName, claimLocked, trigger }: ClaimDeleteDialogProps) => {
const { t } = useLingui();
const { toast } = useToast();

View file

@ -1,7 +1,3 @@
import { useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import type { TLicenseClaim } from '@documenso/lib/types/license';
import { trpc } from '@documenso/trpc/react';
import type { TFindSubscriptionClaimsResponse } from '@documenso/trpc/server/admin-router/find-subscription-claims.types';
@ -16,6 +12,8 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { SubscriptionClaimForm } from '../forms/subscription-claim-form';
@ -74,12 +72,7 @@ export const ClaimUpdateDialog = ({ claim, trigger, licenseFlags }: ClaimUpdateD
licenseFlags={licenseFlags}
formSubmitTrigger={
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setOpen(false)}
disabled={isPending}
>
<Button type="button" variant="outline" onClick={() => setOpen(false)} disabled={isPending}>
<Trans>Cancel</Trans>
</Button>

View file

@ -1,15 +1,3 @@
import { useEffect, 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 * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Loader2, Search } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { FolderType } from '@documenso/lib/types/folder-type';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
@ -23,16 +11,19 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
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';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Loader2, Search } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
import { useCurrentTeam } from '~/providers/team';
@ -165,7 +156,7 @@ export const DocumentMoveToFolderDialog = ({
</DialogHeader>
<div className="relative">
<Search className="text-muted-foreground absolute left-2 top-3 h-4 w-4" />
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={_(msg`Search folders...`)}
value={searchTerm}
@ -219,7 +210,7 @@ export const DocumentMoveToFolderDialog = ({
))}
{searchTerm && filteredFolders?.length === 0 && (
<div className="text-muted-foreground px-2 py-2 text-center text-sm">
<div className="px-2 py-2 text-center text-muted-foreground text-sm">
<Trans>No folders found</Trans>
</div>
)}
@ -239,9 +230,7 @@ export const DocumentMoveToFolderDialog = ({
<Button
type="submit"
disabled={
isFoldersLoading || form.formState.isSubmitting || currentFolderId === null
}
disabled={isFoldersLoading || form.formState.isSubmitting || currentFolderId === null}
>
<Trans>Move</Trans>
</Button>

View file

@ -1,14 +1,3 @@
import { 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 Recipient, SigningStatus, type Team, type User } from '@prisma/client';
import { History } from 'lucide-react';
import { useForm, useWatch } from 'react-hook-form';
import * as z from 'zod';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
@ -27,14 +16,17 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { DropdownMenuItem } from '@documenso/ui/primitives/dropdown-menu';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel } from '@documenso/ui/primitives/form/form';
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';
import { Trans } from '@lingui/react/macro';
import { type Recipient, SigningStatus, type Team, type User } from '@prisma/client';
import { History } from 'lucide-react';
import { useState } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import * as z from 'zod';
import { useCurrentTeam } from '~/providers/team';
@ -141,10 +133,7 @@ export const DocumentResendDialog = ({ document, recipients }: DocumentResendDia
render={({ field: { value, onChange } }) => (
<>
{recipients.map((recipient) => (
<FormItem
key={recipient.id}
className="flex flex-row items-center justify-between gap-x-3"
>
<FormItem key={recipient.id} className="flex flex-row items-center justify-between gap-x-3">
<FormLabel
className={cn('my-2 flex items-center gap-2 font-normal', {
'opacity-50': !value.includes(recipient.id),
@ -183,7 +172,7 @@ export const DocumentResendDialog = ({ document, recipients }: DocumentResendDia
<DialogClose asChild>
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
className="flex-1 bg-black/5 hover:bg-black/10 dark:bg-muted dark:hover:bg-muted/80"
variant="secondary"
disabled={isSubmitting}
>

View file

@ -1,11 +1,3 @@
import { useEffect, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { DocumentStatus, EnvelopeType } from '@prisma/client';
import { P, match } from 'ts-pattern';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
@ -22,6 +14,11 @@ import {
} from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentStatus, EnvelopeType } from '@prisma/client';
import { useEffect, useState } from 'react';
import { match, P } from 'ts-pattern';
type EnvelopeDeleteDialogProps = {
id: string;
@ -130,13 +127,13 @@ export const EnvelopeDeleteDialog = ({
<AlertDescription>
{type === EnvelopeType.DOCUMENT ? (
<Trans>
Please note that this action is <strong>irreversible</strong>. Once confirmed,
this document will be permanently deleted.
Please note that this action is <strong>irreversible</strong>. Once confirmed, this document will
be permanently deleted.
</Trans>
) : (
<Trans>
Please note that this action is <strong>irreversible</strong>. Once confirmed,
this template will be permanently deleted.
Please note that this action is <strong>irreversible</strong>. Once confirmed, this template will
be permanently deleted.
</Trans>
)}
</AlertDescription>

View file

@ -1,16 +1,3 @@
import { useEffect, useMemo, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { DocumentDistributionMethod, DocumentStatus, EnvelopeType } from '@prisma/client';
import { AnimatePresence, motion } from 'framer-motion';
import { InfoIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { match } from 'ts-pattern';
import * as z from 'zod';
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
@ -32,27 +19,24 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
import { Textarea } from '@documenso/ui/primitives/textarea';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentDistributionMethod, DocumentStatus, EnvelopeType } from '@prisma/client';
import { AnimatePresence, motion } from 'framer-motion';
import { InfoIcon } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { match } from 'ts-pattern';
import * as z from 'zod';
export type EnvelopeDistributeDialogProps = {
onDistribute?: () => Promise<void>;
@ -66,10 +50,7 @@ export const ZEnvelopeDistributeFormSchema = z.object({
emailReplyTo: z.preprocess((val) => (val === '' ? undefined : val), zEmail().optional()),
subject: z.string(),
message: z.string(),
distributionMethod: z
.nativeEnum(DocumentDistributionMethod)
.optional()
.default(DocumentDistributionMethod.EMAIL),
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional().default(DocumentDistributionMethod.EMAIL),
}),
});
@ -100,8 +81,7 @@ export const EnvelopeDistributeDialog = ({
emailReplyTo: envelope.documentMeta?.emailReplyTo || undefined,
subject: envelope.documentMeta?.subject ?? '',
message: envelope.documentMeta?.message ?? '',
distributionMethod:
envelope.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
distributionMethod: envelope.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
},
},
resolver: zodResolver(ZEnvelopeDistributeFormSchema),
@ -114,16 +94,15 @@ export const EnvelopeDistributeDialog = ({
formState: { isSubmitting },
} = form;
const { data: emailData, isLoading: isLoadingEmails } =
trpc.enterprise.organisation.email.find.useQuery(
{
organisationId: organisation.id,
perPage: 100,
},
{
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
},
);
const { data: emailData, isLoading: isLoadingEmails } = trpc.enterprise.organisation.email.find.useQuery(
{
organisationId: organisation.id,
perPage: 100,
},
{
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
},
);
const emails = emailData?.data || [];
@ -153,9 +132,7 @@ export const EnvelopeDistributeDialog = ({
recipientAuth: recipient.authOptions,
});
return (
(auth.recipientAccessAuthRequired || auth.recipientActionAuthRequired) && !recipient.email
);
return (auth.recipientAccessAuthRequired || auth.recipientActionAuthRequired) && !recipient.email;
});
}, [recipientsWithIndex, envelope.authOptions]);
@ -310,14 +287,9 @@ export const EnvelopeDistributeDialog = ({
<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>
@ -346,8 +318,7 @@ export const EnvelopeDistributeDialog = ({
<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>
@ -367,8 +338,7 @@ export const EnvelopeDistributeDialog = ({
<FormItem>
<FormLabel>
<Trans>
Subject{' '}
<span className="text-muted-foreground">(Optional)</span>
Subject <span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
@ -387,8 +357,7 @@ export const EnvelopeDistributeDialog = ({
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>
Message{' '}
<span className="text-muted-foreground">(Optional)</span>
Message <span className="text-muted-foreground">(Optional)</span>
</Trans>
<Tooltip>
<TooltipTrigger type="button">
@ -422,15 +391,15 @@ export const EnvelopeDistributeDialog = ({
exit={{ opacity: 0, transition: { duration: 0.15 } }}
className="min-h-60 rounded-lg border"
>
<div className="py-24 text-center text-sm text-muted-foreground">
<div className="py-24 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>
@ -470,7 +439,7 @@ export const EnvelopeDistributeDialog = ({
<AlertDescription>
<Trans>The following signers are missing signature fields:</Trans>
<ul className="ml-2 mt-1 list-inside list-disc">
<ul className="mt-1 ml-2 list-inside list-disc">
{recipientsMissingSignatureFields.map((recipient) => (
<li key={recipient.id}>
{recipient.email || recipient.name || t`Recipient ${recipient.index + 1}`}
@ -483,7 +452,7 @@ export const EnvelopeDistributeDialog = ({
<AlertDescription>
<Trans>The following recipients require an email address:</Trans>
<ul className="ml-2 mt-1 list-inside list-disc">
<ul className="mt-1 ml-2 list-inside list-disc">
{recipientsMissingRequiredEmail.map((recipient) => (
<li key={recipient.id}>
{recipient.email || recipient.name || t`Recipient ${recipient.index + 1}`}

View file

@ -1,10 +1,3 @@
import { useState } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { DocumentStatus, type EnvelopeItem } from '@prisma/client';
import { DownloadIcon, FileTextIcon } from 'lucide-react';
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
@ -18,6 +11,10 @@ import {
} from '@documenso/ui/primitives/dialog';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentStatus, type EnvelopeItem } from '@prisma/client';
import { DownloadIcon, FileTextIcon } from 'lucide-react';
import { useState } from 'react';
type EnvelopeItemToDownload = Pick<EnvelopeItem, 'id' | 'envelopeId' | 'title' | 'order'>;
@ -54,24 +51,20 @@ export const EnvelopeDownloadDialog = ({
const generateDownloadKey = (envelopeItemId: string, version: 'original' | 'signed') =>
`${envelopeItemId}-${version}`;
const { data: envelopeItemsPayload, isLoading: isLoadingEnvelopeItems } =
trpc.envelope.item.getManyByToken.useQuery(
{
envelopeId,
access: token ? { type: 'recipient', token } : { type: 'user' },
},
{
initialData: initialEnvelopeItems ? { data: initialEnvelopeItems } : undefined,
enabled: open,
},
);
const { data: envelopeItemsPayload, isLoading: isLoadingEnvelopeItems } = trpc.envelope.item.getManyByToken.useQuery(
{
envelopeId,
access: token ? { type: 'recipient', token } : { type: 'user' },
},
{
initialData: initialEnvelopeItems ? { data: initialEnvelopeItems } : undefined,
enabled: open,
},
);
const envelopeItems = envelopeItemsPayload?.data || [];
const onDownload = async (
envelopeItem: EnvelopeItemToDownload,
version: 'original' | 'signed',
) => {
const onDownload = async (envelopeItem: EnvelopeItemToDownload, version: 'original' | 'signed') => {
const { id: envelopeItemId } = envelopeItem;
if (isDownloadingState[generateDownloadKey(envelopeItemId, version)]) {
@ -127,13 +120,9 @@ export const EnvelopeDownloadDialog = ({
</DialogHeader>
<div className="flex w-full flex-col gap-4 overflow-hidden">
{isLoadingEnvelopeItems ? (
<>
{Array.from({ length: 1 }).map((_, index) => (
<div
key={index}
className="border-border bg-card flex items-center gap-2 rounded-lg border p-4"
>
{isLoadingEnvelopeItems
? Array.from({ length: 1 }).map((_, index) => (
<div key={index} className="flex items-center gap-2 rounded-lg border border-border bg-card p-4">
<Skeleton className="h-10 w-10 flex-shrink-0 rounded-lg" />
<div className="flex w-full flex-col gap-2">
@ -143,62 +132,59 @@ export const EnvelopeDownloadDialog = ({
<Skeleton className="h-10 w-20 flex-shrink-0 rounded-lg" />
</div>
))}
</>
) : (
envelopeItems.map((item) => (
<div
key={item.id}
className="border-border bg-card hover:bg-accent/50 flex items-center gap-4 rounded-lg border p-4 transition-colors"
>
<div className="flex-shrink-0">
<div className="bg-primary/10 flex h-10 w-10 items-center justify-center rounded-lg">
<FileTextIcon className="text-primary h-5 w-5" />
))
: envelopeItems.map((item) => (
<div
key={item.id}
className="flex items-center gap-4 rounded-lg border border-border bg-card p-4 transition-colors hover:bg-accent/50"
>
<div className="flex-shrink-0">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
<FileTextIcon className="h-5 w-5 text-primary" />
</div>
</div>
</div>
<div className="min-w-0 flex-1">
{/* Todo: Envelopes - Fix overflow */}
<h4 className="text-foreground truncate text-sm font-medium" title={item.title}>
{item.title}
</h4>
<p className="text-muted-foreground mt-0.5 text-xs">
<Trans>PDF Document</Trans>
</p>
</div>
<div className="min-w-0 flex-1">
{/* Todo: Envelopes - Fix overflow */}
<h4 className="truncate font-medium text-foreground text-sm" title={item.title}>
{item.title}
</h4>
<p className="mt-0.5 text-muted-foreground text-xs">
<Trans>PDF Document</Trans>
</p>
</div>
<div className="flex flex-shrink-0 items-center gap-2">
<Button
variant="outline"
size="sm"
className="text-xs"
onClick={async () => onDownload(item, 'original')}
loading={isDownloadingState[generateDownloadKey(item.id, 'original')]}
>
{!isDownloadingState[generateDownloadKey(item.id, 'original')] && (
<DownloadIcon className="mr-2 h-4 w-4" />
)}
<Trans context="Original document (adjective)">Original</Trans>
</Button>
{envelopeStatus === DocumentStatus.COMPLETED && (
<div className="flex flex-shrink-0 items-center gap-2">
<Button
variant="default"
variant="outline"
size="sm"
className="text-xs"
onClick={async () => onDownload(item, 'signed')}
loading={isDownloadingState[generateDownloadKey(item.id, 'signed')]}
onClick={async () => onDownload(item, 'original')}
loading={isDownloadingState[generateDownloadKey(item.id, 'original')]}
>
{!isDownloadingState[generateDownloadKey(item.id, 'signed')] && (
{!isDownloadingState[generateDownloadKey(item.id, 'original')] && (
<DownloadIcon className="mr-2 h-4 w-4" />
)}
<Trans context="Signed document (adjective)">Signed</Trans>
<Trans context="Original document (adjective)">Original</Trans>
</Button>
)}
{envelopeStatus === DocumentStatus.COMPLETED && (
<Button
variant="default"
size="sm"
className="text-xs"
onClick={async () => onDownload(item, 'signed')}
loading={isDownloadingState[generateDownloadKey(item.id, 'signed')]}
>
{!isDownloadingState[generateDownloadKey(item.id, 'signed')] && (
<DownloadIcon className="mr-2 h-4 w-4" />
)}
<Trans context="Signed document (adjective)">Signed</Trans>
</Button>
)}
</div>
</div>
</div>
))
)}
))}
</div>
</DialogContent>
</Dialog>

View file

@ -1,9 +1,3 @@
import { useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { EnvelopeType } from '@prisma/client';
import { useNavigate } from 'react-router';
import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
@ -18,6 +12,10 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { EnvelopeType } from '@prisma/client';
import { useState } from 'react';
import { useNavigate } from 'react-router';
import { useCurrentTeam } from '~/providers/team';
@ -27,11 +25,7 @@ type EnvelopeDuplicateDialogProps = {
trigger?: React.ReactNode;
};
export const EnvelopeDuplicateDialog = ({
envelopeId,
envelopeType,
trigger,
}: EnvelopeDuplicateDialogProps) => {
export const EnvelopeDuplicateDialog = ({ envelopeId, envelopeType, trigger }: EnvelopeDuplicateDialogProps) => {
const navigate = useNavigate();
const [open, setOpen] = useState(false);
@ -43,23 +37,22 @@ export const EnvelopeDuplicateDialog = ({
const isDocument = envelopeType === EnvelopeType.DOCUMENT;
const { mutateAsync: duplicateEnvelope, isPending: isDuplicating } =
trpc.envelope.duplicate.useMutation({
onSuccess: async ({ id }) => {
toast({
title: isDocument ? t`Document Duplicated` : t`Template Duplicated`,
description: isDocument
? t`Your document has been successfully duplicated.`
: t`Your template has been successfully duplicated.`,
duration: 5000,
});
const { mutateAsync: duplicateEnvelope, isPending: isDuplicating } = trpc.envelope.duplicate.useMutation({
onSuccess: async ({ id }) => {
toast({
title: isDocument ? t`Document Duplicated` : t`Template Duplicated`,
description: isDocument
? t`Your document has been successfully duplicated.`
: t`Your template has been successfully duplicated.`,
duration: 5000,
});
const path = isDocument ? formatDocumentsPath(team.url) : formatTemplatesPath(team.url);
const path = isDocument ? formatDocumentsPath(team.url) : formatTemplatesPath(team.url);
await navigate(`${path}/${id}/edit`);
setOpen(false);
},
});
await navigate(`${path}/${id}/edit`);
setOpen(false);
},
});
const onDuplicate = async () => {
try {

View file

@ -1,8 +1,3 @@
import { useState } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
@ -16,6 +11,8 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
export type EnvelopeItemDeleteDialogProps = {
canItemBeDeleted: boolean;
@ -39,28 +36,27 @@ export const EnvelopeItemDeleteDialog = ({
const { t } = useLingui();
const { toast } = useToast();
const { mutateAsync: deleteEnvelopeItem, isPending: isDeleting } =
trpc.envelope.item.delete.useMutation({
onSuccess: () => {
toast({
title: t`Success`,
description: t`You have successfully removed this envelope item.`,
duration: 5000,
});
const { mutateAsync: deleteEnvelopeItem, isPending: isDeleting } = trpc.envelope.item.delete.useMutation({
onSuccess: () => {
toast({
title: t`Success`,
description: t`You have successfully removed this envelope item.`,
duration: 5000,
});
onDelete?.(envelopeItemId);
onDelete?.(envelopeItemId);
setOpen(false);
},
onError: () => {
toast({
title: t`An unknown error occurred`,
description: t`We encountered an unknown error while attempting to remove this envelope item. Please try again later.`,
variant: 'destructive',
duration: 10000,
});
},
});
setOpen(false);
},
onError: () => {
toast({
title: t`An unknown error occurred`,
description: t`We encountered an unknown error while attempting to remove this envelope item. Please try again later.`,
variant: 'destructive',
duration: 10000,
});
},
});
return (
<Dialog open={open} onOpenChange={(value) => !isDeleting && setOpen(value)}>
@ -74,16 +70,12 @@ export const EnvelopeItemDeleteDialog = ({
</DialogTitle>
<DialogDescription className="mt-4">
<Trans>
You are about to remove the following document and all associated fields
</Trans>
<Trans>You are about to remove the following document and all associated fields</Trans>
</DialogDescription>
</DialogHeader>
<Alert variant="neutral">
<AlertDescription className="text-center font-semibold">
{envelopeItemTitle}
</AlertDescription>
<AlertDescription className="text-center font-semibold">{envelopeItemTitle}</AlertDescription>
</Alert>
<fieldset disabled={isDeleting}>
@ -116,9 +108,7 @@ export const EnvelopeItemDeleteDialog = ({
</DialogTitle>
<DialogDescription className="mt-4">
<Trans>
You cannot delete this item because the document has been sent to recipients.
</Trans>
<Trans>You cannot delete this item because the document has been sent to recipients.</Trans>
</DialogDescription>
</DialogHeader>

View file

@ -1,13 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Plural, Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { AlertTriangleIcon, FileIcon, UploadIcon, XIcon } from 'lucide-react';
import { type FileRejection, useDropzone } from 'react-dropzone';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
@ -28,16 +18,17 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Plural, Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { AlertTriangleIcon, FileIcon, UploadIcon, XIcon } from 'lucide-react';
import { useEffect, useState } from 'react';
import { type FileRejection, useDropzone } from 'react-dropzone';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const ZEditEnvelopeItemFormSchema = z.object({
title: ZDocumentTitleSchema,
@ -66,9 +57,7 @@ export const EnvelopeItemEditDialog = ({
const { envelope, editorFields, setLocalEnvelope, isEmbedded } = useCurrentEnvelopeEditor();
const [isOpen, setIsOpen] = useState(false);
const [replacementFile, setReplacementFile] = useState<{ file: File; pageCount: number } | null>(
null,
);
const [replacementFile, setReplacementFile] = useState<{ file: File; pageCount: number } | null>(null);
const [isDropping, setIsDropping] = useState(false);
const form = useForm<TEditEnvelopeItemFormSchema>({
@ -82,9 +71,7 @@ export const EnvelopeItemEditDialog = ({
onSuccess: ({ data, fields }) => {
setLocalEnvelope({
envelopeItems: envelope.envelopeItems.map((item) =>
item.id === data.id
? { ...item, documentDataId: data.documentDataId, title: data.title }
: item,
item.id === data.id ? { ...item, documentDataId: data.documentDataId, title: data.title } : item,
),
});
@ -98,8 +85,7 @@ export const EnvelopeItemEditDialog = ({
const fieldsOnExcessPages =
replacementFile !== null
? envelope.fields.filter(
(field) =>
field.envelopeItemId === envelopeItem.id && field.page > replacementFile.pageCount,
(field) => field.envelopeItemId === envelopeItem.id && field.page > replacementFile.pageCount,
)
: [];
@ -222,11 +208,7 @@ export const EnvelopeItemEditDialog = ({
};
return (
<Dialog
{...props}
open={isOpen}
onOpenChange={(value) => !form.formState.isSubmitting && setIsOpen(value)}
>
<Dialog {...props} open={isOpen} onOpenChange={(value) => !form.formState.isSubmitting && setIsOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger}
</DialogTrigger>
@ -279,18 +261,12 @@ export const EnvelopeItemEditDialog = ({
<div className="flex min-w-0 items-center space-x-2">
<FileIcon className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
<div className="min-w-0">
<p className="truncate text-sm font-medium">
{replacementFile.file.name}
</p>
<p className="text-xs text-muted-foreground">
<p className="truncate font-medium text-sm">{replacementFile.file.name}</p>
<p className="text-muted-foreground text-xs">
{formatFileSize(replacementFile.file.size)}
{isDropping ? ' · …' : ' · '}
{!isDropping && replacementFile.pageCount !== null && (
<Plural
one="1 page"
other="# pages"
value={replacementFile.pageCount}
/>
<Plural one="1 page" other="# pages" value={replacementFile.pageCount} />
)}
</p>
</div>
@ -326,14 +302,14 @@ export const EnvelopeItemEditDialog = ({
data-testid="envelope-item-edit-dropzone"
{...getRootProps()}
className={cn(
'mt-1.5 flex cursor-pointer items-center justify-center rounded-md border border-dashed border-border px-4 py-4 transition-colors',
'mt-1.5 flex cursor-pointer items-center justify-center rounded-md border border-border border-dashed px-4 py-4 transition-colors',
isDragActive
? 'border-primary/50 bg-primary/5'
: 'hover:border-muted-foreground/50 hover:bg-muted/50',
)}
>
<input {...getInputProps()} />
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
<div className="flex items-center space-x-2 text-muted-foreground text-sm">
<UploadIcon className="h-4 w-4" />
<span>
<Trans>Drop PDF here or click to select</Trans>

View file

@ -1,13 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { DocumentStatus, EnvelopeType, type Recipient, SigningStatus } from '@prisma/client';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import type { TEnvelope } from '@documenso/lib/types/envelope';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
@ -25,14 +15,15 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel } from '@documenso/ui/primitives/form/form';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentStatus, EnvelopeType, type Recipient, SigningStatus } from '@prisma/client';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { StackAvatar } from '../general/stack-avatar';
@ -51,10 +42,7 @@ export const ZEnvelopeRedistributeFormSchema = z.object({
export type TEnvelopeRedistributeFormSchema = z.infer<typeof ZEnvelopeRedistributeFormSchema>;
export const EnvelopeRedistributeDialog = ({
envelope,
trigger,
}: EnvelopeRedistributeDialogProps) => {
export const EnvelopeRedistributeDialog = ({ envelope, trigger }: EnvelopeRedistributeDialogProps) => {
const recipients = envelope.recipients;
const { toast } = useToast();

View file

@ -1,20 +1,12 @@
import { useEffect, useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { DOCUMENT_TITLE_MAX_LENGTH } from '@documenso/trpc/server/document-router/schema';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { useEffect, useState } from 'react';
export type EnvelopeRenameDialogProps = {
id: string;
@ -89,9 +81,7 @@ export const EnvelopeRenameDialog = ({
<Dialog open={open} onOpenChange={(value) => !isPending && onOpenChange(value)}>
<DialogContent>
<DialogHeader>
<DialogTitle>
{isTemplate ? <Trans>Rename Template</Trans> : <Trans>Rename Document</Trans>}
</DialogTitle>
<DialogTitle>{isTemplate ? <Trans>Rename Template</Trans> : <Trans>Rename Document</Trans>}</DialogTitle>
</DialogHeader>
<div className="py-2">

View file

@ -1,9 +1,3 @@
import { useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
@ -20,6 +14,10 @@ import {
} from '@documenso/ui/primitives/dialog';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { useCurrentTeam } from '~/providers/team';
@ -28,10 +26,7 @@ type EnvelopeSaveAsTemplateDialogProps = {
trigger?: React.ReactNode;
};
export const EnvelopeSaveAsTemplateDialog = ({
envelopeId,
trigger,
}: EnvelopeSaveAsTemplateDialogProps) => {
export const EnvelopeSaveAsTemplateDialog = ({ envelopeId, trigger }: EnvelopeSaveAsTemplateDialogProps) => {
const navigate = useNavigate();
const [open, setOpen] = useState(false);
@ -146,10 +141,7 @@ export const EnvelopeSaveAsTemplateDialog = ({
disabled={!includeRecipients}
onCheckedChange={(checked) => field.onChange(checked === true)}
/>
<Label
htmlFor="envelopeIncludeFields"
className={!includeRecipients ? 'opacity-50' : ''}
>
<Label htmlFor="envelopeIncludeFields" className={!includeRecipients ? 'opacity-50' : ''}>
<Trans>Include Fields</Trans>
</Label>
</div>

View file

@ -1,9 +1,3 @@
import { plural } from '@lingui/core/macro';
import { Plural, useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { EnvelopeType } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
@ -16,6 +10,10 @@ import {
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { plural } from '@lingui/core/macro';
import { Plural, Trans, useLingui } from '@lingui/react/macro';
import { EnvelopeType } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
export type EnvelopesBulkDeleteDialogProps = {
envelopeIds: string[];
@ -88,9 +86,7 @@ export const EnvelopesBulkDeleteDialog = ({
<Dialog {...props} open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>
{isDocument ? <Trans>Delete Documents</Trans> : <Trans>Delete Templates</Trans>}
</DialogTitle>
<DialogTitle>{isDocument ? <Trans>Delete Documents</Trans> : <Trans>Delete Templates</Trans>}</DialogTitle>
<DialogDescription>
{isDocument ? (
@ -149,12 +145,7 @@ export const EnvelopesBulkDeleteDialog = ({
</Alert>
<DialogFooter>
<Button
type="button"
variant="secondary"
onClick={() => onOpenChange(false)}
disabled={isPending}
>
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)} disabled={isPending}>
<Trans>Cancel</Trans>
</Button>

View file

@ -1,15 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Plural, useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { EnvelopeType } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Loader2, Search } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
@ -21,16 +9,18 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Plural, Trans, useLingui } from '@lingui/react/macro';
import { EnvelopeType } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Loader2, Search } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { z } from 'zod';
export type EnvelopesBulkMoveDialogProps = {
envelopeIds: string[];
@ -119,10 +109,7 @@ export const EnvelopesBulkMoveDialog = ({
const error = AppError.parseError(err);
const errorMessage = match(error.code)
.with(
AppErrorCode.NOT_FOUND,
() => t`The folder you are trying to move the items to does not exist.`,
)
.with(AppErrorCode.NOT_FOUND, () => t`The folder you are trying to move the items to does not exist.`)
.with(AppErrorCode.UNAUTHORIZED, () => t`You are not allowed to move these items.`)
.with(AppErrorCode.INVALID_BODY, () => t`All items must be of the same type.`)
.otherwise(() => t`An error occurred while moving the items.`);
@ -143,11 +130,7 @@ export const EnvelopesBulkMoveDialog = ({
<DialogContent>
<DialogHeader>
<DialogTitle>
{isDocument ? (
<Trans>Move Documents to Folder</Trans>
) : (
<Trans>Move Templates to Folder</Trans>
)}
{isDocument ? <Trans>Move Documents to Folder</Trans> : <Trans>Move Templates to Folder</Trans>}
</DialogTitle>
<DialogDescription>
@ -168,7 +151,7 @@ export const EnvelopesBulkMoveDialog = ({
</DialogHeader>
<div className="relative">
<Search className="absolute left-2 top-3 h-4 w-4 text-muted-foreground" />
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t`Search folders...`}
value={searchTerm}
@ -222,7 +205,7 @@ export const EnvelopesBulkMoveDialog = ({
))}
{searchTerm && filteredFolders?.length === 0 && (
<div className="px-2 py-2 text-center text-sm text-muted-foreground">
<div className="px-2 py-2 text-center text-muted-foreground text-sm">
<Trans>No folders found</Trans>
</div>
)}

View file

@ -1,14 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type { FolderType } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderPlusIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { useParams } from 'react-router';
import { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -20,16 +9,18 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type { FolderType } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderPlusIcon } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useParams } from 'react-router';
import { z } from 'zod';
const ZCreateFolderFormSchema = z.object({
name: z.string().min(1, { message: 'Folder name is required' }),
@ -43,12 +34,7 @@ export type FolderCreateDialogProps = {
parentFolderId?: string | null;
} & Omit<DialogPrimitive.DialogProps, 'children'>;
export const FolderCreateDialog = ({
type,
trigger,
parentFolderId,
...props
}: FolderCreateDialogProps) => {
export const FolderCreateDialog = ({ type, trigger, parentFolderId, ...props }: FolderCreateDialogProps) => {
const { t } = useLingui();
const { toast } = useToast();
const { folderId } = useParams();
@ -98,11 +84,7 @@ export const FolderCreateDialog = ({
<Dialog {...props} open={isCreateFolderOpen} onOpenChange={setIsCreateFolderOpen}>
<DialogTrigger asChild>
{trigger ?? (
<Button
variant="outline"
className="flex items-center"
data-testid="folder-create-button"
>
<Button variant="outline" className="flex items-center" data-testid="folder-create-button">
<FolderPlusIcon className="mr-2 h-4 w-4" />
<Trans>Create Folder</Trans>
</Button>
@ -139,11 +121,7 @@ export const FolderCreateDialog = ({
/>
<DialogFooter>
<Button
type="button"
variant="secondary"
onClick={() => setIsCreateFolderOpen(false)}
>
<Button type="button" variant="secondary" onClick={() => setIsCreateFolderOpen(false)}>
<Trans>Cancel</Trans>
</Button>

View file

@ -1,12 +1,3 @@
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import type { TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema';
@ -20,16 +11,15 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export type FolderDeleteDialogProps = {
folder: TFolderWithSubfolders;
@ -110,14 +100,12 @@ export const FolderDeleteDialog = ({ folder, isOpen, onOpenChange }: FolderDelet
</DialogDescription>
</DialogHeader>
{(folder._count.documents > 0 ||
folder._count.templates > 0 ||
folder._count.subfolders > 0) && (
{(folder._count.documents > 0 || folder._count.templates > 0 || folder._count.subfolders > 0) && (
<Alert variant="destructive">
<AlertDescription>
<Trans>
This folder contains multiple items. Deleting it will remove all subfolders and move
all nested documents and templates to the root folder.
This folder contains multiple items. Deleting it will remove all subfolders and move all nested
documents and templates to the root folder.
</Trans>
</AlertDescription>
</Alert>
@ -134,9 +122,7 @@ export const FolderDeleteDialog = ({ folder, isOpen, onOpenChange }: FolderDelet
<FormLabel>
<Trans>
Confirm by typing:{' '}
<span className="font-sm text-destructive font-semibold">
{deleteMessage}
</span>
<span className="font-semibold font-sm text-destructive">{deleteMessage}</span>
</Trans>
</FormLabel>
<FormControl>

View file

@ -1,13 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Search } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import type { TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema';
@ -20,15 +10,16 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Search } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export type FolderMoveDialogProps = {
foldersData: TFolderWithSubfolders[] | undefined;
@ -43,12 +34,7 @@ const ZMoveFolderFormSchema = z.object({
type TMoveFolderFormSchema = z.infer<typeof ZMoveFolderFormSchema>;
export const FolderMoveDialog = ({
foldersData,
folder,
isOpen,
onOpenChange,
}: FolderMoveDialogProps) => {
export const FolderMoveDialog = ({ foldersData, folder, isOpen, onOpenChange }: FolderMoveDialogProps) => {
const { t } = useLingui();
const { toast } = useToast();
const [searchTerm, setSearchTerm] = useState('');
@ -129,7 +115,7 @@ export const FolderMoveDialog = ({
</DialogHeader>
<div className="relative">
<Search className="text-muted-foreground absolute left-2 top-3 h-4 w-4" />
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t`Search folders...`}
value={searchTerm}

View file

@ -1,12 +1,3 @@
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import { trpc } from '@documenso/trpc/react';
@ -21,23 +12,16 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { useOptionalCurrentTeam } from '~/providers/team';

View file

@ -1,17 +1,3 @@
import { useEffect, useMemo, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { ExternalLinkIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { Link, useSearchParams } from 'react-router';
import { match } from 'ts-pattern';
import type { z } from 'zod';
import type { InternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useSession } from '@documenso/lib/client-only/providers/session';
@ -34,18 +20,22 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { ExternalLinkIcon } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { Link, useSearchParams } from 'react-router';
import { match } from 'ts-pattern';
import type { z } from 'zod';
import { IndividualPersonalLayoutCheckoutButton } from '../general/billing-plans';
@ -70,9 +60,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
const actionSearchParam = searchParams?.get('action');
const [step, setStep] = useState<'billing' | 'create'>(
IS_BILLING_ENABLED() ? 'billing' : 'create',
);
const [step, setStep] = useState<'billing' | 'create'>(IS_BILLING_ENABLED() ? 'billing' : 'create');
const [selectedPriceId, setSelectedPriceId] = useState<string>('');
@ -145,11 +133,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
};
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
@ -215,10 +199,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@ -237,11 +218,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
<DialogFooter>
{IS_BILLING_ENABLED() ? (
<Button
type="button"
variant="secondary"
onClick={() => setStep('billing')}
>
<Button type="button" variant="secondary" onClick={() => setStep('billing')}>
<Trans>Back</Trans>
</Button>
) : (
@ -290,30 +267,23 @@ type BillingPlanFormProps = {
canCreateFreeOrganisation: boolean;
};
const BillingPlanForm = ({
value,
onChange,
plans,
canCreateFreeOrganisation,
}: BillingPlanFormProps) => {
const BillingPlanForm = ({ value, onChange, plans, canCreateFreeOrganisation }: BillingPlanFormProps) => {
const { t } = useLingui();
const [billingPeriod, setBillingPeriod] = useState<'monthlyPrice' | 'yearlyPrice'>('yearlyPrice');
const dynamicPlans = useMemo(() => {
return [INTERNAL_CLAIM_ID.INDIVIDUAL, INTERNAL_CLAIM_ID.TEAM, INTERNAL_CLAIM_ID.PLATFORM].map(
(planId) => {
const plan = plans[planId];
return [INTERNAL_CLAIM_ID.INDIVIDUAL, INTERNAL_CLAIM_ID.TEAM, INTERNAL_CLAIM_ID.PLATFORM].map((planId) => {
const plan = plans[planId];
return {
id: planId,
name: plan.name,
description: parseMessageDescriptorMacro(t, internalClaimsDescription[planId]),
monthlyPrice: plan.monthlyPrice,
yearlyPrice: plan.yearlyPrice,
};
},
);
return {
id: planId,
name: plan.name,
description: parseMessageDescriptorMacro(t, internalClaimsDescription[planId]),
monthlyPrice: plan.monthlyPrice,
yearlyPrice: plan.yearlyPrice,
};
});
}, [plans]);
useEffect(() => {
@ -357,9 +327,9 @@ const BillingPlanForm = ({
<button
onClick={() => onChange('')}
className={cn(
'hover:border-primary flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:shadow-sm',
'flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:border-primary hover:shadow-sm',
{
'ring-primary/10 border-primary ring-2 ring-offset-1': '' === value,
'border-primary ring-2 ring-primary/10 ring-offset-1': '' === value,
},
)}
disabled={!canCreateFreeOrganisation}
@ -390,10 +360,9 @@ const BillingPlanForm = ({
key={plan[billingPeriod]?.id}
onClick={() => onChange(plan[billingPeriod]?.id ?? '')}
className={cn(
'hover:border-primary flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:shadow-sm',
'flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:border-primary hover:shadow-sm',
{
'ring-primary/10 border-primary ring-2 ring-offset-1':
plan[billingPeriod]?.id === value,
'border-primary ring-2 ring-primary/10 ring-offset-1': plan[billingPeriod]?.id === value,
},
)}
>
@ -401,14 +370,10 @@ const BillingPlanForm = ({
<p className="font-medium">{plan.name}</p>
<p className="text-muted-foreground">{plan.description}</p>
</div>
<div className="whitespace-nowrap text-right text-sm font-medium">
<div className="whitespace-nowrap text-right font-medium text-sm">
<p>{plan[billingPeriod]?.friendlyPrice}</p>
<span className="text-muted-foreground text-xs">
{billingPeriod === 'monthlyPrice' ? (
<Trans>per month</Trans>
) : (
<Trans>per year</Trans>
)}
{billingPeriod === 'monthlyPrice' ? <Trans>per month</Trans> : <Trans>per year</Trans>}
</span>
</div>
</button>
@ -417,13 +382,13 @@ const BillingPlanForm = ({
<Link
to="https://documen.so/enterprise-cta"
target="_blank"
className="bg-muted/30 flex items-center space-x-2 rounded-md border p-4"
className="flex items-center space-x-2 rounded-md border bg-muted/30 p-4"
>
<div className="flex-1 font-normal">
<p className="text-muted-foreground font-medium">
<p className="font-medium text-muted-foreground">
<Trans>Enterprise</Trans>
</p>
<p className="text-muted-foreground flex flex-row items-center gap-1">
<p className="flex flex-row items-center gap-1 text-muted-foreground">
<Trans>Contact sales here</Trans>
<ExternalLinkIcon className="h-4 w-4" />
</p>
@ -434,7 +399,7 @@ const BillingPlanForm = ({
<div className="mt-6 text-center">
<Link
to="https://documenso.com/pricing"
className="text-primary hover:text-primary/80 flex items-center justify-center gap-1 text-sm hover:underline"
className="flex items-center justify-center gap-1 text-primary text-sm hover:text-primary/80 hover:underline"
target="_blank"
>
<Trans>Compare all plans and features in detail</Trans>

View file

@ -1,13 +1,3 @@
import { useEffect, 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 { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { AppError } from '@documenso/lib/errors/app-error';
@ -22,16 +12,17 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
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';
import { Trans } from '@lingui/react/macro';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
export type OrganisationDeleteDialogProps = {
trigger?: React.ReactNode;
@ -117,19 +108,16 @@ export const OrganisationDeleteDialog = ({ trigger }: OrganisationDeleteDialogPr
<DialogDescription>
<Trans>
You are about to delete <span className="font-semibold">{organisation.name}</span>.
All data related to this organisation such as teams, documents, and all other
resources will be deleted. This action is irreversible.
You are about to delete <span className="font-semibold">{organisation.name}</span>. All data related to
this organisation such as teams, documents, and all other resources will be deleted. This action is
irreversible.
</Trans>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="organisationName"

View file

@ -1,11 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZCreateOrganisationEmailRequestSchema } from '@documenso/trpc/server/enterprise-router/create-organisation-email.types';
@ -30,6 +22,12 @@ import {
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
type EmailDomain = {
id: string;
@ -69,8 +67,7 @@ export const OrganisationEmailCreateDialog = ({
},
});
const { mutateAsync: createOrganisationEmail, isPending } =
trpc.enterprise.organisation.email.create.useMutation();
const { mutateAsync: createOrganisationEmail, isPending } = trpc.enterprise.organisation.email.create.useMutation();
// Reset state when dialog closes
useEffect(() => {
@ -176,14 +173,14 @@ export const OrganisationEmailCreateDialog = ({
}}
placeholder={t`support`}
/>
<div className="bg-muted text-muted-foreground absolute bottom-0 right-0 top-0 flex items-center rounded-r-md border px-3 py-2 text-sm">
<div className="absolute top-0 right-0 bottom-0 flex items-center rounded-r-md border bg-muted px-3 py-2 text-muted-foreground text-sm">
@{emailDomain.domain}
</div>
</div>
</FormControl>
<FormMessage />
{!form.formState.errors.email && (
<span className="text-foreground/50 text-xs font-normal">
<span className="font-normal text-foreground/50 text-xs">
{field.value ? (
field.value
) : (
@ -225,11 +222,7 @@ export const OrganisationEmailCreateDialog = ({
<Trans>Cancel</Trans>
</Button>
<Button
type="submit"
data-testid="dialog-create-organisation-email-button"
loading={isPending}
>
<Button type="submit" data-testid="dialog-create-organisation-email-button" loading={isPending}>
<Trans>Create Email</Trans>
</Button>
</DialogFooter>

View file

@ -1,8 +1,3 @@
import { useState } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
@ -17,6 +12,8 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
export type OrganisationEmailDeleteDialogProps = {
emailId: string;
@ -24,11 +21,7 @@ export type OrganisationEmailDeleteDialogProps = {
trigger?: React.ReactNode;
};
export const OrganisationEmailDeleteDialog = ({
trigger,
emailId,
email,
}: OrganisationEmailDeleteDialogProps) => {
export const OrganisationEmailDeleteDialog = ({ trigger, emailId, email }: OrganisationEmailDeleteDialogProps) => {
const [open, setOpen] = useState(false);
const { t } = useLingui();
@ -36,26 +29,25 @@ export const OrganisationEmailDeleteDialog = ({
const organisation = useCurrentOrganisation();
const { mutateAsync: deleteEmail, isPending: isDeleting } =
trpc.enterprise.organisation.email.delete.useMutation({
onSuccess: () => {
toast({
title: t`Success`,
description: t`You have successfully removed this email from the organisation.`,
duration: 5000,
});
const { mutateAsync: deleteEmail, isPending: isDeleting } = trpc.enterprise.organisation.email.delete.useMutation({
onSuccess: () => {
toast({
title: t`Success`,
description: t`You have successfully removed this email from the organisation.`,
duration: 5000,
});
setOpen(false);
},
onError: () => {
toast({
title: t`An unknown error occurred`,
description: t`We encountered an unknown error while attempting to remove this email. Please try again later.`,
variant: 'destructive',
duration: 10000,
});
},
});
setOpen(false);
},
onError: () => {
toast({
title: t`An unknown error occurred`,
description: t`We encountered an unknown error while attempting to remove this email. Please try again later.`,
variant: 'destructive',
duration: 10000,
});
},
});
return (
<Dialog open={open} onOpenChange={(value) => !isDeleting && setOpen(value)}>

View file

@ -1,12 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
@ -32,6 +23,12 @@ import {
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
import { OrganisationEmailDomainRecordContent } from './organisation-email-domain-records-dialog';
@ -43,9 +40,7 @@ const ZCreateOrganisationEmailDomainFormSchema = ZCreateOrganisationEmailDomainR
domain: true,
});
type TCreateOrganisationEmailDomainFormSchema = z.infer<
typeof ZCreateOrganisationEmailDomainFormSchema
>;
type TCreateOrganisationEmailDomainFormSchema = z.infer<typeof ZCreateOrganisationEmailDomainFormSchema>;
type DomainRecord = {
name: string;
@ -53,10 +48,7 @@ type DomainRecord = {
type: string;
};
export const OrganisationEmailDomainCreateDialog = ({
trigger,
...props
}: OrganisationEmailCreateDialogProps) => {
export const OrganisationEmailDomainCreateDialog = ({ trigger, ...props }: OrganisationEmailCreateDialogProps) => {
const { t } = useLingui();
const { toast } = useToast();
const organisation = useCurrentOrganisation();
@ -72,8 +64,7 @@ export const OrganisationEmailDomainCreateDialog = ({
},
});
const { mutateAsync: createOrganisationEmail } =
trpc.enterprise.organisation.emailDomain.create.useMutation();
const { mutateAsync: createOrganisationEmail } = trpc.enterprise.organisation.emailDomain.create.useMutation();
// Reset state when dialog closes
useEffect(() => {
@ -119,11 +110,7 @@ export const OrganisationEmailDomainCreateDialog = ({
};
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
@ -140,18 +127,15 @@ export const OrganisationEmailDomainCreateDialog = ({
</DialogTitle>
<DialogDescription>
<Trans>
Add a custom domain to send emails on behalf of your organisation. We'll generate
DKIM records that you need to add to your DNS provider.
Add a custom domain to send emails on behalf of your organisation. We'll generate DKIM records that you
need to add to your DNS provider.
</Trans>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="domain"
@ -165,10 +149,7 @@ export const OrganisationEmailDomainCreateDialog = ({
</FormControl>
<FormMessage />
<FormDescription>
<Trans>
Enter the domain you want to use for sending emails (without http:// or
www)
</Trans>
<Trans>Enter the domain you want to use for sending emails (without http:// or www)</Trans>
</FormDescription>
</FormItem>
)}

View file

@ -1,11 +1,3 @@
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
@ -18,16 +10,14 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export type OrganisationEmailDomainDeleteDialogProps = {
emailDomainId: string;
@ -107,10 +97,9 @@ export const OrganisationEmailDomainDeleteDialog = ({
<DialogDescription className="mt-4">
<Trans>
You are about to remove the email domain{' '}
<span className="font-semibold">{emailDomain}</span> from{' '}
<span className="font-semibold">{organisation.name}</span>. All emails associated with
this domain will be deleted.
You are about to remove the email domain <span className="font-semibold">{emailDomain}</span> from{' '}
<span className="font-semibold">{organisation.name}</span>. All emails associated with this domain will be
deleted.
</Trans>
</DialogDescription>
</DialogHeader>
@ -126,9 +115,7 @@ export const OrganisationEmailDomainDeleteDialog = ({
<FormLabel>
<Trans>
Confirm by typing{' '}
<span className="font-sm text-destructive font-semibold">
{deleteMessage}
</span>
<span className="font-semibold font-sm text-destructive">{deleteMessage}</span>
</Trans>
</FormLabel>
<FormControl>

View file

@ -1,7 +1,3 @@
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
@ -18,6 +14,8 @@ import {
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
export type OrganisationEmailDomainRecordsDialogProps = {
trigger: React.ReactNode;
@ -72,7 +70,7 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
<div className="relative">
<Input className="pr-12" disabled value={record.type} />
<div className="absolute bottom-0 right-2 top-0 flex items-center justify-center">
<div className="absolute top-0 right-2 bottom-0 flex items-center justify-center">
<CopyTextButton
value={record.type}
onCopySuccess={() => toast({ title: t`Copied to clipboard` })}
@ -88,7 +86,7 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
<div className="relative">
<Input className="pr-12" disabled value={record.name} />
<div className="absolute bottom-0 right-2 top-0 flex items-center justify-center">
<div className="absolute top-0 right-2 bottom-0 flex items-center justify-center">
<CopyTextButton
value={record.name}
onCopySuccess={() => toast({ title: t`Copied to clipboard` })}
@ -104,7 +102,7 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
<div className="relative">
<Input className="pr-12" disabled value={record.value} />
<div className="absolute bottom-0 right-2 top-0 flex items-center justify-center">
<div className="absolute top-0 right-2 bottom-0 flex items-center justify-center">
<CopyTextButton
value={record.value}
onCopySuccess={() => toast({ title: t`Copied to clipboard` })}
@ -119,9 +117,8 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
<Alert variant="neutral">
<AlertDescription>
<Trans>
Once you update your DNS records, it may take up to 48 hours for it to be propogated.
Once the DNS propagation is complete you will need to come back and press the "Sync"
domains button.
Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS
propagation is complete you will need to come back and press the "Sync" domains button.
</Trans>
</AlertDescription>
</Alert>

View file

@ -1,11 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import type { TGetOrganisationEmailDomainResponse } from '@documenso/trpc/server/enterprise-router/get-organisation-email-domain.types';
import { ZUpdateOrganisationEmailRequestSchema } from '@documenso/trpc/server/enterprise-router/update-organisation-email.types';
@ -30,6 +22,12 @@ import {
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
export type OrganisationEmailUpdateDialogProps = {
trigger: React.ReactNode;
@ -61,8 +59,7 @@ export const OrganisationEmailUpdateDialog = ({
},
});
const { mutateAsync: updateOrganisationEmail, isPending } =
trpc.enterprise.organisation.email.update.useMutation();
const { mutateAsync: updateOrganisationEmail, isPending } = trpc.enterprise.organisation.email.update.useMutation();
const onFormSubmit = async ({ emailName }: ZUpdateOrganisationEmailSchema) => {
try {
@ -98,11 +95,7 @@ export const OrganisationEmailUpdateDialog = ({
}, [open, form]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger}
</DialogTrigger>
@ -115,8 +108,7 @@ export const OrganisationEmailUpdateDialog = ({
<DialogDescription>
<Trans>
You are currently updating{' '}
<span className="font-bold">{organisationEmail.email}</span>
You are currently updating <span className="font-bold">{organisationEmail.email}</span>
</Trans>
</DialogDescription>
</DialogHeader>

View file

@ -1,13 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { OrganisationMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { ORGANISATION_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/organisations';
import { EXTENDED_ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
@ -35,14 +25,15 @@ import {
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { OrganisationMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
export type OrganisationGroupCreateDialogProps = {
trigger?: React.ReactNode;
@ -56,10 +47,7 @@ const ZCreateOrganisationGroupFormSchema = ZCreateOrganisationGroupRequestSchema
type TCreateOrganisationGroupFormSchema = z.infer<typeof ZCreateOrganisationGroupFormSchema>;
export const OrganisationGroupCreateDialog = ({
trigger,
...props
}: OrganisationGroupCreateDialogProps) => {
export const OrganisationGroupCreateDialog = ({ trigger, ...props }: OrganisationGroupCreateDialogProps) => {
const { t } = useLingui();
const { toast } = useToast();
@ -77,18 +65,13 @@ export const OrganisationGroupCreateDialog = ({
const { mutateAsync: createOrganisationGroup } = trpc.organisation.group.create.useMutation();
const { data: membersFindResult, isLoading: isLoadingMembers } =
trpc.organisation.member.find.useQuery({
organisationId: organisation.id,
});
const { data: membersFindResult, isLoading: isLoadingMembers } = trpc.organisation.member.find.useQuery({
organisationId: organisation.id,
});
const members = membersFindResult?.data ?? [];
const onFormSubmit = async ({
name,
organisationRole,
memberIds,
}: TCreateOrganisationGroupFormSchema) => {
const onFormSubmit = async ({ name, organisationRole, memberIds }: TCreateOrganisationGroupFormSchema) => {
try {
await createOrganisationGroup({
organisationId: organisation.id,
@ -122,11 +105,7 @@ export const OrganisationGroupCreateDialog = ({
}, [open, form]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
@ -148,10 +127,7 @@ export const OrganisationGroupCreateDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@ -178,14 +154,12 @@ export const OrganisationGroupCreateDialog = ({
</FormLabel>
<FormControl>
<Select {...field} onValueChange={field.onChange}>
<SelectTrigger className="text-muted-foreground w-full">
<SelectTrigger className="w-full text-muted-foreground">
<SelectValue />
</SelectTrigger>
<SelectContent position="popper">
{ORGANISATION_MEMBER_ROLE_HIERARCHY[
organisation.currentOrganisationRole
].map((role) => (
{ORGANISATION_MEMBER_ROLE_HIERARCHY[organisation.currentOrganisationRole].map((role) => (
<SelectItem key={role} value={role}>
{t(EXTENDED_ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
</SelectItem>
@ -217,7 +191,7 @@ export const OrganisationGroupCreateDialog = ({
loading={isLoadingMembers}
selectedValues={field.value}
onChange={field.onChange}
className="bg-background w-full"
className="w-full bg-background"
emptySelectionPlaceholder={t`Select members`}
/>
</FormControl>

View file

@ -1,9 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
@ -18,6 +12,10 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
export type OrganisationGroupDeleteDialogProps = {
organisationGroupId: string;
@ -37,28 +35,27 @@ export const OrganisationGroupDeleteDialog = ({
const organisation = useCurrentOrganisation();
const { mutateAsync: deleteGroup, isPending: isDeleting } =
trpc.organisation.group.delete.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`You have successfully removed this group from the organisation.`),
duration: 5000,
});
const { mutateAsync: deleteGroup, isPending: isDeleting } = trpc.organisation.group.delete.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`You have successfully removed this group from the organisation.`),
duration: 5000,
});
setOpen(false);
},
onError: () => {
toast({
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to remove this group. Please try again later.`,
),
variant: 'destructive',
duration: 10000,
});
},
});
setOpen(false);
},
onError: () => {
toast({
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to remove this group. Please try again later.`,
),
variant: 'destructive',
duration: 10000,
});
},
});
return (
<Dialog open={open} onOpenChange={(value) => !isDeleting && setOpen(value)}>
@ -85,9 +82,7 @@ export const OrganisationGroupDeleteDialog = ({
</DialogHeader>
<Alert variant="neutral">
<AlertDescription className="text-center font-semibold">
{organisationGroupName}
</AlertDescription>
<AlertDescription className="text-center font-semibold">{organisationGroupName}</AlertDescription>
</Alert>
<fieldset disabled={isDeleting}>

View file

@ -1,9 +1,3 @@
import { useState } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import type { OrganisationMemberRole } from '@prisma/client';
import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
import { trpc } from '@documenso/trpc/react';
@ -20,6 +14,9 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import type { OrganisationMemberRole } from '@prisma/client';
import { useState } from 'react';
export type OrganisationLeaveDialogProps = {
organisationId: string;
@ -41,26 +38,25 @@ export const OrganisationLeaveDialog = ({
const { t } = useLingui();
const { toast } = useToast();
const { mutateAsync: leaveOrganisation, isPending: isLeavingOrganisation } =
trpc.organisation.leave.useMutation({
onSuccess: () => {
toast({
title: t`Success`,
description: t`You have successfully left this organisation.`,
duration: 5000,
});
const { mutateAsync: leaveOrganisation, isPending: isLeavingOrganisation } = trpc.organisation.leave.useMutation({
onSuccess: () => {
toast({
title: t`Success`,
description: t`You have successfully left this organisation.`,
duration: 5000,
});
setOpen(false);
},
onError: () => {
toast({
title: t`An unknown error occurred`,
description: t`We encountered an unknown error while attempting to leave this organisation. Please try again later.`,
variant: 'destructive',
duration: 10000,
});
},
});
setOpen(false);
},
onError: () => {
toast({
title: t`An unknown error occurred`,
description: t`We encountered an unknown error while attempting to leave this organisation. Please try again later.`,
variant: 'destructive',
duration: 10000,
});
},
});
return (
<Dialog open={open} onOpenChange={(value) => !isLeavingOrganisation && setOpen(value)}>

View file

@ -1,9 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { trpc } from '@documenso/trpc/react';
import { Alert } from '@documenso/ui/primitives/alert';
@ -19,6 +13,10 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
export type OrganisationMemberDeleteDialogProps = {
organisationMemberId: string;
@ -81,8 +79,8 @@ export const OrganisationMemberDeleteDialog = ({
<DialogDescription className="mt-4">
<Trans>
You are about to remove the following user from{' '}
<span className="font-semibold">{organisation.name}</span>.
You are about to remove the following user from <span className="font-semibold">{organisation.name}</span>
.
</Trans>
</DialogDescription>
</DialogHeader>

View file

@ -1,16 +1,3 @@
import { 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 { OrganisationMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { Download, Mail, MailIcon, PlusCircle, Trash, Upload, UsersIcon } from 'lucide-react';
import Papa, { type ParseResult } from 'papaparse';
import { useFieldArray, useForm } from 'react-hook-form';
import { z } from 'zod';
import { downloadFile } from '@documenso/lib/client-only/download-file';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { IS_BILLING_ENABLED, SUPPORT_EMAIL } from '@documenso/lib/constants/app';
@ -33,25 +20,23 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
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';
import { Trans } from '@lingui/react/macro';
import { OrganisationMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { Download, Mail, MailIcon, PlusCircle, Trash, Upload, UsersIcon } from 'lucide-react';
import Papa, { type ParseResult } from 'papaparse';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { z } from 'zod';
export type OrganisationMemberInviteDialogProps = {
trigger?: React.ReactNode;
@ -100,10 +85,7 @@ const ZImportOrganisationMemberSchema = z.array(
}),
);
export const OrganisationMemberInviteDialog = ({
trigger,
...props
}: OrganisationMemberInviteDialogProps) => {
export const OrganisationMemberInviteDialog = ({ trigger, ...props }: OrganisationMemberInviteDialogProps) => {
const [open, setOpen] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const [invitationType, setInvitationType] = useState<TabTypes>('INDIVIDUAL');
@ -134,8 +116,7 @@ export const OrganisationMemberInviteDialog = ({
name: 'invitations',
});
const { mutateAsync: createOrganisationMemberInvites } =
trpc.organisation.member.invite.createMany.useMutation();
const { mutateAsync: createOrganisationMemberInvites } = trpc.organisation.member.invite.createMany.useMutation();
const { data: fullOrganisation } = trpc.organisation.get.useQuery({
organisationReference: organisation.id,
@ -242,9 +223,7 @@ export const OrganisationMemberInviteDialog = ({
toast({
title: _(msg`Something went wrong`),
description: _(
msg`Please check the CSV file and make sure it is according to our format`,
),
description: _(msg`Please check the CSV file and make sure it is according to our format`),
variant: 'destructive',
});
}
@ -259,8 +238,7 @@ export const OrganisationMemberInviteDialog = ({
{ email: 'member@documenso.com', role: 'Member' },
];
const csvContent =
'Email address,Role\n' + data.map((row) => `${row.email},${row.role}`).join('\n');
const csvContent = 'Email address,Role\n' + data.map((row) => `${row.email},${row.role}`).join('\n');
const blob = new Blob([csvContent], {
type: 'text/csv',
@ -273,11 +251,7 @@ export const OrganisationMemberInviteDialog = ({
};
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger ?? (
<Button variant="secondary">
@ -301,15 +275,11 @@ export const OrganisationMemberInviteDialog = ({
{dialogState === 'alert' && (
<>
<Alert
className="flex flex-col justify-between p-6 sm:flex-row sm:items-center"
variant="neutral"
>
<Alert className="flex flex-col justify-between p-6 sm:flex-row sm:items-center" variant="neutral">
<AlertDescription>
<Trans>
Your plan does not support inviting members. Please upgrade or your plan or
contact sales at <a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you
would like to discuss your options.
Your plan does not support inviting members. Please upgrade or your plan or contact sales at{' '}
<a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you would like to discuss your options.
</Trans>
</AlertDescription>
</Alert>
@ -343,16 +313,10 @@ export const OrganisationMemberInviteDialog = ({
<TabsContent value="INDIVIDUAL">
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<div className="custom-scrollbar -m-1 max-h-[60vh] space-y-4 overflow-y-auto p-1">
{organisationMemberInvites.map((organisationMemberInvite, index) => (
<div
className="flex w-full flex-row space-x-4"
key={organisationMemberInvite.id}
>
<div className="flex w-full flex-row space-x-4" key={organisationMemberInvite.id}>
<FormField
control={form.control}
name={`invitations.${index}.email`}
@ -388,13 +352,13 @@ export const OrganisationMemberInviteDialog = ({
</SelectTrigger>
<SelectContent position="popper">
{ORGANISATION_MEMBER_ROLE_HIERARCHY[
organisation.currentOrganisationRole
].map((role) => (
<SelectItem key={role} value={role}>
{_(ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
</SelectItem>
))}
{ORGANISATION_MEMBER_ROLE_HIERARCHY[organisation.currentOrganisationRole].map(
(role) => (
<SelectItem key={role} value={role}>
{_(ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
</SelectItem>
),
)}
</SelectContent>
</Select>
</FormControl>
@ -457,13 +421,7 @@ export const OrganisationMemberInviteDialog = ({
<Trans>Click here to upload</Trans>
</p>
<input
onChange={onFileInputChange}
type="file"
ref={fileInputRef}
accept=".csv"
hidden
/>
<input onChange={onFileInputChange} type="file" ref={fileInputRef} accept=".csv" hidden />
</CardContent>
</Card>

View file

@ -1,14 +1,3 @@
import { useEffect, 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 { OrganisationMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { ORGANISATION_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/organisations';
import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
import { isOrganisationRoleWithinUserHierarchy } from '@documenso/lib/utils/organisations';
@ -23,22 +12,18 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
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 { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export type OrganisationMemberUpdateDialogProps = {
currentUserOrganisationRole: OrganisationMemberRole;
@ -113,9 +98,7 @@ export const OrganisationMemberUpdateDialog = ({
form.reset();
if (
!isOrganisationRoleWithinUserHierarchy(currentUserOrganisationRole, organisationMemberRole)
) {
if (!isOrganisationRoleWithinUserHierarchy(currentUserOrganisationRole, organisationMemberRole)) {
setOpen(false);
toast({
@ -127,11 +110,7 @@ export const OrganisationMemberUpdateDialog = ({
}, [open, currentUserOrganisationRole, organisationMemberRole, form, toast]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger ?? (
<Button variant="secondary">
@ -148,8 +127,7 @@ export const OrganisationMemberUpdateDialog = ({
<DialogDescription className="mt-4">
<Trans>
You are currently updating <span className="font-bold">{organisationMemberName}</span>
.
You are currently updating <span className="font-bold">{organisationMemberName}</span>.
</Trans>
</DialogDescription>
</DialogHeader>
@ -172,13 +150,11 @@ export const OrganisationMemberUpdateDialog = ({
</SelectTrigger>
<SelectContent className="w-full" position="popper">
{ORGANISATION_MEMBER_ROLE_HIERARCHY[currentUserOrganisationRole].map(
(role) => (
<SelectItem key={role} value={role}>
{_(ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
</SelectItem>
),
)}
{ORGANISATION_MEMBER_ROLE_HIERARCHY[currentUserOrganisationRole].map((role) => (
<SelectItem key={role} value={role}>
{_(ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>

View file

@ -1,15 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Plural, Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { startRegistration } from '@simplewebauthn/browser';
import { KeyRoundIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { UAParser } from 'ua-parser-js';
import { z } from 'zod';
import { MAXIMUM_PASSKEYS } from '@documenso/lib/constants/auth';
import { AppError } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
@ -24,16 +12,19 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Plural, Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { startRegistration } from '@simplewebauthn/browser';
import { KeyRoundIcon } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { UAParser } from 'ua-parser-js';
import { z } from 'zod';
export type PasskeyCreateDialogProps = {
trigger?: React.ReactNode;
@ -133,15 +124,11 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
}, [open, form]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button variant="secondary" loading={isPending}>
<KeyRoundIcon className="-ml-1 mr-1 h-5 w-5" />
<KeyRoundIcon className="mr-1 -ml-1 h-5 w-5" />
<Trans>Add passkey</Trans>
</Button>
)}
@ -154,19 +141,13 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
</DialogTitle>
<DialogDescription className="mt-4">
<Trans>
Passkeys allow you to sign in and authenticate using biometrics, password managers,
etc.
</Trans>
<Trans>Passkeys allow you to sign in and authenticate using biometrics, password managers, etc.</Trans>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="passkeyName"
@ -186,15 +167,15 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
<Alert variant="neutral">
<AlertDescription>
<Trans>
When you click continue, you will be prompted to add the first available
authenticator on your system.
When you click continue, you will be prompted to add the first available authenticator on your
system.
</Trans>
</AlertDescription>
<AlertDescription className="mt-2">
<Trans>
If you do not want to use the authenticator prompted, you can close it, which
will then display the next available authenticator.
If you do not want to use the authenticator prompted, you can close it, which will then display the
next available authenticator.
</Trans>
</AlertDescription>
</Alert>
@ -219,9 +200,7 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
.with('InvalidStateError', () => (
<>
<AlertTitle className="text-sm">
<Trans>
Passkey creation cancelled due to one of the following reasons:
</Trans>
<Trans>Passkey creation cancelled due to one of the following reasons:</Trans>
</AlertTitle>
<AlertDescription>
<ul className="mt-1 list-inside list-disc">

View file

@ -1,17 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Plural, Trans } from '@lingui/react/macro';
import { type TemplateDirectLink, TemplateType } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { CheckCircle2Icon, CircleIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { P, match } from 'ts-pattern';
import { z } from 'zod';
import { type Template } from '@documenso/prisma/types/template-legacy-schema';
import type { Template } from '@documenso/prisma/types/template-legacy-schema';
import { trpc } from '@documenso/trpc/react';
import {
MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH,
@ -29,25 +16,22 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@documenso/ui/primitives/table';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@documenso/ui/primitives/table';
import { Textarea } from '@documenso/ui/primitives/textarea';
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';
import { Plural, Trans } from '@lingui/react/macro';
import { type TemplateDirectLink, TemplateType } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { CheckCircle2Icon, CircleIcon } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { match, P } from 'ts-pattern';
import { z } from 'zod';
import { useCurrentTeam } from '~/providers/team';
@ -145,10 +129,7 @@ export const ManagePublicTemplateDialog = ({
}
};
const onFormSubmit = async ({
publicTitle,
publicDescription,
}: TUpdatePublicTemplateFormSchema) => {
const onFormSubmit = async ({ publicTitle, publicDescription }: TUpdatePublicTemplateFormSchema) => {
if (!selectedTemplateId) {
return;
}
@ -251,9 +232,7 @@ export const ManagePublicTemplateDialog = ({
<DialogDescription>
{team ? (
<Trans>
Select a template you'd like to display on your team's public profile
</Trans>
<Trans>Select a template you'd like to display on your team's public profile</Trans>
) : (
<Trans>Select a template you'd like to display on your public profile</Trans>
)}
@ -290,13 +269,9 @@ export const ManagePublicTemplateDialog = ({
key={row.id}
onClick={() => setSelectedTemplateId(row.id)}
>
<TableCell className="text-muted-foreground max-w-[30ch] text-sm">
{row.title}
</TableCell>
<TableCell className="max-w-[30ch] text-muted-foreground text-sm">{row.title}</TableCell>
<TableCell className="text-muted-foreground text-sm">
{i18n.date(row.createdAt)}
</TableCell>
<TableCell className="text-muted-foreground text-sm">{i18n.date(row.createdAt)}</TableCell>
<TableCell>
{selectedTemplateId === row.id ? (
@ -317,11 +292,7 @@ export const ManagePublicTemplateDialog = ({
</Button>
</DialogClose>
<Button
type="button"
disabled={selectedTemplateId === null}
onClick={() => onManageStep()}
>
<Button type="button" disabled={selectedTemplateId === null} onClick={() => onManageStep()}>
<Trans>Continue</Trans>
</Button>
</DialogFooter>
@ -340,10 +311,7 @@ export const ManagePublicTemplateDialog = ({
</DialogHeader>
<Form {...form}>
<form
className="flex h-full flex-col space-y-4"
onSubmit={form.handleSubmit(onFormSubmit)}
>
<form className="flex h-full flex-col space-y-4" onSubmit={form.handleSubmit(onFormSubmit)}>
<FormField
control={form.control}
name="publicTitle"
@ -351,10 +319,7 @@ export const ManagePublicTemplateDialog = ({
<FormItem>
<FormLabel required>Title</FormLabel>
<FormControl>
<Input
placeholder={_(msg`The public name for your template`)}
{...field}
/>
<Input placeholder={_(msg`The public name for your template`)} {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -365,17 +330,14 @@ export const ManagePublicTemplateDialog = ({
control={form.control}
name="publicDescription"
render={({ field }) => {
const remaningLength =
MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH - (field.value || '').length;
const remaningLength = MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH - (field.value || '').length;
return (
<FormItem>
<FormLabel required>Description</FormLabel>
<FormControl>
<Textarea
placeholder={_(
msg`The public description that will be displayed with this template`,
)}
placeholder={_(msg`The public description that will be displayed with this template`)}
{...field}
/>
</FormControl>

View file

@ -1,7 +1,3 @@
import { useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { authClient } from '@documenso/auth/client';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -15,6 +11,8 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
type SessionLogoutAllDialogProps = {
onSuccess?: () => Promise<unknown>;
@ -71,8 +69,8 @@ export const SessionLogoutAllDialog = ({ onSuccess, disabled }: SessionLogoutAll
</DialogTitle>
<DialogDescription>
<Trans>
This will sign you out of all other devices. You will need to sign in again on those
devices to continue using your account.
This will sign you out of all other devices. You will need to sign in again on those devices to continue
using your account.
</Trans>
</DialogDescription>
</DialogHeader>

View file

@ -1,13 +1,5 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Plural, Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm, useWatch } from 'react-hook-form';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { validateCheckboxLength } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { type TCheckboxFieldMeta } from '@documenso/lib/types/field-meta';
import type { TCheckboxFieldMeta } from '@documenso/lib/types/field-meta';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
@ -20,6 +12,13 @@ import {
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Form, FormControl, FormField, FormItem } from '@documenso/ui/primitives/form/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Plural, Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm, useWatch } from 'react-hook-form';
import { match } from 'ts-pattern';
import { z } from 'zod';
export type SignFieldCheckboxDialogProps = {
fieldMeta: TCheckboxFieldMeta;
@ -28,157 +27,133 @@ export type SignFieldCheckboxDialogProps = {
preselectedIndices: number[];
};
export const SignFieldCheckboxDialog = createCallable<
SignFieldCheckboxDialogProps,
number[] | null
>(({ call, fieldMeta, validationRule, validationLength, preselectedIndices }) => {
const ZSignFieldCheckboxFormSchema = z
.object({
values: z.array(
z.object({
checked: z.boolean(),
value: z.string(),
}),
),
})
.superRefine((data, ctx) => {
// Allow unselecting all options if the field is not required even if
// validation is not met.
if (!fieldMeta.required && data.values.every((value) => !value.checked)) {
return;
}
export const SignFieldCheckboxDialog = createCallable<SignFieldCheckboxDialogProps, number[] | null>(
({ call, fieldMeta, validationRule, validationLength, preselectedIndices }) => {
const ZSignFieldCheckboxFormSchema = z
.object({
values: z.array(
z.object({
checked: z.boolean(),
value: z.string(),
}),
),
})
.superRefine((data, ctx) => {
// Allow unselecting all options if the field is not required even if
// validation is not met.
if (!fieldMeta.required && data.values.every((value) => !value.checked)) {
return;
}
const numberOfSelectedValues = data.values.filter((value) => value.checked).length;
const numberOfSelectedValues = data.values.filter((value) => value.checked).length;
const isValid = validateCheckboxLength(
numberOfSelectedValues,
validationRule,
validationLength,
);
const isValid = validateCheckboxLength(numberOfSelectedValues, validationRule, validationLength);
if (!isValid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: msg`Validation failed`.id,
});
}
if (!isValid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: msg`Validation failed`.id,
});
}
});
const form = useForm<z.infer<typeof ZSignFieldCheckboxFormSchema>>({
resolver: zodResolver(ZSignFieldCheckboxFormSchema),
defaultValues: {
values: (fieldMeta.values || []).map((value, index) => ({
checked: preselectedIndices.includes(index) || false,
value: value.value,
})),
},
});
const form = useForm<z.infer<typeof ZSignFieldCheckboxFormSchema>>({
resolver: zodResolver(ZSignFieldCheckboxFormSchema),
defaultValues: {
values: (fieldMeta.values || []).map((value, index) => ({
checked: preselectedIndices.includes(index) || false,
value: value.value,
})),
},
});
const formValues = useWatch({
control: form.control,
});
const formValues = useWatch({
control: form.control,
});
return (
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
<DialogContent position="center">
<DialogHeader>
<DialogTitle>{fieldMeta.label || <Trans>Select Options</Trans>}</DialogTitle>
return (
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
<DialogContent position="center">
<DialogHeader>
<DialogTitle>{fieldMeta.label || <Trans>Select Options</Trans>}</DialogTitle>
<DialogDescription
className={cn('mt-4', {
'text-destructive': Object.keys(form.formState.errors).length > 0,
})}
>
{match(validationRule)
.with('>=', () => (
<Plural
value={validationLength}
one="Select at least # option"
other="Select at least # options"
/>
))
.with('=', () => (
<Plural
value={validationLength}
one="Select exactly # option"
other="Select exactly # options"
/>
))
.with('<=', () => (
<Plural
value={validationLength}
one="Select at most # option"
other="Select at most # options"
/>
))
.exhaustive()}
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit((data) =>
call.end(
data.values
.map((value, i) => (value.checked ? i : null))
.filter((value) => value !== null),
),
)}
>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
<DialogDescription
className={cn('mt-4', {
'text-destructive': Object.keys(form.formState.errors).length > 0,
})}
>
<ul className="space-y-3">
{(formValues.values || []).map((value, index) => (
<li key={`checkbox-${index}`}>
<FormField
control={form.control}
name={`values.${index}`}
render={({ field }) => (
<FormItem>
<FormControl>
<div className="flex items-center">
<Checkbox
id={`checkbox-value-${index}`}
className="h-5 w-5 border-foreground/30 data-[state=checked]:bg-primary"
checked={field.value.checked}
onCheckedChange={(checked) => {
field.onChange({
...field.value,
checked,
});
}}
/>
{match(validationRule)
.with('>=', () => (
<Plural value={validationLength} one="Select at least # option" other="Select at least # options" />
))
.with('=', () => (
<Plural value={validationLength} one="Select exactly # option" other="Select exactly # options" />
))
.with('<=', () => (
<Plural value={validationLength} one="Select at most # option" other="Select at most # options" />
))
.exhaustive()}
</DialogDescription>
</DialogHeader>
<label
className="ml-2 w-full text-sm text-muted-foreground"
htmlFor={`checkbox-value-${index}`}
>
{value.value}
</label>
</div>
</FormControl>
</FormItem>
)}
/>
</li>
))}
</ul>
<Form {...form}>
<form
onSubmit={form.handleSubmit((data) =>
call.end(data.values.map((value, i) => (value.checked ? i : null)).filter((value) => value !== null)),
)}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<ul className="space-y-3">
{(formValues.values || []).map((value, index) => (
<li key={`checkbox-${index}`}>
<FormField
control={form.control}
name={`values.${index}`}
render={({ field }) => (
<FormItem>
<FormControl>
<div className="flex items-center">
<Checkbox
id={`checkbox-value-${index}`}
className="h-5 w-5 border-foreground/30 data-[state=checked]:bg-primary"
checked={field.value.checked}
onCheckedChange={(checked) => {
field.onChange({
...field.value,
checked,
});
}}
/>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
<Trans>Cancel</Trans>
</Button>
<label
className="ml-2 w-full text-muted-foreground text-sm"
htmlFor={`checkbox-value-${index}`}
>
{value.value}
</label>
</div>
</FormControl>
</FormItem>
)}
/>
</li>
))}
</ul>
<Button type="submit">
<Trans>Confirm</Trans>
</Button>
</DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent>
</Dialog>
);
});
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit">
<Trans>Confirm</Trans>
</Button>
</DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent>
</Dialog>
);
},
);

View file

@ -1,6 +1,3 @@
import { useLingui } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import type { TDropdownFieldMeta } from '@documenso/lib/types/field-meta';
import {
CommandDialog,
@ -10,6 +7,8 @@ import {
CommandItem,
CommandList,
} from '@documenso/ui/primitives/command';
import { useLingui } from '@lingui/react/macro';
import { createCallable } from 'react-call';
export type SignFieldDropdownDialogProps = {
fieldMeta: TDropdownFieldMeta;

View file

@ -1,10 +1,3 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zEmail } from '@documenso/lib/utils/zod';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -15,14 +8,14 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const ZSignFieldEmailFormSchema = z.object({
email: zEmail().min(1, { message: msg`Email is required`.id }),
@ -58,10 +51,7 @@ export const SignFieldEmailDialog = createCallable<SignFieldEmailDialogProps, st
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.email))}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="email"

View file

@ -1,10 +1,3 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@ -14,15 +7,14 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const ZSignFieldInitialsFormSchema = z.object({
initials: z.string().min(1, { message: msg`Initials are required`.id }),
@ -34,64 +26,59 @@ export type SignFieldInitialsDialogProps = {
//
};
export const SignFieldInitialsDialog = createCallable<SignFieldInitialsDialogProps, string | null>(
({ call }) => {
const form = useForm<TSignFieldInitialsFormSchema>({
resolver: zodResolver(ZSignFieldInitialsFormSchema),
defaultValues: {
initials: '',
},
});
export const SignFieldInitialsDialog = createCallable<SignFieldInitialsDialogProps, string | null>(({ call }) => {
const form = useForm<TSignFieldInitialsFormSchema>({
resolver: zodResolver(ZSignFieldInitialsFormSchema),
defaultValues: {
initials: '',
},
});
return (
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Enter Initials</Trans>
</DialogTitle>
return (
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Enter Initials</Trans>
</DialogTitle>
<DialogDescription className="mt-4">
<Trans>Please enter your initials</Trans>
</DialogDescription>
</DialogHeader>
<DialogDescription className="mt-4">
<Trans>Please enter your initials</Trans>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.initials))}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<FormField
control={form.control}
name="initials"
render={({ field }) => (
<FormItem>
<FormLabel required>
<Trans>Initials</Trans>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.initials))}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="initials"
render={({ field }) => (
<FormItem>
<FormLabel required>
<Trans>Initials</Trans>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
<Trans>Cancel</Trans>
</Button>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit">
<Trans>Enter</Trans>
</Button>
</DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent>
</Dialog>
);
},
);
<Button type="submit">
<Trans>Enter</Trans>
</Button>
</DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent>
</Dialog>
);
});

View file

@ -1,10 +1,3 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@ -14,14 +7,14 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const ZSignFieldNameFormSchema = z.object({
name: z.string().min(1, { message: msg`Name is required`.id }),
@ -33,61 +26,56 @@ export type SignFieldNameDialogProps = {
//
};
export const SignFieldNameDialog = createCallable<SignFieldNameDialogProps, string | null>(
({ call }) => {
const form = useForm<TSignFieldNameFormSchema>({
resolver: zodResolver(ZSignFieldNameFormSchema),
defaultValues: {
name: '',
},
});
export const SignFieldNameDialog = createCallable<SignFieldNameDialogProps, string | null>(({ call }) => {
const form = useForm<TSignFieldNameFormSchema>({
resolver: zodResolver(ZSignFieldNameFormSchema),
defaultValues: {
name: '',
},
});
return (
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Enter Name</Trans>
</DialogTitle>
return (
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Enter Name</Trans>
</DialogTitle>
<DialogDescription className="mt-4">
<Trans>Please enter your full name</Trans>
</DialogDescription>
</DialogHeader>
<DialogDescription className="mt-4">
<Trans>Please enter your full name</Trans>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.name))}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.name))}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
<Trans>Cancel</Trans>
</Button>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit">
<Trans>Enter</Trans>
</Button>
</DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent>
</Dialog>
);
},
);
<Button type="submit">
<Trans>Enter</Trans>
</Button>
</DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent>
</Dialog>
);
});

View file

@ -1,10 +1,3 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import type { TNumberFieldMeta } from '@documenso/lib/types/field-meta';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@ -17,14 +10,13 @@ import {
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { numberFormatValues } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export type SignFieldNumberDialogProps = {
fieldMeta: TNumberFieldMeta;
@ -115,10 +107,7 @@ export const SignFieldNumberDialog = createCallable<SignFieldNumberDialogProps,
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.number))}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="number"

View file

@ -1,17 +1,9 @@
import { useState } from 'react';
import { Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
import { createCallable } from 'react-call';
import { DocumentSigningDisclosure } from '../general/document-signing/document-signing-disclosure';
@ -23,18 +15,8 @@ export type SignFieldSignatureDialogProps = {
drawSignatureEnabled?: boolean;
};
export const SignFieldSignatureDialog = createCallable<
SignFieldSignatureDialogProps,
string | null
>(
({
call,
fullName,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
initialSignature,
}) => {
export const SignFieldSignatureDialog = createCallable<SignFieldSignatureDialogProps, string | null>(
({ call, fullName, typedSignatureEnabled, uploadSignatureEnabled, drawSignatureEnabled, initialSignature }) => {
const [localSignature, setLocalSignature] = useState(initialSignature);
return (
@ -64,11 +46,7 @@ export const SignFieldSignatureDialog = createCallable<
<Trans>Cancel</Trans>
</Button>
<Button
type="button"
disabled={!localSignature}
onClick={() => call.end(localSignature || null)}
>
<Button type="button" disabled={!localSignature} onClick={() => call.end(localSignature || null)}>
<Trans>Sign</Trans>
</Button>
</DialogFooter>

View file

@ -1,11 +1,3 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Plural, useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import type { TTextFieldMeta } from '@documenso/lib/types/field-meta';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@ -17,14 +9,14 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@documenso/ui/primitives/form/form';
import { Textarea } from '@documenso/ui/primitives/textarea';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { Plural, Trans, useLingui } from '@lingui/react/macro';
import { createCallable } from 'react-call';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const ZSignFieldTextFormSchema = z.object({
text: z.string().min(1, { message: msg`Text is required`.id }),
@ -36,80 +28,73 @@ export type SignFieldTextDialogProps = {
fieldMeta?: TTextFieldMeta;
};
export const SignFieldTextDialog = createCallable<SignFieldTextDialogProps, string | null>(
({ call, fieldMeta }) => {
const { t } = useLingui();
export const SignFieldTextDialog = createCallable<SignFieldTextDialogProps, string | null>(({ call, fieldMeta }) => {
const { t } = useLingui();
const form = useForm<TSignFieldTextFormSchema>({
resolver: zodResolver(ZSignFieldTextFormSchema),
defaultValues: {
text: '',
},
});
const form = useForm<TSignFieldTextFormSchema>({
resolver: zodResolver(ZSignFieldTextFormSchema),
defaultValues: {
text: '',
},
});
return (
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>{fieldMeta?.label || <Trans>Enter Text</Trans>}</DialogTitle>
return (
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>{fieldMeta?.label || <Trans>Enter Text</Trans>}</DialogTitle>
<DialogDescription className="mt-4">
<Trans>Please enter a value</Trans>
</DialogDescription>
</DialogHeader>
<DialogDescription className="mt-4">
<Trans>Please enter a value</Trans>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.text))}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<FormField
control={form.control}
name="text"
render={({ field, fieldState }) => (
<FormItem>
<FormControl>
<Textarea
id="custom-text"
placeholder={fieldMeta?.placeholder ?? t`Enter your text here`}
className={cn('w-full rounded-md', {
'border-2 border-red-300 text-left ring-2 ring-red-200 ring-offset-2 ring-offset-red-200 focus-visible:border-red-400 focus-visible:ring-4 focus-visible:ring-red-200 focus-visible:ring-offset-2 focus-visible:ring-offset-red-200':
fieldState.error,
})}
{...field}
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.text))}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="text"
render={({ field, fieldState }) => (
<FormItem>
<FormControl>
<Textarea
id="custom-text"
placeholder={fieldMeta?.placeholder ?? t`Enter your text here`}
className={cn('w-full rounded-md', {
'border-2 border-red-300 text-left ring-2 ring-red-200 ring-offset-2 ring-offset-red-200 focus-visible:border-red-400 focus-visible:ring-4 focus-visible:ring-red-200 focus-visible:ring-offset-2 focus-visible:ring-offset-red-200':
fieldState.error,
})}
{...field}
/>
</FormControl>
<FormMessage />
{fieldMeta?.characterLimit !== undefined && fieldMeta?.characterLimit > 0 && !fieldState.error && (
<div className="text-muted-foreground text-sm">
<Plural
value={fieldMeta?.characterLimit - (field.value?.length ?? 0)}
one="# character remaining"
other="# characters remaining"
/>
</FormControl>
<FormMessage />
{fieldMeta?.characterLimit !== undefined &&
fieldMeta?.characterLimit > 0 &&
!fieldState.error && (
<div className="text-sm text-muted-foreground">
<Plural
value={fieldMeta?.characterLimit - (field.value?.length ?? 0)}
one="# character remaining"
other="# characters remaining"
/>
</div>
)}
</FormItem>
)}
/>
</div>
)}
</FormItem>
)}
/>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
<Trans>Cancel</Trans>
</Button>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit">
<Trans>Enter</Trans>
</Button>
</DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent>
</Dialog>
);
},
);
<Button type="submit">
<Trans>Enter</Trans>
</Button>
</DialogFooter>
</fieldset>
</form>
</Form>
</DialogContent>
</Dialog>
);
});

View file

@ -1,22 +1,7 @@
import { useEffect, useMemo, 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 * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { useSearchParams } from 'react-router';
import type { z } from 'zod';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import {
IS_BILLING_ENABLED,
NEXT_PUBLIC_WEBAPP_URL,
SUPPORT_EMAIL,
} from '@documenso/lib/constants/app';
import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL, SUPPORT_EMAIL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZCreateTeamRequestSchema } from '@documenso/trpc/server/team-router/create-team.types';
@ -32,17 +17,19 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
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';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useSearchParams } from 'react-router';
import type { z } from 'zod';
export type TeamCreateDialogProps = {
trigger?: React.ReactNode;
@ -118,9 +105,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
toast({
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to create a team. Please try again later.`,
),
description: _(msg`We encountered an unknown error while attempting to create a team. Please try again later.`),
variant: 'destructive',
});
}
@ -166,11 +151,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
}, [open, form]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
@ -194,15 +175,11 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
{dialogState === 'alert' && (
<>
<Alert
className="flex flex-col justify-between p-6 sm:flex-row sm:items-center"
variant="neutral"
>
<Alert className="flex flex-col justify-between p-6 sm:flex-row sm:items-center" variant="neutral">
<AlertDescription className="mt-0">
<Trans>
You have reached the maximum number of teams for your plan. Please contact sales
at <a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you would like to
adjust your plan.
You have reached the maximum number of teams for your plan. Please contact sales at{' '}
<a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you would like to adjust your plan.
</Trans>
</AlertDescription>
</Alert>
@ -218,10 +195,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
{dialogState === 'form' && (
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="teamName"
@ -264,7 +238,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
<Input className="bg-background" {...field} />
</FormControl>
{!form.formState.errors.teamUrl && (
<span className="text-xs font-normal text-foreground/50">
<span className="font-normal text-foreground/50 text-xs">
{field.value ? (
`${NEXT_PUBLIC_WEBAPP_URL()}/t/${field.value}`
) : (
@ -285,16 +259,9 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
<FormItem className="flex items-center space-x-2">
<FormControl>
<div className="flex items-center">
<Checkbox
id="inherit-members"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Checkbox id="inherit-members" checked={field.value} onCheckedChange={field.onChange} />
<label
className="ml-2 text-sm text-muted-foreground"
htmlFor="inherit-members"
>
<label className="ml-2 text-muted-foreground text-sm" htmlFor="inherit-members">
<Trans>Allow all organisation members to access this team</Trans>
</label>
</div>
@ -308,11 +275,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
<Trans>Cancel</Trans>
</Button>
<Button
type="submit"
data-testid="dialog-create-team-button"
loading={form.formState.isSubmitting}
>
<Button type="submit" data-testid="dialog-create-team-button" loading={form.formState.isSubmitting}>
<Trans>Create Team</Trans>
</Button>
</DialogFooter>

View file

@ -1,13 +1,3 @@
import { useEffect, 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 { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { AppError } from '@documenso/lib/errors/app-error';
@ -22,24 +12,19 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import type { Toast } from '@documenso/ui/primitives/use-toast';
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';
import { Trans } from '@lingui/react/macro';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
export type TeamDeleteDialogProps = {
teamId: number;
@ -48,12 +33,7 @@ export type TeamDeleteDialogProps = {
trigger?: React.ReactNode;
};
export const TeamDeleteDialog = ({
trigger,
teamId,
teamName,
redirectTo,
}: TeamDeleteDialogProps) => {
export const TeamDeleteDialog = ({ trigger, teamId, teamName, redirectTo }: TeamDeleteDialogProps) => {
const navigate = useNavigate();
const [open, setOpen] = useState(false);
@ -154,18 +134,15 @@ export const TeamDeleteDialog = ({
<DialogDescription className="mt-4">
<Trans>
Please note that you will lose access to all documents associated with this team & all
the members will be removed and notified
Please note that you will lose access to all documents associated with this team & all the members will be
removed and notified
</Trans>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="teamName"
@ -196,9 +173,7 @@ export const TeamDeleteDialog = ({
<FormControl>
<Select {...field} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue
placeholder={_(msg`Don't transfer (Delete all documents)`)}
/>
<SelectValue placeholder={_(msg`Don't transfer (Delete all documents)`)} />
</SelectTrigger>
<SelectContent>

View file

@ -1,13 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { Plus } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { useRevalidator } from 'react-router';
import type { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZCreateTeamEmailVerificationMutationSchema } from '@documenso/trpc/server/team-router/schema';
@ -21,16 +11,17 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { Plus } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useRevalidator } from 'react-router';
import type { z } from 'zod';
export type TeamEmailAddDialogProps = {
teamId: number;
@ -59,8 +50,7 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
},
});
const { mutateAsync: sendTeamEmailVerification, isPending } =
trpc.team.email.verification.send.useMutation();
const { mutateAsync: sendTeamEmailVerification, isPending } = trpc.team.email.verification.send.useMutation();
const onFormSubmit = async ({ name, email }: TCreateTeamEmailFormSchema) => {
try {
@ -106,15 +96,11 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
}, [open, form]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button variant="outline" loading={isPending} className="bg-background">
<Plus className="-ml-1 mr-1 h-5 w-5" />
<Plus className="mr-1 -ml-1 h-5 w-5" />
<Trans>Add email</Trans>
</Button>
)}
@ -133,10 +119,7 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@ -162,11 +145,7 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
<Trans>Email</Trans>
</FormLabel>
<FormControl>
<Input
className="bg-background"
placeholder="example@example.com"
{...field}
/>
<Input className="bg-background" placeholder="example@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>

View file

@ -1,11 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Prisma } from '@prisma/client';
import { useRevalidator } from 'react-router';
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
import { trpc } from '@documenso/trpc/react';
@ -22,6 +14,12 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Prisma } from '@prisma/client';
import { useState } from 'react';
import { useRevalidator } from 'react-router';
export type TeamEmailDeleteDialogProps = {
trigger?: React.ReactNode;
@ -47,24 +45,23 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele
const { toast } = useToast();
const { revalidate } = useRevalidator();
const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } =
trpc.team.email.delete.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Team email has been removed`),
duration: 5000,
});
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`Unable to remove team email at this time. Please try again.`),
variant: 'destructive',
duration: 10000,
});
},
});
const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } = trpc.team.email.delete.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Team email has been removed`),
duration: 5000,
});
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`Unable to remove team email at this time. Please try again.`),
variant: 'destructive',
duration: 10000,
});
},
});
const { mutateAsync: deleteTeamEmailVerification, isPending: isDeletingTeamEmailVerification } =
trpc.team.email.verification.delete.useMutation({
@ -115,8 +112,7 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele
<DialogDescription className="mt-4">
<Trans>
You are about to delete the following team email from{' '}
<span className="font-semibold">{teamName}</span>.
You are about to delete the following team email from <span className="font-semibold">{teamName}</span>.
</Trans>
</DialogDescription>
</DialogHeader>
@ -125,19 +121,13 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele
<AvatarWithText
avatarClass="h-12 w-12"
avatarSrc={formatAvatarUrl(team.avatarImageId)}
avatarFallback={extractInitials(
(team.teamEmail?.name || team.emailVerification?.name) ?? '',
)}
avatarFallback={extractInitials((team.teamEmail?.name || team.emailVerification?.name) ?? '')}
primaryText={
<span className="text-foreground/80 text-sm font-semibold">
<span className="font-semibold text-foreground/80 text-sm">
{team.teamEmail?.name || team.emailVerification?.name}
</span>
}
secondaryText={
<span className="text-sm">
{team.teamEmail?.email || team.emailVerification?.email}
</span>
}
secondaryText={<span className="text-sm">{team.teamEmail?.email || team.emailVerification?.email}</span>}
/>
</Alert>

View file

@ -1,13 +1,3 @@
import { useEffect, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type { TeamEmail } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { useRevalidator } from 'react-router';
import { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -19,16 +9,17 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import type { TeamEmail } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useRevalidator } from 'react-router';
import { z } from 'zod';
export type TeamEmailUpdateDialogProps = {
teamEmail: TeamEmail;
@ -41,11 +32,7 @@ const ZUpdateTeamEmailFormSchema = z.object({
type TUpdateTeamEmailFormSchema = z.infer<typeof ZUpdateTeamEmailFormSchema>;
export const TeamEmailUpdateDialog = ({
teamEmail,
trigger,
...props
}: TeamEmailUpdateDialogProps) => {
export const TeamEmailUpdateDialog = ({ teamEmail, trigger, ...props }: TeamEmailUpdateDialogProps) => {
const [open, setOpen] = useState(false);
const { t } = useLingui();
@ -95,11 +82,7 @@ export const TeamEmailUpdateDialog = ({
}, [open, form]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger ?? (
<Button variant="outline" className="bg-background">
@ -121,10 +104,7 @@ export const TeamEmailUpdateDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset
className="flex h-full flex-col space-y-4"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"

View file

@ -1,13 +1,3 @@
import { useEffect, useMemo, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { OrganisationGroupType, TeamMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { TEAM_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/teams';
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
import { trpc } from '@documenso/trpc/react';
@ -32,14 +22,16 @@ import {
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { OrganisationGroupType, TeamMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { useCurrentTeam } from '~/providers/team';
@ -184,21 +176,18 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
value: group.id,
}))}
loading={organisationGroupQuery.isLoading || teamGroupQuery.isLoading}
selectedValues={field.value.map(
({ organisationGroupId }) => organisationGroupId,
)}
selectedValues={field.value.map(({ organisationGroupId }) => organisationGroupId)}
onChange={(value) => {
field.onChange(
value.map((organisationGroupId) => ({
organisationGroupId,
teamRole:
field.value.find(
(value) => value.organisationGroupId === organisationGroupId,
)?.teamRole || TeamMemberRole.MEMBER,
field.value.find((value) => value.organisationGroupId === organisationGroupId)
?.teamRole || TeamMemberRole.MEMBER,
})),
);
}}
className="bg-background w-full"
className="w-full bg-background"
emptySelectionPlaceholder={t`Select groups`}
/>
</FormControl>
@ -243,9 +232,8 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
readOnly
className="bg-background"
value={
avaliableOrganisationGroups.find(
({ id }) => id === group.organisationGroupId,
)?.name || t`Untitled Group`
avaliableOrganisationGroups.find(({ id }) => id === group.organisationGroupId)?.name ||
t`Untitled Group`
}
/>
</div>
@ -267,13 +255,11 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
</SelectTrigger>
<SelectContent position="popper">
{TEAM_MEMBER_ROLE_HIERARCHY[team.currentTeamRole].map(
(role) => (
<SelectItem key={role} value={role}>
{t(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
</SelectItem>
),
)}
{TEAM_MEMBER_ROLE_HIERARCHY[team.currentTeamRole].map((role) => (
<SelectItem key={role} value={role}>
{t(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>

View file

@ -1,10 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { TeamMemberRole } from '@prisma/client';
import { isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
@ -19,6 +12,11 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { TeamMemberRole } from '@prisma/client';
import { useState } from 'react';
import { useCurrentTeam } from '~/providers/team';
@ -82,8 +80,7 @@ export const TeamGroupDeleteDialog = ({
<DialogDescription className="mt-4">
<Trans context="Removing group from team">
You are about to remove the following group from{' '}
<span className="font-semibold">{team.name}</span>.
You are about to remove the following group from <span className="font-semibold">{team.name}</span>.
</Trans>
</DialogDescription>
</DialogHeader>
@ -91,9 +88,7 @@ export const TeamGroupDeleteDialog = ({
{isTeamRoleWithinUserHierarchy(team.currentTeamRole, teamGroupRole) ? (
<>
<Alert variant="neutral">
<AlertDescription className="text-center font-semibold">
{teamGroupName}
</AlertDescription>
<AlertDescription className="text-center font-semibold">{teamGroupName}</AlertDescription>
</Alert>
<fieldset disabled={isDeleting}>

View file

@ -1,14 +1,3 @@
import { useEffect, 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 { TeamMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { TEAM_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/teams';
import { EXTENDED_TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
import { isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
@ -24,22 +13,18 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
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 { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { TeamMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { useCurrentTeam } from '~/providers/team';
@ -117,11 +102,7 @@ export const TeamGroupUpdateDialog = ({
}, [open, team.currentTeamRole, teamGroupRole, form, toast]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger ?? (
<Button variant="secondary">
@ -138,8 +119,7 @@ export const TeamGroupUpdateDialog = ({
<DialogDescription>
<Trans>
You are currently updating the <span className="font-bold">{teamGroupName}</span> team
group.
You are currently updating the <span className="font-bold">{teamGroupName}</span> team group.
</Trans>
</DialogDescription>
</DialogHeader>

View file

@ -1,6 +1,3 @@
import { Trans, useLingui } from '@lingui/react/macro';
import type { TeamGroup } from '@prisma/client';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -14,6 +11,8 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import type { TeamGroup } from '@prisma/client';
import { useCurrentTeam } from '~/providers/team';
@ -60,8 +59,8 @@ export const TeamMemberInheritDisableDialog = ({ group }: TeamMemberInheritDisab
<DialogDescription className="mt-4">
<Trans>
You are about to remove default access to this team for all organisation members. Any
members not explicitly added to this team will no longer have access.
You are about to remove default access to this team for all organisation members. Any members not
explicitly added to this team will no longer have access.
</Trans>
</DialogDescription>
</DialogHeader>

View file

@ -1,6 +1,3 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { OrganisationGroupType, OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
@ -15,6 +12,8 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { OrganisationGroupType, OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { useCurrentTeam } from '~/providers/team';
@ -81,8 +80,7 @@ export const TeamMemberInheritEnableDialog = () => {
<DialogDescription className="mt-4">
<Trans>
You are about to give all organisation members access to this team under their
organisation role.
You are about to give all organisation members access to this team under their organisation role.
</Trans>
</DialogDescription>
</DialogHeader>

View file

@ -1,15 +1,3 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { TeamMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { InfoIcon, UserPlusIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { TEAM_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/teams';
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
@ -37,15 +25,19 @@ import {
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { TeamMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { InfoIcon, UserPlusIcon } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { OrganisationMemberInviteDialog } from '~/components/dialogs/organisation-member-invite-dialog';
import { useCurrentTeam } from '~/providers/team';
@ -104,13 +96,10 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
const organisationMembers = organisationMemberQuery.data?.data ?? [];
const teamMembers = teamMemberQuery.data?.data ?? [];
return organisationMembers.filter(
(member) => !teamMembers.some((teamMember) => teamMember.id === member.id),
);
return organisationMembers.filter((member) => !teamMembers.some((teamMember) => teamMember.id === member.id));
}, [organisationMemberQuery, teamMemberQuery]);
const hasNoAvailableMembers =
!organisationMemberQuery.isLoading && avaliableOrganisationMembers.length === 0;
const hasNoAvailableMembers = !organisationMemberQuery.isLoading && avaliableOrganisationMembers.length === 0;
const onFormSubmit = async ({ members }: TAddTeamMembersFormSchema) => {
if (members.length === 0) {
@ -199,8 +188,8 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
</TooltipTrigger>
<TooltipContent className="z-[99999] max-w-xs text-muted-foreground">
<Trans>
To be able to add members to a team, you must first add them to the
organisation. For more information, please see the{' '}
To be able to add members to a team, you must first add them to the organisation. For more
information, please see the{' '}
<Link
to="https://docs.documenso.com/users/organisations/members"
target="_blank"
@ -260,25 +249,23 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
<FormControl>
{hasNoAvailableMembers ? (
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed border-muted-foreground/25 bg-muted/30 px-6 py-12 text-center">
<div className="flex flex-col items-center justify-center rounded-lg border border-muted-foreground/25 border-dashed bg-muted/30 px-6 py-12 text-center">
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted">
<UserPlusIcon className="h-6 w-6 text-muted-foreground" />
</div>
<h3 className="mb-2 text-sm font-semibold">
<h3 className="mb-2 font-semibold text-sm">
<Trans>No organisation members available</Trans>
</h3>
<p className="mb-6 max-w-sm text-sm text-muted-foreground">
<p className="mb-6 max-w-sm text-muted-foreground text-sm">
{canInviteOrganisationMembers ? (
<Trans>
To add members to this team, you must first add them to the
organisation.
To add members to this team, you must first add them to the organisation.
</Trans>
) : (
<Trans>
To add members to this team, they must first be invited to the
organisation. Only organisation admins and managers can invite
new members please contact one of them to invite members on
your behalf.
To add members to this team, they must first be invited to the organisation. Only
organisation admins and managers can invite new members please contact one of them
to invite members on your behalf.
</Trans>
)}
</p>
@ -302,18 +289,14 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
value: member.id,
}))}
loading={organisationMemberQuery.isLoading}
selectedValues={field.value.map(
(member) => member.organisationMemberId,
)}
selectedValues={field.value.map((member) => member.organisationMemberId)}
onChange={(value) => {
field.onChange(
value.map((organisationMemberId) => ({
organisationMemberId,
teamRole:
field.value.find(
(member) =>
member.organisationMemberId === organisationMemberId,
)?.teamRole || TeamMemberRole.MEMBER,
field.value.find((member) => member.organisationMemberId === organisationMemberId)
?.teamRole || TeamMemberRole.MEMBER,
})),
);
}}
@ -330,10 +313,7 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
</FormDescription>
{canInviteOrganisationMembers && (
<Alert
variant="neutral"
className="mt-2 flex items-center gap-2 space-y-0"
>
<Alert variant="neutral" className="mt-2 flex items-center gap-2 space-y-0">
<div>
<UserPlusIcon className="h-5 w-5 text-muted-foreground" />
</div>
@ -346,7 +326,7 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
<Button
type="button"
variant="link"
className="h-auto p-0 text-sm font-medium text-documenso-700 hover:text-documenso-600"
className="h-auto p-0 font-medium text-documenso-700 text-sm hover:text-documenso-600"
>
<Trans>Invite them to the organisation first</Trans>
</Button>
@ -394,9 +374,8 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
readOnly
className="bg-background"
value={
organisationMemberQuery.data?.data.find(
({ id }) => id === member.organisationMemberId,
)?.name || ''
organisationMemberQuery.data?.data.find(({ id }) => id === member.organisationMemberId)
?.name || ''
}
/>
</div>
@ -418,13 +397,11 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
</SelectTrigger>
<SelectContent position="popper">
{TEAM_MEMBER_ROLE_HIERARCHY[team.currentTeamRole].map(
(role) => (
<SelectItem key={role} value={role}>
{t(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
</SelectItem>
),
)}
{TEAM_MEMBER_ROLE_HIERARCHY[team.currentTeamRole].map((role) => (
<SelectItem key={role} value={role}>
{t(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>

View file

@ -1,9 +1,3 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
@ -18,6 +12,10 @@ import {
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
export type TeamMemberDeleteDialogProps = {
teamId: number;
@ -43,28 +41,27 @@ export const TeamMemberDeleteDialog = ({
const { _ } = useLingui();
const { toast } = useToast();
const { mutateAsync: deleteTeamMember, isPending: isDeletingTeamMember } =
trpc.team.member.delete.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`You have successfully removed this user from the team.`),
duration: 5000,
});
const { mutateAsync: deleteTeamMember, isPending: isDeletingTeamMember } = trpc.team.member.delete.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`You have successfully removed this user from the team.`),
duration: 5000,
});
setOpen(false);
},
onError: () => {
toast({
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to remove this user. Please try again later.`,
),
variant: 'destructive',
duration: 10000,
});
},
});
setOpen(false);
},
onError: () => {
toast({
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to remove this user. Please try again later.`,
),
variant: 'destructive',
duration: 10000,
});
},
});
return (
<Dialog open={open} onOpenChange={(value) => !isDeletingTeamMember && setOpen(value)}>
@ -84,8 +81,7 @@ export const TeamMemberDeleteDialog = ({
<DialogDescription className="mt-4">
<Trans>
You are about to remove the following user from{' '}
<span className="font-semibold">{teamName}</span>.
You are about to remove the following user from <span className="font-semibold">{teamName}</span>.
</Trans>
</DialogDescription>
</DialogHeader>
@ -93,9 +89,7 @@ export const TeamMemberDeleteDialog = ({
{isInheritMemberEnabled ? (
<Alert variant="neutral">
<AlertDescription>
<Trans>
You cannot remove members from this team if the inherit member feature is enabled.
</Trans>
<Trans>You cannot remove members from this team if the inherit member feature is enabled.</Trans>
</AlertDescription>
</Alert>
) : (

View file

@ -1,14 +1,3 @@
import { useEffect, 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 { TeamMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { TEAM_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/teams';
import { EXTENDED_TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
import { isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
@ -23,22 +12,18 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
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 { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { TeamMemberRole } from '@prisma/client';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export type TeamMemberUpdateDialogProps = {
currentUserTeamRole: TeamMemberRole;
@ -125,11 +110,7 @@ export const TeamMemberUpdateDialog = ({
}, [open, currentUserTeamRole, memberTeamRole, form, toast]);
return (
<Dialog
{...props}
open={open}
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
>
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger ?? (
<Button variant="secondary">

View file

@ -1,11 +1,3 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { File as FileIcon, Upload, X } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
@ -20,6 +12,13 @@ import {
} from '@documenso/ui/primitives/dialog';
import { Form, FormControl, FormField, FormItem } from '@documenso/ui/primitives/form/form';
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';
import { Trans } from '@lingui/react/macro';
import { File as FileIcon, Upload, X } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { useCurrentTeam } from '~/providers/team';
@ -37,12 +36,7 @@ export type TemplateBulkSendDialogProps = {
onSuccess?: () => void;
};
export const TemplateBulkSendDialog = ({
templateId,
recipients,
trigger,
onSuccess,
}: TemplateBulkSendDialogProps) => {
export const TemplateBulkSendDialog = ({ templateId, recipients, trigger, onSuccess }: TemplateBulkSendDialogProps) => {
const { _ } = useLingui();
const { toast } = useToast();
@ -58,10 +52,7 @@ export const TemplateBulkSendDialog = ({
const { mutateAsync: uploadBulkSend } = trpc.template.uploadBulkSend.useMutation();
const onDownloadTemplate = () => {
const headers = recipients.flatMap((_, index) => [
`recipient_${index + 1}_email`,
`recipient_${index + 1}_name`,
]);
const headers = recipients.flatMap((_, index) => [`recipient_${index + 1}_email`, `recipient_${index + 1}_name`]);
const exampleRow = recipients.flatMap((recipient) => [recipient.email, recipient.name || '']);
@ -92,9 +83,7 @@ export const TemplateBulkSendDialog = ({
toast({
title: _(msg`Success`),
description: _(
msg`Your bulk send has been initiated. You will receive an email notification upon completion.`,
),
description: _(msg`Your bulk send has been initiated. You will receive an email notification upon completion.`),
});
form.reset();
@ -129,8 +118,8 @@ export const TemplateBulkSendDialog = ({
<DialogDescription>
<Trans>
Upload a CSV file to create multiple documents from this template. Each row represents
one document with its recipient details.
Upload a CSV file to create multiple documents from this template. Each row represents one document with
its recipient details.
</Trans>
</DialogDescription>
</DialogHeader>
@ -138,14 +127,14 @@ export const TemplateBulkSendDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-y-4">
<div className="rounded-lg border bg-muted/70 p-4">
<h3 className="text-sm font-medium">
<h3 className="font-medium text-sm">
<Trans>CSV Structure</Trans>
</h3>
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-1 text-muted-foreground text-sm">
<Trans>
For each recipient, provide their email (required) and name (optional) in separate
columns. Download the template CSV below for the correct format.
For each recipient, provide their email (required) and name (optional) in separate columns. Download
the template CSV below for the correct format.
</Trans>
</p>
@ -153,11 +142,9 @@ export const TemplateBulkSendDialog = ({
<Trans>Current recipients:</Trans>
</p>
<ul className="mt-2 list-inside list-disc text-sm text-muted-foreground">
<ul className="mt-2 list-inside list-disc text-muted-foreground text-sm">
{recipients.map((recipient, index) => (
<li key={index}>
{recipient.name ? `${recipient.name} (${recipient.email})` : recipient.email}
</li>
<li key={index}>{recipient.name ? `${recipient.name} (${recipient.email})` : recipient.email}</li>
))}
</ul>
</div>
@ -167,7 +154,7 @@ export const TemplateBulkSendDialog = ({
<Trans>Download Template CSV</Trans>
</Button>
<p className="text-xs text-muted-foreground">
<p className="text-muted-foreground text-xs">
<Trans>Pre-formatted CSV template with example data.</Trans>
</p>
</div>
@ -207,7 +194,7 @@ export const TemplateBulkSendDialog = ({
<Button
type="button"
variant="link"
className="p-0 text-xs text-destructive hover:text-destructive"
className="p-0 text-destructive text-xs hover:text-destructive"
onClick={() => onChange(null)}
disabled={form.formState.isSubmitting}
>
@ -220,12 +207,11 @@ export const TemplateBulkSendDialog = ({
)}
</FormControl>
{error && <p className="text-sm text-destructive">{error.message}</p>}
{error && <p className="text-destructive text-sm">{error.message}</p>}
<p className="text-xs text-muted-foreground">
<p className="text-muted-foreground text-xs">
<Trans>
Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use
template defaults.
Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use template defaults.
</Trans>
</p>
</FormItem>
@ -239,15 +225,11 @@ export const TemplateBulkSendDialog = ({
<FormItem className="flex items-center space-x-2">
<FormControl>
<div className="flex items-center">
<Checkbox
id="send-immediately"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Checkbox id="send-immediately" checked={field.value} onCheckedChange={field.onChange} />
<label
htmlFor="send-immediately"
className="ml-2 flex items-center text-sm text-muted-foreground"
className="ml-2 flex items-center text-muted-foreground text-sm"
>
<Trans>Send documents to recipients immediately</Trans>
</label>

View file

@ -1,20 +1,3 @@
import { useEffect, useMemo, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { type Recipient, RecipientRole, type TemplateDirectLink } from '@prisma/client';
import {
CircleDotIcon,
CircleIcon,
ClipboardCopyIcon,
InfoIcon,
LinkIcon,
LoaderIcon,
} from 'lucide-react';
import { Link, useRevalidator } from 'react-router';
import { P, match } from 'ts-pattern';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
@ -38,16 +21,17 @@ import {
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { Switch } from '@documenso/ui/primitives/switch';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@documenso/ui/primitives/table';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@documenso/ui/primitives/table';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { type Recipient, RecipientRole, type TemplateDirectLink } from '@prisma/client';
import { CircleDotIcon, CircleIcon, ClipboardCopyIcon, InfoIcon, LinkIcon, LoaderIcon } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { Link, useRevalidator } from 'react-router';
import { match, P } from 'ts-pattern';
type TemplateDirectLinkDialogProps = {
templateId: number;
@ -79,17 +63,14 @@ export const TemplateDirectLinkDialog = ({
const [isEnabled, setIsEnabled] = useState(directLink?.enabled ?? false);
const [token, setToken] = useState(directLink?.token ?? null);
const [selectedRecipientId, setSelectedRecipientId] = useState<number | null>(null);
const [currentStep, setCurrentStep] = useState<TemplateDirectLinkStep>(
token ? 'MANAGE' : 'ONBOARD',
);
const [currentStep, setCurrentStep] = useState<TemplateDirectLinkStep>(token ? 'MANAGE' : 'ONBOARD');
const organisation = useCurrentOrganisation();
const validDirectTemplateRecipients = useMemo(
() =>
recipients.filter(
(recipient) =>
recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
(recipient) => recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
),
[recipients],
);
@ -163,9 +144,7 @@ export const TemplateDirectLinkDialog = ({
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(
msg`We encountered an error while removing the direct template link. Please try again later.`,
),
description: _(msg`We encountered an error while removing the direct template link. Please try again later.`),
variant: 'destructive',
});
},
@ -192,8 +171,7 @@ export const TemplateDirectLinkDialog = ({
});
};
const isLoading =
isCreatingTemplateDirectLink || isTogglingTemplateAccess || isDeletingTemplateDirectLink;
const isLoading = isCreatingTemplateDirectLink || isTogglingTemplateAccess || isDeletingTemplateDirectLink;
useEffect(() => {
resetCreateTemplateDirectLink();
@ -234,13 +212,13 @@ export const TemplateDirectLinkDialog = ({
{DIRECT_TEMPLATE_DOCUMENTATION.map((step, index) => (
<li className="relative" key={index}>
<div className="absolute -left-12">
<div className="flex h-8 w-8 items-center justify-center rounded-full border-[3px] border-neutral-200 text-sm font-bold">
<div className="flex h-8 w-8 items-center justify-center rounded-full border-[3px] border-neutral-200 font-bold text-sm">
{index + 1}
</div>
</div>
<h3 className="font-semibold">{_(step.title)}</h3>
<p className="mt-1 text-sm text-muted-foreground">{_(step.description)}</p>
<p className="mt-1 text-muted-foreground text-sm">{_(step.description)}</p>
</li>
))}
</ul>
@ -249,14 +227,12 @@ export const TemplateDirectLinkDialog = ({
<Alert variant="warning">
<AlertTitle>
<Trans>
Direct template link usage exceeded ({quota.directTemplates}/
{quota.directTemplates})
Direct template link usage exceeded ({quota.directTemplates}/{quota.directTemplates})
</Trans>
</AlertTitle>
<AlertDescription>
<Trans>
You have reached the maximum limit of {quota.directTemplates} direct
templates.{' '}
You have reached the maximum limit of {quota.directTemplates} direct templates.{' '}
<Link
className="mt-1 block underline underline-offset-4"
to={`/o/${organisation.url}/settings/billing`}
@ -326,13 +302,13 @@ export const TemplateDirectLinkDialog = ({
onClick={async () => onRecipientTableRowClick(row.id)}
>
<TableCell>
<div className="text-sm text-muted-foreground">
<div className="text-muted-foreground text-sm">
<p>{row.name}</p>
<p className="text-xs text-muted-foreground/70">{row.email}</p>
<p className="text-muted-foreground/70 text-xs">{row.email}</p>
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
<TableCell className="text-muted-foreground text-sm">
{_(RECIPIENT_ROLES_DESCRIPTION[row.role].roleName)}
</TableCell>
@ -350,13 +326,11 @@ export const TemplateDirectLinkDialog = ({
</div>
{/* Prevent creating placeholder direct template recipient if the email already exists. */}
{!recipients.some(
(recipient) => recipient.email === DIRECT_TEMPLATE_RECIPIENT_EMAIL,
) && (
{!recipients.some((recipient) => recipient.email === DIRECT_TEMPLATE_RECIPIENT_EMAIL) && (
<DialogFooter className="mx-auto">
<div className="flex flex-col items-center justify-center">
{validDirectTemplateRecipients.length !== 0 && (
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>Or</Trans>
</p>
)}
@ -399,19 +373,12 @@ export const TemplateDirectLinkDialog = ({
<InfoIcon className="h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
<Trans>
Disabling direct link signing will prevent anyone from accessing the
link.
</Trans>
<Trans>Disabling direct link signing will prevent anyone from accessing the link.</Trans>
</TooltipContent>
</Tooltip>
</Label>
<Switch
className="mt-2"
checked={isEnabled}
onCheckedChange={(value) => setIsEnabled(value)}
/>
<Switch className="mt-2" checked={isEnabled} onCheckedChange={(value) => setIsEnabled(value)} />
</div>
<div className="mt-2">
@ -428,7 +395,7 @@ export const TemplateDirectLinkDialog = ({
className="pr-12"
/>
<div className="absolute bottom-0 right-1 top-0 flex items-center justify-center">
<div className="absolute top-0 right-1 bottom-0 flex items-center justify-center">
<Button
variant="none"
type="button"
@ -479,18 +446,13 @@ export const TemplateDirectLinkDialog = ({
<DialogDescription>
<Trans>
Please note that proceeding will remove direct linking recipient and turn it
into a placeholder.
Please note that proceeding will remove direct linking recipient and turn it into a placeholder.
</Trans>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
type="button"
variant="secondary"
onClick={() => setCurrentStep('MANAGE')}
>
<Button type="button" variant="secondary" onClick={() => setCurrentStep('MANAGE')}>
<Trans>Cancel</Trans>
</Button>

View file

@ -1,15 +1,3 @@
import { useEffect, 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 * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Loader2, Search } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { FolderType } from '@documenso/lib/types/folder-type';
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
@ -23,16 +11,19 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
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';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Loader2, Search } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
import { useCurrentTeam } from '~/providers/team';
@ -157,7 +148,7 @@ export function TemplateMoveToFolderDialog({
</DialogHeader>
<div className="relative">
<Search className="text-muted-foreground absolute left-2 top-3 h-4 w-4" />
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={_(msg`Search folders...`)}
value={searchTerm}
@ -211,7 +202,7 @@ export function TemplateMoveToFolderDialog({
))}
{searchTerm && filteredFolders?.length === 0 && (
<div className="text-muted-foreground px-2 py-2 text-center text-sm">
<div className="px-2 py-2 text-center text-muted-foreground text-sm">
<Trans>No folders found</Trans>
</div>
)}

View file

@ -1,25 +1,9 @@
import { useEffect, 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 { Recipient } from '@prisma/client';
import { DocumentDistributionMethod, DocumentSigningOrder } from '@prisma/client';
import { FileTextIcon, InfoIcon, Plus, UploadCloudIcon, X } from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import * as z from 'zod';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import {
TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX,
TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX,
} from '@documenso/lib/constants/template';
import {
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
SKIP_QUERY_BATCH_META,
} from '@documenso/lib/constants/trpc';
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION, SKIP_QUERY_BATCH_META } from '@documenso/lib/constants/trpc';
import { AppError } from '@documenso/lib/errors/app-error';
import { ZRecipientEmailSchema } from '@documenso/lib/types/recipient';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
@ -37,19 +21,23 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import type { Toast } from '@documenso/ui/primitives/use-toast';
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';
import { Trans } from '@lingui/react/macro';
import type { Recipient } from '@prisma/client';
import { DocumentDistributionMethod, DocumentSigningOrder } from '@prisma/client';
import { FileTextIcon, InfoIcon, Plus, UploadCloudIcon, X } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import * as z from 'zod';
const ZAddRecipientsForNewDocumentSchema = z.object({
distributeDocument: z.boolean(),
@ -127,13 +115,9 @@ export function TemplateUseDialog({
recipients: recipients
.sort((a, b) => (a.signingOrder || 0) - (b.signingOrder || 0))
.map((recipient) => {
const isRecipientEmailPlaceholder = recipient.email.match(
TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX,
);
const isRecipientEmailPlaceholder = recipient.email.match(TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX);
const isRecipientNamePlaceholder = recipient.name.match(
TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX,
);
const isRecipientNamePlaceholder = recipient.name.match(TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX);
return {
id: recipient.id,
@ -155,8 +139,7 @@ export function TemplateUseDialog({
name: 'customDocumentData',
});
const { mutateAsync: createDocumentFromTemplate } =
trpc.template.createDocumentFromTemplate.useMutation();
const { mutateAsync: createDocumentFromTemplate } = trpc.template.createDocumentFromTemplate.useMutation();
const onSubmit = async (data: TAddRecipientsForNewDocumentSchema) => {
try {
@ -191,10 +174,7 @@ export function TemplateUseDialog({
let documentPath = `${documentRootPath}/${envelopeId}`;
if (
data.distributeDocument &&
documentDistributionMethod === DocumentDistributionMethod.NONE
) {
if (data.distributeDocument && documentDistributionMethod === DocumentDistributionMethod.NONE) {
documentPath += '?action=view-signing-links';
}
@ -209,9 +189,7 @@ export function TemplateUseDialog({
};
if (error.code === 'DOCUMENT_SEND_FAILED') {
toastPayload.description = _(
msg`The document was created but could not be sent to recipients.`,
);
toastPayload.description = _(msg`The document was created but could not be sent to recipients.`);
}
toast(toastPayload);
@ -246,7 +224,7 @@ export function TemplateUseDialog({
<DialogTrigger asChild>
{trigger || (
<Button variant="outline" className="bg-background">
<Plus className="-ml-1 mr-2 h-4 w-4" />
<Plus className="mr-2 -ml-1 h-4 w-4" />
<Trans>Use Template</Trans>
</Button>
)}
@ -286,10 +264,7 @@ export function TemplateUseDialog({
{...field}
disabled
className="items-center justify-center"
value={
field.value?.toString() ||
recipients[index]?.signingOrder?.toString()
}
value={field.value?.toString() || recipients[index]?.signingOrder?.toString()}
/>
</FormControl>
<FormMessage />
@ -359,7 +334,7 @@ export function TemplateUseDialog({
{documentDistributionMethod === DocumentDistributionMethod.EMAIL && (
<label
className="ml-2 flex items-center text-sm text-muted-foreground"
className="ml-2 flex items-center text-muted-foreground text-sm"
htmlFor="distributeDocument"
>
<Trans>Send document</Trans>
@ -371,15 +346,12 @@ export function TemplateUseDialog({
<TooltipContent className="z-[99999] max-w-md space-y-2 p-4 text-muted-foreground">
<p>
<Trans>
The document will be immediately sent to recipients if this
is checked.
The document will be immediately sent to recipients if this is checked.
</Trans>
</p>
<p>
<Trans>
Otherwise, the document will be created as a draft.
</Trans>
<Trans>Otherwise, the document will be created as a draft.</Trans>
</p>
</TooltipContent>
</Tooltip>
@ -388,7 +360,7 @@ export function TemplateUseDialog({
{documentDistributionMethod === DocumentDistributionMethod.NONE && (
<label
className="ml-2 flex items-center text-sm text-muted-foreground"
className="ml-2 flex items-center text-muted-foreground text-sm"
htmlFor="distributeDocument"
>
<Trans>Create as pending</Trans>
@ -398,9 +370,7 @@ export function TemplateUseDialog({
</TooltipTrigger>
<TooltipContent className="z-[99999] max-w-md space-y-2 p-4 text-muted-foreground">
<p>
<Trans>
Create the document as pending and ready to sign.
</Trans>
<Trans>Create the document as pending and ready to sign.</Trans>
</p>
<p>
@ -409,8 +379,8 @@ export function TemplateUseDialog({
<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>
</TooltipContent>
@ -442,7 +412,7 @@ export function TemplateUseDialog({
}}
/>
<label
className="ml-2 flex items-center text-sm text-muted-foreground"
className="ml-2 flex items-center text-muted-foreground text-sm"
htmlFor="useCustomDocument"
>
<Trans>Upload custom document</Trans>
@ -453,8 +423,7 @@ export function TemplateUseDialog({
<TooltipContent className="z-[99999] max-w-md space-y-2 p-4 text-muted-foreground">
<p>
<Trans>
Upload a custom document to use instead of the template's default
document
Upload a custom document to use instead of the template's default document
</Trans>
</p>
</TooltipContent>
@ -489,16 +458,11 @@ export function TemplateUseDialog({
</div>
<div className="min-w-0 flex-1">
<h4 className="truncate text-sm font-medium text-foreground">
{item.title}
</h4>
<p className="mt-0.5 text-xs text-muted-foreground">
<h4 className="truncate font-medium text-foreground text-sm">{item.title}</h4>
<p className="mt-0.5 text-muted-foreground text-xs">
{field.value ? (
<div>
<Trans>
Custom {(field.value.size / (1024 * 1024)).toFixed(2)}{' '}
MB file
</Trans>
<Trans>Custom {(field.value.size / (1024 * 1024)).toFixed(2)} MB file</Trans>
</div>
) : (
<Trans>Default file</Trans>
@ -567,10 +531,7 @@ export function TemplateUseDialog({
return;
}
if (
file.size >
APP_DOCUMENT_UPLOAD_SIZE_LIMIT * 1024 * 1024
) {
if (file.size > APP_DOCUMENT_UPLOAD_SIZE_LIMIT * 1024 * 1024) {
form.setError('customDocumentData', {
type: 'manual',
message: _(

Some files were not shown because too many files have changed in this diff Show more