mirror of
https://github.com/documenso/documenso
synced 2026-04-21 13:27:18 +00:00
fix: lint project
This commit is contained in:
parent
b7b077f57c
commit
89eb086b13
1384 changed files with 12667 additions and 23921 deletions
|
|
@ -10,4 +10,4 @@
|
|||
"baseDir": "src",
|
||||
"uiLibrary": "radix-ui",
|
||||
"commands": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => ({
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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>>;
|
||||
|
|
|
|||
|
|
@ -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>>;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}`}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)}>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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)}>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue