Compare commits

...

34 Commits

Author SHA1 Message Date
Mythie
d71e43c5d6 fix: minor tidying 2023-08-30 14:01:30 +10:00
Ephraim Atta-Duncan
ed6fa4dc2a feat: avoid updating password with existing password 2023-08-30 03:26:24 +00:00
Ephraim Atta-Duncan
4f3970c361 feat: prevent a user from updating password with the same password 2023-08-30 03:22:47 +00:00
Ephraim Atta-Duncan
40767430d9 feat: reset password from on submit 2023-08-30 03:09:40 +00:00
Lucas Smith
8e42dcb7ee Merge pull request #323 from documenso/feat/promise-safety
feat: promise safety
2023-08-30 11:32:32 +10:00
Lucas Smith
1888ee97e6 Merge pull request #296 from documenso/feat/inbox
feat: add inbox
2023-08-30 10:27:32 +10:00
Lucas Smith
068aef665d Merge pull request #328 from nsylke/nsylke-patch-8
chore: change root package.json name
2023-08-30 10:10:42 +10:00
nsylke
2772fc1678 chore: change root package.json name 2023-08-29 16:19:56 -05:00
Mythie
8c4120f0a2 fix: remove further unused code 2023-08-29 18:12:46 +10:00
Mythie
9f93af6134 fix: remove unused code 2023-08-29 17:26:19 +10:00
Mythie
68a5a9da1e feat: add data table actions 2023-08-29 14:33:07 +10:00
Mythie
1f8d5e45e1 feat: onepage inbox 2023-08-29 14:33:05 +10:00
David Nguyen
8fd9730e2b feat: add inbox 2023-08-29 14:31:13 +10:00
Lucas Smith
04f6df6839 Merge pull request #304 from G3root/fix-overscroll
fix: tab overflow on smaller viewport and tab scroll ux
2023-08-29 13:26:26 +10:00
Mythie
ca40e983e3 feat: promise safety with eslint 2023-08-29 13:01:19 +10:00
Lucas Smith
ba054ae915 Merge pull request #319 from documenso/chore/reduce-refetch-time
chore: reduce open page caching time to 1 hour
2023-08-28 22:58:05 +10:00
Lucas Smith
bf71d2a14e Merge pull request #305 from G3root/responsive-signing-card
fix: make signing form card responsive
2023-08-28 13:07:46 +10:00
Lucas Smith
163911255e Merge pull request #308 from adithyaakrishna/feat/pr-validate
feat: pr title validator workflow
2023-08-28 12:21:36 +10:00
Lucas Smith
24e38a3bbc Merge pull request #309 from nsylke/nsylke-patch-7
feat: set min/max lengths and autocomplete for password
2023-08-28 12:21:08 +10:00
Lucas Smith
dfd714f16a Merge pull request #310 from adithyaakrishna/feat/add-sharp
feat: added sharp package for NextJS13 image optimizations
2023-08-28 12:19:33 +10:00
Mythie
722081f89e fix: dependency ordering 2023-08-28 12:14:15 +10:00
Lucas Smith
f0e1df22b8 Merge pull request #312 from G3root/fix-auth
fix: authentication allowing any password
2023-08-28 12:04:07 +10:00
nafees nazik
615cb263fb fix: authentication 2023-08-27 07:10:41 +05:30
nafees nazik
18faaf49d9 fix: style 2023-08-27 06:26:49 +05:30
Adithya Krishna
650b69ae56 feat: added sharp for image optimizations on nextjs
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-08-26 21:23:30 +05:30
Adithya Krishna
eb4be963e3 fix: added eol to workflow file
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-08-26 20:57:02 +05:30
Adithya Krishna
27c27743e3 chore: updated workflow name
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-08-26 20:57:02 +05:30
Adithya Krishna
92930a2f63 feat: added pr title validator
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-08-26 20:57:02 +05:30
nsylke
7ad3365b0e feat: add autocomplete for password managers 2023-08-26 10:22:44 -05:00
nsylke
f8bf4fea36 feat: set min/max lengths for password 2023-08-26 09:53:58 -05:00
Lucas Smith
10cd8144eb Merge pull request #307 from adithyaakrishna/feat/optimize-images
chore: optimize images
2023-08-26 20:06:48 +10:00
Adithya Krishna
66973a3745 chore: optimized images to save ~8mb
Signed-off-by: Adithya Krishna <adikrish@redhat.com>
2023-08-26 13:13:30 +05:30
nafees nazik
85677bb792 fix: make signing form card responsive 2023-08-26 08:47:19 +05:30
nafees nazik
7ae99d2038 fix: overflow and scroll ux 2023-08-26 07:22:51 +05:30
88 changed files with 1427 additions and 360 deletions

7
.eslintignore Normal file
View File

@@ -0,0 +1,7 @@
# Config files
*.config.js
*.config.cjs
# Statically hosted javascript files
apps/*/public/*.js
apps/*/public/*.cjs

View File

@@ -0,0 +1,21 @@
name: "Validate PR Name"
on:
pull_request_target:
types:
- opened
- reopened
- edited
- synchronize
permissions:
pull-requests: read
jobs:
validate-pr:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -30,6 +30,7 @@
"react-hook-form": "^7.43.9",
"react-icons": "^4.8.0",
"recharts": "^2.7.2",
"sharp": "0.32.5",
"typescript": "5.1.6",
"zod": "^3.21.4"
},

View File

@@ -5,7 +5,7 @@ import { allDocuments } from 'contentlayer/generated';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
export const generateStaticParams = async () =>
export const generateStaticParams = () =>
allDocuments.map((post) => ({ post: post._raw.flattenedPath }));
export const generateMetadata = ({ params }: { params: { content: string } }) => {

View File

@@ -7,7 +7,7 @@ import { ChevronLeft } from 'lucide-react';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
export const generateStaticParams = async () =>
export const generateStaticParams = () =>
allBlogPosts.map((post) => ({ post: post._raw.flattenedPath }));
export const generateMetadata = ({ params }: { params: { post: string } }) => {

View File

@@ -43,7 +43,7 @@ export default async function OpenPage() {
accept: 'application/vnd.github.v3+json',
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => ZGithubStatsResponse.parse(res));
const { total_count: mergedPullRequests } = await fetch(
@@ -54,7 +54,7 @@ export default async function OpenPage() {
},
},
)
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => ZMergedPullRequestsResponse.parse(res));
const STARGAZERS_DATA = await fetch('https://stargrazer-live.onrender.com/api/stats', {
@@ -62,7 +62,7 @@ export default async function OpenPage() {
accept: 'application/json',
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => ZStargazersLiveResponse.parse(res));
return (

View File

@@ -24,7 +24,7 @@ export default async function IndexPage() {
accept: 'application/vnd.github.v3+json',
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => (typeof res.stargazers_count === 'number' ? res.stargazers_count : undefined))
.catch(() => undefined);

View File

@@ -63,7 +63,9 @@ export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialog
const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => {
try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000));
const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
const [redirectUrl] = await Promise.all([
claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }),

View File

@@ -13,7 +13,7 @@ export const PasswordReveal = ({ password }: PasswordRevealProps) => {
const [, copy] = useCopyToClipboard();
const onCopyClick = () => {
copy(password).then(() => {
void copy(password).then(() => {
toast({
title: 'Copied to clipboard',
description: 'Your password has been copied to your clipboard.',

View File

@@ -124,7 +124,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
setValue('signatureDataUrl', draftSignatureDataUrl);
setValue('signatureText', '');
trigger('signatureDataUrl');
void trigger('signatureDataUrl');
setShowSigningDialog(false);
};
@@ -135,7 +135,9 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
signatureText,
}: TWidgetFormSchema) => {
try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000));
const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
// eslint-disable-next-line turbo/no-undeclared-env-vars
const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID;

View File

@@ -21,6 +21,7 @@
"formidable": "^2.1.1",
"framer-motion": "^10.12.8",
"lucide-react": "^0.214.0",
"luxon": "^3.4.0",
"micro": "^10.0.1",
"nanoid": "^4.0.2",
"next": "13.4.12",
@@ -36,12 +37,14 @@
"react-hook-form": "^7.43.9",
"react-icons": "^4.8.0",
"react-rnd": "^10.4.1",
"sharp": "0.32.5",
"ts-pattern": "^5.0.5",
"typescript": "5.1.6",
"zod": "^3.21.4"
},
"devDependencies": {
"@types/formidable": "^2.0.6",
"@types/luxon": "^3.3.1",
"@types/node": "20.1.0",
"@types/react": "18.2.18",
"@types/react-dom": "18.2.7"

View File

@@ -22,14 +22,14 @@ import { LocaleDate } from '~/components/formatter/locale-date';
import { UploadDocument } from './upload-document';
export default async function DashboardPage() {
const session = await getRequiredServerComponentSession();
const user = await getRequiredServerComponentSession();
const [stats, results] = await Promise.all([
getStats({
userId: session.id,
user,
}),
findDocuments({
userId: session.id,
userId: user.id,
perPage: 10,
}),
]);

View File

@@ -0,0 +1,65 @@
'use client';
import Link from 'next/link';
import { Edit, Pencil, Share } from 'lucide-react';
import { useSession } from 'next-auth/react';
import { match } from 'ts-pattern';
import { Document, DocumentStatus, Recipient, SigningStatus, User } from '@documenso/prisma/client';
import { Button } from '@documenso/ui/primitives/button';
export type DataTableActionButtonProps = {
row: Document & {
User: Pick<User, 'id' | 'name' | 'email'>;
Recipient: Recipient[];
};
};
export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => {
const { data: session } = useSession();
if (!session) {
return null;
}
const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email);
const isOwner = row.User.id === session.user.id;
const isRecipient = !!recipient;
const isDraft = row.status === DocumentStatus.DRAFT;
const isPending = row.status === DocumentStatus.PENDING;
const isComplete = row.status === DocumentStatus.COMPLETED;
const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
return match({
isOwner,
isRecipient,
isDraft,
isPending,
isComplete,
isSigned,
})
.with({ isOwner: true, isDraft: true }, () => (
<Button className="w-24" asChild>
<Link href={`/documents/${row.id}`}>
<Edit className="-ml-1 mr-2 h-4 w-4" />
Edit
</Link>
</Button>
))
.with({ isRecipient: true, isPending: true, isSigned: false }, () => (
<Button className="w-24" asChild>
<Link href={`/sign/${recipient?.token}`}>
<Pencil className="-ml-1 mr-2 h-4 w-4" />
Sign
</Link>
</Button>
))
.otherwise(() => (
<Button className="w-24" disabled>
<Share className="-ml-1 mr-2 h-4 w-4" />
Share
</Button>
));
};

View File

@@ -0,0 +1,133 @@
'use client';
import Link from 'next/link';
import {
Copy,
Download,
Edit,
History,
MoreHorizontal,
Pencil,
Share,
Trash2,
XCircle,
} from 'lucide-react';
import { useSession } from 'next-auth/react';
import { Document, DocumentStatus, Recipient, User } from '@documenso/prisma/client';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from '@documenso/ui/primitives/dropdown-menu';
export type DataTableActionDropdownProps = {
row: Document & {
User: Pick<User, 'id' | 'name' | 'email'>;
Recipient: Recipient[];
};
};
export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) => {
const { data: session } = useSession();
if (!session) {
return null;
}
const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email);
const isOwner = row.User.id === session.user.id;
// const isRecipient = !!recipient;
// const isDraft = row.status === DocumentStatus.DRAFT;
// const isPending = row.status === DocumentStatus.PENDING;
const isComplete = row.status === DocumentStatus.COMPLETED;
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
const onDownloadClick = () => {
let decodedDocument = row.document;
try {
decodedDocument = atob(decodedDocument);
} catch (err) {
// We're just going to ignore this error and try to download the document
console.error(err);
}
const documentBytes = Uint8Array.from(decodedDocument.split('').map((c) => c.charCodeAt(0)));
const blob = new Blob([documentBytes], {
type: 'application/pdf',
});
const link = window.document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = row.title || 'document.pdf';
link.click();
window.URL.revokeObjectURL(link.href);
};
return (
<DropdownMenu>
<DropdownMenuTrigger>
<MoreHorizontal className="h-5 w-5 text-gray-500" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52" align="start" forceMount>
<DropdownMenuLabel>Action</DropdownMenuLabel>
<DropdownMenuItem disabled={!recipient} asChild>
<Link href={`/sign/${recipient?.token}`}>
<Pencil className="mr-2 h-4 w-4" />
Sign
</Link>
</DropdownMenuItem>
<DropdownMenuItem disabled={!isOwner} asChild>
<Link href={`/documents/${row.id}`}>
<Edit className="mr-2 h-4 w-4" />
Edit
</Link>
</DropdownMenuItem>
<DropdownMenuItem disabled={!isComplete} onClick={onDownloadClick}>
<Download className="mr-2 h-4 w-4" />
Download
</DropdownMenuItem>
<DropdownMenuItem disabled>
<Copy className="mr-2 h-4 w-4" />
Duplicate
</DropdownMenuItem>
<DropdownMenuItem disabled>
<XCircle className="mr-2 h-4 w-4" />
Void
</DropdownMenuItem>
<DropdownMenuItem disabled>
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
<DropdownMenuLabel>Share</DropdownMenuLabel>
<DropdownMenuItem disabled>
<History className="mr-2 h-4 w-4" />
Resend
</DropdownMenuItem>
<DropdownMenuItem disabled>
<Share className="mr-2 h-4 w-4" />
Share
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

View File

@@ -8,7 +8,7 @@ import { Loader } from 'lucide-react';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { FindResultSet } from '@documenso/lib/types/find-result-set';
import { DocumentWithReciepient } from '@documenso/prisma/types/document-with-recipient';
import { Document, Recipient, User } from '@documenso/prisma/client';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
@@ -16,8 +16,16 @@ import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-a
import { DocumentStatus } from '~/components/formatter/document-status';
import { LocaleDate } from '~/components/formatter/locale-date';
import { DataTableActionButton } from './data-table-action-button';
import { DataTableActionDropdown } from './data-table-action-dropdown';
export type DocumentsDataTableProps = {
results: FindResultSet<DocumentWithReciepient>;
results: FindResultSet<
Document & {
Recipient: Recipient[];
User: Pick<User, 'id' | 'name' | 'email'>;
}
>;
};
export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => {
@@ -45,7 +53,11 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => {
{
header: 'Title',
cell: ({ row }) => (
<Link href={`/documents/${row.original.id}`} className="font-medium hover:underline">
<Link
href={`/documents/${row.original.id}`}
title={row.original.title}
className="block max-w-[10rem] truncate font-medium hover:underline md:max-w-[20rem]"
>
{row.original.title}
</Link>
),
@@ -67,6 +79,15 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => {
accessorKey: 'created',
cell: ({ row }) => <LocaleDate date={row.getValue('created')} />,
},
{
header: 'Actions',
cell: ({ row }) => (
<div className="flex items-center gap-x-4">
<DataTableActionButton row={row.original} />
<DataTableActionDropdown row={row.original} />
</div>
),
},
]}
data={results.data}
perPage={results.perPage}

View File

@@ -3,8 +3,8 @@ import Link from 'next/link';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import { getStats } from '@documenso/lib/server-only/document/get-stats';
import { isDocumentStatus } from '@documenso/lib/types/is-document-status';
import { DocumentStatus as InternalDocumentStatus } from '@documenso/prisma/client';
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
import { PeriodSelector } from '~/components/(dashboard)/period-selector/period-selector';
@@ -16,7 +16,7 @@ import { DocumentsDataTable } from './data-table';
export type DocumentsPageProps = {
searchParams?: {
status?: InternalDocumentStatus | 'ALL';
status?: ExtendedDocumentStatus;
period?: PeriodSelectorValue;
page?: string;
perPage?: string;
@@ -24,22 +24,20 @@ export type DocumentsPageProps = {
};
export default async function DocumentsPage({ searchParams = {} }: DocumentsPageProps) {
const session = await getRequiredServerComponentSession();
const user = await getRequiredServerComponentSession();
const stats = await getStats({
userId: session.id,
user,
});
const status = isDocumentStatus(searchParams.status) ? searchParams.status : 'ALL';
const status = isExtendedDocumentStatus(searchParams.status) ? searchParams.status : 'ALL';
// const period = isPeriodSelectorValue(searchParams.period) ? searchParams.period : '';
const page = Number(searchParams.page) || 1;
const perPage = Number(searchParams.perPage) || 20;
const shouldDefaultToPending = status === 'ALL' && stats.PENDING > 0;
const results = await findDocuments({
userId: session.id,
status: status === 'ALL' ? undefined : status,
userId: user.id,
status,
orderBy: {
column: 'created',
direction: 'desc',
@@ -57,10 +55,6 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage
params.delete('page');
}
if (value === 'ALL') {
params.delete('status');
}
return `/documents?${params.toString()}`;
};
@@ -71,41 +65,27 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage
<h1 className="mt-12 text-4xl font-semibold">Documents</h1>
<div className="mt-8 flex flex-wrap gap-x-4 gap-y-6">
<Tabs defaultValue={shouldDefaultToPending ? InternalDocumentStatus.PENDING : status}>
<Tabs defaultValue={status} className="overflow-x-auto">
<TabsList>
<TabsTrigger className="min-w-[60px]" value={InternalDocumentStatus.PENDING} asChild>
<Link href={getTabHref(InternalDocumentStatus.PENDING)}>
<DocumentStatus status={InternalDocumentStatus.PENDING} />
{[
ExtendedDocumentStatus.INBOX,
ExtendedDocumentStatus.PENDING,
ExtendedDocumentStatus.COMPLETED,
ExtendedDocumentStatus.DRAFT,
ExtendedDocumentStatus.ALL,
].map((value) => (
<TabsTrigger key={value} className="min-w-[60px]" value={value} asChild>
<Link href={getTabHref(value)} scroll={false}>
<DocumentStatus status={value} />
<span className="ml-1 hidden opacity-50 md:inline-block">
{Math.min(stats.PENDING, 99)}
</span>
</Link>
</TabsTrigger>
<TabsTrigger className="min-w-[60px]" value={InternalDocumentStatus.COMPLETED} asChild>
<Link href={getTabHref(InternalDocumentStatus.COMPLETED)}>
<DocumentStatus status={InternalDocumentStatus.COMPLETED} />
<span className="ml-1 hidden opacity-50 md:inline-block">
{Math.min(stats.COMPLETED, 99)}
</span>
</Link>
</TabsTrigger>
<TabsTrigger className="min-w-[60px]" value={InternalDocumentStatus.DRAFT} asChild>
<Link href={getTabHref(InternalDocumentStatus.DRAFT)}>
<DocumentStatus status={InternalDocumentStatus.DRAFT} />
<span className="ml-1 hidden opacity-50 md:inline-block">
{Math.min(stats.DRAFT, 99)}
</span>
</Link>
</TabsTrigger>
<TabsTrigger className="min-w-[60px]" value="ALL" asChild>
<Link href={getTabHref('ALL')}>All</Link>
</TabsTrigger>
{value !== ExtendedDocumentStatus.ALL && (
<span className="ml-1 hidden opacity-50 md:inline-block">
{Math.min(stats[value], 99)}
</span>
)}
</Link>
</TabsTrigger>
))}
</TabsList>
</Tabs>

View File

@@ -64,7 +64,7 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
<hr className="border-border mb-8 mt-4" />
<div className="-mx-2 flex flex-1 flex-col overflow-y-auto px-2">
<div className="-mx-2 flex flex-1 flex-col gap-4 overflow-y-auto px-2">
<div className="flex flex-1 flex-col gap-y-4">
<div>
<Label htmlFor="full-name">Full Name</Label>
@@ -98,10 +98,10 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
</div>
</div>
<div className="flex gap-4">
<div className="flex flex-col gap-4 md:flex-row">
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
className="dark:bg-muted dark:hover:bg-muted/80 w-full bg-black/5 hover:bg-black/10"
variant="secondary"
size="lg"
>
@@ -109,8 +109,8 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
</Button>
<Button
className="w-full"
type="submit"
className="flex-1"
size="lg"
disabled={!isComplete || isSubmitting}
>

View File

@@ -149,7 +149,7 @@ export const NameField = ({ field, recipient }: NameFieldProps) => {
disabled={!localFullName}
onClick={() => {
setShowFullNameModal(false);
onSign('local');
void onSign('local');
}}
>
Sign

View File

@@ -57,7 +57,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
</p>
</div>
<div className="mt-8 grid grid-cols-12 gap-8">
<div className="mt-8 grid grid-cols-12 gap-y-8 lg:gap-x-8 lg:gap-y-0">
<Card
className="col-span-12 rounded-xl before:rounded-xl lg:col-span-7 xl:col-span-8"
gradient

View File

@@ -182,7 +182,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
disabled={!localSignature}
onClick={() => {
setShowSignatureModal(false);
onSign('local');
void onSign('local');
}}
>
Sign

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 KiB

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 MiB

After

Width:  |  Height:  |  Size: 14 MiB

View File

@@ -7,9 +7,11 @@ import { cn } from '@documenso/ui/lib/utils';
export type DesktopNavProps = HTMLAttributes<HTMLDivElement>;
export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
// const pathname = usePathname();
return (
<div className={cn('ml-8 hidden flex-1 gap-x-6 md:flex', className)} {...props}>
{/* No Nav tabs while there is only one main page */}
{/* We have no other subpaths rn */}
{/* <Link
href="/documents"
className={cn(

View File

@@ -4,11 +4,8 @@ import { HTMLAttributes } from 'react';
import Link from 'next/link';
import { Menu } from 'lucide-react';
import { User } from '@documenso/prisma/client';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Logo } from '~/components/branding/logo';
@@ -23,7 +20,7 @@ export const Header = ({ className, user, ...props }: HeaderProps) => {
return (
<header
className={cn(
'supports-backdrop-blur:bg-background/60 bg-background/95 sticky top-0 z-40 flex h-16 w-full items-center border-b backdrop-blur',
'supports-backdrop-blur:bg-background/60 bg-background/95 sticky top-0 z-50 flex h-16 w-full items-center border-b backdrop-blur',
className,
)}
{...props}
@@ -41,9 +38,9 @@ export const Header = ({ className, user, ...props }: HeaderProps) => {
<div className="flex gap-x-4">
<ProfileDropdown user={user} />
<Button variant="outline" size="sm" className="h-10 w-10 p-0.5 md:hidden">
{/* <Button variant="outline" size="sm" className="h-10 w-10 p-0.5 md:hidden">
<Menu className="h-6 w-6" />
</Button>
</Button> */}
</div>
</div>
</header>

View File

@@ -118,7 +118,7 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
<DropdownMenuItem
onSelect={() =>
signOut({
void signOut({
callbackUrl: '/',
})
}

View File

@@ -39,7 +39,7 @@ export const PeriodSelector = () => {
params.delete('period');
}
router.push(`${pathname}?${params.toString()}`);
router.push(`${pathname}?${params.toString()}`, { scroll: false });
};
return (

View File

@@ -63,7 +63,9 @@ export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialog
const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => {
try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000));
const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
const [redirectUrl] = await Promise.all([
claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }),

View File

@@ -13,7 +13,7 @@ export const PasswordReveal = ({ password }: PasswordRevealProps) => {
const [, copy] = useCopyToClipboard();
const onCopyClick = () => {
copy(password).then(() => {
void copy(password).then(() => {
toast({
title: 'Copied to clipboard',
description: 'Your password has been copied to your clipboard.',

View File

@@ -124,7 +124,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
setValue('signatureDataUrl', draftSignatureDataUrl);
setValue('signatureText', '');
trigger('signatureDataUrl');
void trigger('signatureDataUrl');
setShowSigningDialog(false);
};
@@ -135,7 +135,9 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
signatureText,
}: TWidgetFormSchema) => {
try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000));
const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
// eslint-disable-next-line turbo/no-undeclared-env-vars
const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID;

View File

@@ -3,16 +3,17 @@ import { HTMLAttributes } from 'react';
import { CheckCircle2, Clock, File } from 'lucide-react';
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
import { DocumentStatus as InternalDocumentStatus } from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { SignatureIcon } from '@documenso/ui/icons/signature';
import { cn } from '@documenso/ui/lib/utils';
type FriendlyStatus = {
label: string;
icon: LucideIcon;
icon?: LucideIcon;
color: string;
};
const FRIENDLY_STATUS_MAP: Record<InternalDocumentStatus, FriendlyStatus> = {
const FRIENDLY_STATUS_MAP: Record<ExtendedDocumentStatus, FriendlyStatus> = {
PENDING: {
label: 'Pending',
icon: Clock,
@@ -28,10 +29,19 @@ const FRIENDLY_STATUS_MAP: Record<InternalDocumentStatus, FriendlyStatus> = {
icon: File,
color: 'text-yellow-500',
},
INBOX: {
label: 'Inbox',
icon: SignatureIcon,
color: 'text-muted-foreground',
},
ALL: {
label: 'All',
color: 'text-muted-foreground',
},
};
export type DocumentStatusProps = HTMLAttributes<HTMLSpanElement> & {
status: InternalDocumentStatus;
status: ExtendedDocumentStatus;
inheritColor?: boolean;
};
@@ -45,11 +55,13 @@ export const DocumentStatus = ({
return (
<span className={cn('flex items-center', className)} {...props}>
<Icon
className={cn('mr-2 inline-block h-4 w-4', {
[color]: !inheritColor,
})}
/>
{Icon && (
<Icon
className={cn('mr-2 inline-block h-4 w-4', {
[color]: !inheritColor,
})}
/>
)}
{label}
</span>
);

View File

@@ -18,8 +18,8 @@ import { FormErrorMessage } from '../form/form-error-message';
export const ZPasswordFormSchema = z
.object({
password: z.string().min(6),
repeatedPassword: z.string().min(6),
password: z.string().min(6).max(72),
repeatedPassword: z.string().min(6).max(72),
})
.refine((data) => data.password === data.repeatedPassword, {
message: 'Passwords do not match',
@@ -39,6 +39,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
const {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting },
} = useForm<TPasswordFormSchema>({
values: {
@@ -56,6 +57,8 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
password,
});
reset();
toast({
title: 'Password updated',
description: 'Your password has been updated successfully.',
@@ -73,7 +76,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
title: 'An unknown error occurred',
variant: 'destructive',
description:
'We encountered an unknown error while attempting to sign you In. Please try again later.',
'We encountered an unknown error while attempting to update your password. Please try again later.',
});
}
}
@@ -92,6 +95,9 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
<Input
id="password"
type="password"
minLength={6}
maxLength={72}
autoComplete="new-password"
className="bg-background mt-2"
{...register('password')}
/>
@@ -107,6 +113,9 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
<Input
id="repeated-password"
type="password"
minLength={6}
maxLength={72}
autoComplete="new-password"
className="bg-background mt-2"
{...register('repeatedPassword')}
/>

View File

@@ -15,7 +15,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZSignInFormSchema = z.object({
email: z.string().email().min(1),
password: z.string().min(1),
password: z.string().min(6).max(72),
});
export type TSignInFormSchema = z.infer<typeof ZSignInFormSchema>;
@@ -76,10 +76,7 @@ export const SignInForm = ({ className }: SignInFormProps) => {
return (
<form
className={cn('flex w-full flex-col gap-y-4', className)}
onSubmit={(e) => {
e.preventDefault();
handleSubmit(onFormSubmit)();
}}
onSubmit={handleSubmit(onFormSubmit)}
>
<div>
<Label htmlFor="email" className="text-slate-500">
@@ -99,6 +96,9 @@ export const SignInForm = ({ className }: SignInFormProps) => {
<Input
id="password"
type="password"
minLength={6}
maxLength={72}
autoComplete="current-password"
className="bg-background mt-2"
{...register('password')}
/>

View File

@@ -18,7 +18,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZSignUpFormSchema = z.object({
name: z.string().min(1),
email: z.string().email().min(1),
password: z.string().min(1),
password: z.string().min(6).max(72),
});
export type TSignUpFormSchema = z.infer<typeof ZSignUpFormSchema>;
@@ -105,6 +105,9 @@ export const SignUpForm = ({ className }: SignUpFormProps) => {
<Input
id="password"
type="password"
minLength={6}
maxLength={72}
autoComplete="new-password"
className="bg-background mt-2"
{...register('password')}
/>

View File

@@ -32,7 +32,7 @@ export const getFlag = async (
revalidate: 60,
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => ZFeatureFlagValueSchema.parse(res))
.catch(() => false);
@@ -64,7 +64,7 @@ export const getAllFlags = async (
revalidate: 60,
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
.catch(() => LOCAL_FEATURE_FLAGS);
};

View File

@@ -11,6 +11,6 @@ export default function PostHogServerClient() {
return new PostHog(postHogConfig.key, {
host: postHogConfig.host,
fetch: (...args) => fetch(...args),
fetch: async (...args) => fetch(...args),
});
}

View File

@@ -0,0 +1,18 @@
import { useEffect, useState } from 'react';
export function useDebouncedValue<T>(value: T, delay: number) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}

View File

@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
export default async function middleware(req: NextRequest) {
export default function middleware(req: NextRequest) {
if (req.nextUrl.pathname === '/') {
const redirectUrl = new URL('/documents', req.url);

View File

@@ -4,7 +4,7 @@ import { appRouter } from '@documenso/trpc/server/router';
export default trpcNext.createNextApiHandler({
router: appRouter,
createContext: ({ req, res }) => createTrpcContext({ req, res }),
createContext: async ({ req, res }) => createTrpcContext({ req, res }),
});
// export default async function handler(_req: NextApiRequest, res: NextApiResponse) {

View File

@@ -67,7 +67,7 @@ export function FeatureFlagProvider({
const interval = setInterval(() => {
if (document.hasFocus()) {
getAllFlags().then((newFlags) => setFlags(newFlags));
void getAllFlags().then((newFlags) => setFlags(newFlags));
}
}, FEATURE_FLAG_POLL_INTERVAL);
@@ -84,7 +84,7 @@ export function FeatureFlagProvider({
return;
}
const onFocus = () => getAllFlags().then((newFlags) => setFlags(newFlags));
const onFocus = () => void getAllFlags().then((newFlags) => setFlags(newFlags));
window.addEventListener('focus', onFocus);

View File

@@ -7,5 +7,6 @@ module.exports = {
content: [
...baseConfig.content,
`${path.join(require.resolve('@documenso/ui'), '..')}/**/*.{ts,tsx}`,
`${path.join(require.resolve('@documenso/email'), '..')}/**/*.{ts,tsx}`,
],
};

434
package-lock.json generated
View File

@@ -53,6 +53,7 @@
"react-hook-form": "^7.43.9",
"react-icons": "^4.8.0",
"recharts": "^2.7.2",
"sharp": "0.32.5",
"typescript": "5.1.6",
"zod": "^3.21.4"
},
@@ -77,6 +78,7 @@
"formidable": "^2.1.1",
"framer-motion": "^10.12.8",
"lucide-react": "^0.214.0",
"luxon": "^3.4.0",
"micro": "^10.0.1",
"nanoid": "^4.0.2",
"next": "13.4.12",
@@ -92,12 +94,14 @@
"react-hook-form": "^7.43.9",
"react-icons": "^4.8.0",
"react-rnd": "^10.4.1",
"sharp": "0.32.5",
"ts-pattern": "^5.0.5",
"typescript": "5.1.6",
"zod": "^3.21.4"
},
"devDependencies": {
"@types/formidable": "^2.0.6",
"@types/luxon": "^3.3.1",
"@types/node": "20.1.0",
"@types/react": "18.2.18",
"@types/react-dom": "18.2.7"
@@ -5213,6 +5217,11 @@
"dequal": "^2.0.3"
}
},
"node_modules/b4a": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz",
"integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw=="
},
"node_modules/bail": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@@ -5227,6 +5236,25 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/bcrypt": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz",
@@ -5256,6 +5284,16 @@
"node": ">=8"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/bplist-parser": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
@@ -5318,6 +5356,29 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -5994,6 +6055,18 @@
}
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -6010,6 +6083,15 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
@@ -6544,6 +6626,14 @@
"node": ">=8"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -6897,6 +6987,14 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/enhanced-resolve": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
@@ -7974,6 +8072,14 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"engines": {
"node": ">=6"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -8008,6 +8114,11 @@
"node": ">=6.0.0"
}
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
},
"node_modules/fast-glob": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
@@ -8258,6 +8369,11 @@
}
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"node_modules/fs-extra": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz",
@@ -8468,6 +8584,11 @@
"node": ">=10"
}
},
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
},
"node_modules/glob": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
@@ -8986,6 +9107,25 @@
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -11367,6 +11507,11 @@
"node": ">=10"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"node_modules/moo": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
@@ -11418,6 +11563,11 @@
"node": "^14 || ^16 || >=18"
}
},
"node_modules/napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -11618,6 +11768,17 @@
"tslib": "^2.0.3"
}
},
"node_modules/node-abi": {
"version": "3.47.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz",
"integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-addon-api": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
@@ -12446,6 +12607,111 @@
"preact": ">=10"
}
},
"node_modules/prebuild-install": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^1.0.1",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prebuild-install/node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"node_modules/prebuild-install/node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/prebuild-install/node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/prebuild-install/node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/prebuild-install/node_modules/tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/prebuild-install/node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -12660,6 +12926,15 @@
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
@@ -12701,6 +12976,11 @@
}
]
},
"node_modules/queue-tick": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
},
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
@@ -12743,6 +13023,28 @@
"node": ">= 0.8"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/rc/node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/re-resizable": {
"version": "6.9.6",
"resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.6.tgz",
@@ -13795,6 +14097,82 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"node_modules/sharp": {
"version": "0.32.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.5.tgz",
"integrity": "sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ==",
"hasInstallScript": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.2",
"node-addon-api": "^6.1.0",
"prebuild-install": "^7.1.1",
"semver": "^7.5.4",
"simple-get": "^4.0.1",
"tar-fs": "^3.0.4",
"tunnel-agent": "^0.6.0"
},
"engines": {
"node": ">=14.15.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/sharp/node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/sharp/node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/sharp/node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
},
"node_modules/sharp/node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -13849,8 +14227,7 @@
"type": "consulting",
"url": "https://feross.org/support"
}
],
"optional": true
]
},
"node_modules/simple-get": {
"version": "3.1.1",
@@ -13863,6 +14240,19 @@
"simple-concat": "^1.0.0"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/simple-swizzle/node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -14009,6 +14399,15 @@
"node": ">=10.0.0"
}
},
"node_modules/streamx": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz",
"integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==",
"dependencies": {
"fast-fifo": "^1.1.0",
"queue-tick": "^1.0.1"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -14472,6 +14871,26 @@
"node": ">=10"
}
},
"node_modules/tar-fs": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz",
"integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==",
"dependencies": {
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^3.1.5"
}
},
"node_modules/tar-stream": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz",
"integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==",
"dependencies": {
"b4a": "^1.6.4",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/text-extensions": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz",
@@ -14913,6 +15332,17 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/turbo": {
"version": "1.10.12",
"resolved": "https://registry.npmjs.org/turbo/-/turbo-1.10.12.tgz",

View File

@@ -25,7 +25,7 @@
"prettier": "^2.5.1",
"turbo": "^1.9.3"
},
"name": "documenso.next",
"name": "@documenso/root",
"packageManager": "npm@8.19.2",
"workspaces": [
"apps/*",

View File

@@ -0,0 +1,71 @@
import { Button, Img, Section, Tailwind, Text } from '@react-email/components';
import * as config from '@documenso/tailwind-config';
export interface TemplateDocumentCompletedProps {
downloadLink: string;
reviewLink: string;
documentName: string;
assetBaseUrl: string;
}
export const TemplateDocumentCompleted = ({
downloadLink,
reviewLink,
documentName,
assetBaseUrl,
}: TemplateDocumentCompletedProps) => {
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Tailwind
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<Section className="flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img className="h-42" src={getAssetUrl('/static/document.png')} alt="Documenso" />
</div>
<Text className="mb-4 flex items-center justify-center text-center text-base font-semibold text-[#7AC455]">
<Img src={getAssetUrl('/static/completed.png')} className="-mb-0.5 mr-2 inline h-7 w-7" />
Completed
</Text>
<Text className="text-primary mb-0 text-center text-lg font-semibold">
{documentName} was signed by all signers
</Text>
<Text className="my-1 text-center text-base text-slate-400">
Continue by downloading or reviewing the document.
</Text>
<Section className="mb-6 mt-8 text-center">
<Button
className="mr-4 inline-flex items-center justify-center rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
href={reviewLink}
>
<Img src={getAssetUrl('/static/review.png')} className="-mb-1 mr-2 inline h-5 w-5" />
Review
</Button>
<Button
className="inline-flex items-center justify-center rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
href={downloadLink}
>
<Img src={getAssetUrl('/static/download.png')} className="-mb-1 mr-2 inline h-5 w-5" />
Download
</Button>
</Section>
</Section>
</Tailwind>
);
};
export default TemplateDocumentCompleted;

View File

@@ -0,0 +1,59 @@
import { Button, Img, Section, Tailwind, Text } from '@react-email/components';
import * as config from '@documenso/tailwind-config';
export interface TemplateDocumentInviteProps {
inviterName: string;
inviterEmail: string;
documentName: string;
signDocumentLink: string;
assetBaseUrl: string;
}
export const TemplateDocumentInvite = ({
inviterName,
documentName,
signDocumentLink,
assetBaseUrl,
}: TemplateDocumentInviteProps) => {
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Tailwind
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<Section className="mt-4 flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img className="h-42" src={getAssetUrl('/static/document.png')} alt="Documenso" />
</div>
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
{inviterName} has invited you to sign "{documentName}"
</Text>
<Text className="my-1 text-center text-base text-slate-400">
Continue by signing the document.
</Text>
<Section className="mb-6 mt-8 text-center">
<Button
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
href={signDocumentLink}
>
Sign Document
</Button>
</Section>
</Section>
</Tailwind>
);
};
export default TemplateDocumentInvite;

View File

@@ -0,0 +1,52 @@
import { Img, Section, Tailwind, Text } from '@react-email/components';
import * as config from '@documenso/tailwind-config';
export interface TemplateDocumentPendingProps {
documentName: string;
assetBaseUrl: string;
}
export const TemplateDocumentPending = ({
documentName,
assetBaseUrl,
}: TemplateDocumentPendingProps) => {
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Tailwind
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<Section className="flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img className="h-42" src={getAssetUrl('/static/document.png')} alt="Documenso" />
</div>
<Text className="mb-4 flex items-center justify-center text-center text-base font-semibold text-blue-500">
<Img src={getAssetUrl('/static/clock.png')} className="-mb-0.5 mr-2 inline h-7 w-7" />
Waiting for others
</Text>
<Text className="text-primary mb-0 text-center text-lg font-semibold">
{documentName} has been signed
</Text>
<Text className="mx-auto mb-6 mt-1 max-w-[80%] text-center text-base text-slate-400">
We're still waiting for other signers to sign this document.
<br />
We'll notify you as soon as it's ready.
</Text>
</Section>
</Tailwind>
);
};
export default TemplateDocumentPending;

View File

@@ -0,0 +1,22 @@
import { Link, Section, Text } from '@react-email/components';
export const TemplateFooter = () => {
return (
<Section>
<Text className="my-4 text-base text-slate-400">
This document was sent using{' '}
<Link className="text-[#7AC455]" href="https://documenso.com">
Documenso.
</Link>
</Text>
<Text className="my-8 text-sm text-slate-400">
Documenso
<br />
2261 Market Street, #5211, San Francisco, CA 94114, USA
</Text>
</Section>
);
};
export default TemplateFooter;

View File

@@ -1,25 +1,23 @@
import {
Body,
Button,
Container,
Head,
Html,
Img,
Link,
Preview,
Section,
Tailwind,
Text,
} from '@react-email/components';
import config from '@documenso/tailwind-config';
interface DocumentCompletedEmailTemplateProps {
downloadLink?: string;
reviewLink?: string;
documentName?: string;
assetBaseUrl?: string;
}
import {
TemplateDocumentCompleted,
TemplateDocumentCompletedProps,
} from '../template-components/template-document-completed';
import TemplateFooter from '../template-components/template-footer';
export type DocumentCompletedEmailTemplateProps = Partial<TemplateDocumentCompletedProps>;
export const DocumentCompletedEmailTemplate = ({
downloadLink = 'https://documenso.com',
@@ -50,74 +48,23 @@ export const DocumentCompletedEmailTemplate = ({
<Section className="bg-white">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
<Img src={getAssetUrl('/static/logo.png')} alt="Documenso Logo" className="h-6" />
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
<Section className="mt-4 flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img
className="h-42"
src={getAssetUrl('/static/document.png')}
alt="Documenso"
/>
</div>
<Text className="mb-4 flex items-center justify-center text-center text-base font-semibold text-[#7AC455]">
<Img
src={getAssetUrl('/static/completed.png')}
className="-mb-0.5 mr-2 inline h-7 w-7"
/>
Completed
</Text>
<Text className="text-primary mb-0 text-center text-lg font-semibold">
{documentName} was signed by all signers
</Text>
<Text className="my-1 text-center text-base text-slate-400">
Continue by downloading or reviewing the document.
</Text>
<Section className="mb-6 mt-8 text-center">
<Button
className="mr-4 inline-flex items-center justify-center rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
href={reviewLink}
>
<Img
src={getAssetUrl('/static/review.png')}
className="-mb-1 mr-2 inline h-5 w-5"
/>
Review
</Button>
<Button
className="inline-flex items-center justify-center rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
href={downloadLink}
>
<Img
src={getAssetUrl('/static/download.png')}
className="-mb-1 mr-2 inline h-5 w-5"
/>
Download
</Button>
</Section>
</Section>
<TemplateDocumentCompleted
downloadLink={downloadLink}
reviewLink={reviewLink}
documentName={documentName}
assetBaseUrl={assetBaseUrl}
/>
</Section>
</Container>
<Container className="mx-auto max-w-xl">
<Section>
<Text className="my-4 text-base text-slate-400">
This document was sent using{' '}
<Link className="text-[#7AC455]" href="https://documenso.com">
Documenso.
</Link>
</Text>
<Text className="my-8 text-sm text-slate-400">
Documenso
<br />
2261 Market Street, #5211, San Francisco, CA 94114, USA
</Text>
</Section>
<TemplateFooter />
</Container>
</Section>
</Body>

View File

@@ -1,6 +1,5 @@
import {
Body,
Button,
Container,
Head,
Hr,
@@ -15,13 +14,13 @@ import {
import config from '@documenso/tailwind-config';
interface DocumentInviteEmailTemplateProps {
inviterName?: string;
inviterEmail?: string;
documentName?: string;
signDocumentLink?: string;
assetBaseUrl?: string;
}
import {
TemplateDocumentInvite,
TemplateDocumentInviteProps,
} from '../template-components/template-document-invite';
import TemplateFooter from '../template-components/template-footer';
export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInviteProps>;
export const DocumentInviteEmailTemplate = ({
inviterName = 'Lucas Smith',
@@ -51,36 +50,21 @@ export const DocumentInviteEmailTemplate = ({
>
<Body className="mx-auto my-auto bg-white font-sans">
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
<Img src={getAssetUrl('/static/logo.png')} alt="Documenso Logo" className="h-6" />
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
<Section className="mt-4 flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img
className="h-42"
src={getAssetUrl('/static/document.png')}
alt="Documenso"
/>
</div>
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
{inviterName} has invited you to sign "{documentName}"
</Text>
<Text className="my-1 text-center text-base text-slate-400">
Continue by signing the document.
</Text>
<Section className="mb-6 mt-8 text-center">
<Button
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
href={signDocumentLink}
>
Sign Document
</Button>
</Section>
</Section>
<TemplateDocumentInvite
inviterName={inviterName}
inviterEmail={inviterEmail}
documentName={documentName}
signDocumentLink={signDocumentLink}
assetBaseUrl={assetBaseUrl}
/>
</Section>
</Container>
@@ -102,20 +86,7 @@ export const DocumentInviteEmailTemplate = ({
<Hr className="mx-auto mt-12 max-w-xl" />
<Container className="mx-auto max-w-xl">
<Section>
<Text className="my-4 text-base text-slate-400">
This document was sent using{' '}
<Link className="text-[#7AC455]" href="https://documenso.com">
Documenso.
</Link>
</Text>
<Text className="my-8 text-sm text-slate-400">
Documenso
<br />
2261 Market Street, #5211, San Francisco, CA 94114, USA
</Text>
</Section>
<TemplateFooter />
</Container>
</Section>
</Body>

View File

@@ -4,19 +4,20 @@ import {
Head,
Html,
Img,
Link,
Preview,
Section,
Tailwind,
Text,
} from '@react-email/components';
import config from '@documenso/tailwind-config';
interface DocumentPendingEmailTemplateProps {
documentName?: string;
assetBaseUrl?: string;
}
import {
TemplateDocumentPending,
TemplateDocumentPendingProps,
} from '../template-components/template-document-pending';
import { TemplateFooter } from '../template-components/template-footer';
export type DocumentPendingEmailTemplateProps = Partial<TemplateDocumentPendingProps>;
export const DocumentPendingEmailTemplate = ({
documentName = 'Open Source Pledge.pdf',
@@ -43,55 +44,20 @@ export const DocumentPendingEmailTemplate = ({
>
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
<Img src={getAssetUrl('/static/logo.png')} alt="Documenso Logo" className="h-6" />
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
<Section>
<Img
src={getAssetUrl('/static/logo.png')}
alt="Documenso Logo"
className="mb-4 h-6"
/>
<Section className="mt-4 flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img
className="h-42"
src={getAssetUrl('/static/document.png')}
alt="Documenso"
/>
</div>
<Text className="mb-4 flex items-center justify-center text-center text-base font-semibold text-blue-500">
<Img
src={getAssetUrl('/static/clock.png')}
className="-mb-0.5 mr-2 inline h-7 w-7"
/>
Waiting for others
</Text>
<Text className="text-primary mb-0 text-center text-lg font-semibold">
{documentName} has been signed
</Text>
<Text className="mx-auto mb-6 mt-1 max-w-[80%] text-center text-base text-slate-400">
We're still waiting for other signers to sign this document.
<br />
We'll notify you as soon as it's ready.
</Text>
</Section>
<TemplateDocumentPending documentName={documentName} assetBaseUrl={assetBaseUrl} />
</Section>
</Container>
<Container className="mx-auto max-w-xl">
<Section>
<Text className="my-4 text-base text-slate-400">
This document was sent using{' '}
<Link className="text-[#7AC455]" href="https://documenso.com">
Documenso.
</Link>
</Text>
<Text className="my-8 text-sm text-slate-400">
Documenso
<br />
2261 Market Street, #5211, San Francisco, CA 94114, USA
</Text>
</Section>
<TemplateFooter />
</Container>
</Section>
</Body>

View File

@@ -110,9 +110,10 @@ export class MailChannelsTransport implements Transport<SentMessageInfo> {
});
}
res.json().then((data) => {
return callback(new Error(`MailChannels error: ${data.message}`), null);
});
res
.json()
.then((data) => callback(new Error(`MailChannels error: ${data.message}`), null))
.catch((err) => callback(err, null));
})
.catch((err) => {
return callback(err, null);

View File

@@ -19,6 +19,8 @@ module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['../../apps/*/tsconfig.json', '../../packages/*/tsconfig.json'],
ecmaVersion: 2022,
ecmaFeatures: {
jsx: true,
@@ -32,6 +34,18 @@ module.exports = {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// Safety with promises so we aren't running with scissors
'no-promise-executor-return': 'error',
'prefer-promise-reject-errors': 'error',
'require-atomic-updates': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': [
'error',
{ checksVoidReturn: { attributes: false } },
],
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/require-await': 'error',
// We never want to use `as` but are required to on occasion to handle
// shortcomings in third-party and generated types.
//

View File

@@ -0,0 +1,9 @@
{
"extends": "@documenso/tsconfig/base.json",
"compilerOptions": {
"allowJs": true,
"noEmit": true,
},
"include": ["**/*.cjs", "**/*.js"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@@ -7,6 +7,7 @@ import GoogleProvider, { GoogleProfile } from 'next-auth/providers/google';
import { prisma } from '@documenso/prisma';
import { getUserByEmail } from '../server-only/user/get-user-by-email';
import { ErrorCodes } from './error-codes';
export const NEXT_AUTH_OPTIONS: AuthOptions = {
adapter: PrismaAdapter(prisma),
@@ -23,21 +24,23 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
},
authorize: async (credentials, _req) => {
if (!credentials) {
return null;
throw new Error(ErrorCodes.CredentialsNotFound);
}
const { email, password } = credentials;
const user = await getUserByEmail({ email }).catch(() => null);
const user = await getUserByEmail({ email }).catch(() => {
throw new Error(ErrorCodes.IncorrectEmailPassword);
});
if (!user || !user.password) {
return null;
if (!user.password) {
throw new Error(ErrorCodes.UserMissingPassword);
}
const isPasswordsSame = compare(password, user.password);
const isPasswordsSame = await compare(password, user.password);
if (!isPasswordsSame) {
return null;
throw new Error(ErrorCodes.IncorrectEmailPassword);
}
return {
@@ -86,7 +89,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
};
},
async session({ token, session }) {
session({ token, session }) {
if (token && token.email) {
return {
...session,

View File

@@ -0,0 +1,5 @@
export const ErrorCodes = {
IncorrectEmailPassword: 'incorrect-email-password',
UserMissingPassword: 'missing-password',
CredentialsNotFound: 'credentials-not-found',
} as const;

View File

@@ -1,9 +1,6 @@
import { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';
import { headers } from 'next/headers';
import { NextRequest } from 'next/server';
import { getServerSession as getNextAuthServerSession } from 'next-auth';
import { getToken } from 'next-auth/jwt';
import { prisma } from '@documenso/prisma';
@@ -30,18 +27,6 @@ export const getServerSession = async ({ req, res }: GetServerSessionOptions) =>
return user;
};
export const getServerComponentToken = async () => {
const requestHeaders = Object.fromEntries(headers().entries());
const req = new NextRequest('http://example.com', {
headers: requestHeaders,
});
const token = await getToken({
req,
});
};
export const getServerComponentSession = async () => {
const session = await getNextAuthServerSession(NEXT_AUTH_OPTIONS);

View File

@@ -87,6 +87,6 @@ export const completeDocumentWithToken = async ({
if (documents.count > 0) {
console.log('sealing document');
sealDocument({ documentId: document.id });
await sealDocument({ documentId: document.id });
}
};

View File

@@ -1,13 +1,15 @@
import { match } from 'ts-pattern';
import { prisma } from '@documenso/prisma';
import { Document, DocumentStatus, Prisma } from '@documenso/prisma/client';
import { DocumentWithReciepient } from '@documenso/prisma/types/document-with-recipient';
import { Document, Prisma, SigningStatus } from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { FindResultSet } from '../../types/find-result-set';
export interface FindDocumentsOptions {
userId: number;
term?: string;
status?: DocumentStatus;
status?: ExtendedDocumentStatus;
page?: number;
perPage?: number;
orderBy?: {
@@ -19,29 +21,102 @@ export interface FindDocumentsOptions {
export const findDocuments = async ({
userId,
term,
status,
status = ExtendedDocumentStatus.ALL,
page = 1,
perPage = 10,
orderBy,
}: FindDocumentsOptions): Promise<FindResultSet<DocumentWithReciepient>> => {
}: FindDocumentsOptions) => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
},
});
const orderByColumn = orderBy?.column ?? 'created';
const orderByDirection = orderBy?.direction ?? 'desc';
const filters: Prisma.DocumentWhereInput = {
status,
userId,
};
const termFilters = !term
? undefined
: ({
title: {
contains: term,
mode: 'insensitive',
},
} as const);
if (term) {
filters.title = {
contains: term,
mode: 'insensitive',
};
}
const filters = match<ExtendedDocumentStatus, Prisma.DocumentWhereInput>(status)
.with(ExtendedDocumentStatus.ALL, () => ({
OR: [
{
userId,
},
{
status: {
not: ExtendedDocumentStatus.DRAFT,
},
Recipient: {
some: {
email: user.email,
},
},
},
],
}))
.with(ExtendedDocumentStatus.INBOX, () => ({
status: {
not: ExtendedDocumentStatus.DRAFT,
},
Recipient: {
some: {
email: user.email,
signingStatus: SigningStatus.NOT_SIGNED,
},
},
}))
.with(ExtendedDocumentStatus.DRAFT, () => ({
userId,
status: ExtendedDocumentStatus.DRAFT,
}))
.with(ExtendedDocumentStatus.PENDING, () => ({
OR: [
{
userId,
status: ExtendedDocumentStatus.PENDING,
},
{
status: ExtendedDocumentStatus.PENDING,
Recipient: {
some: {
email: user.email,
signingStatus: SigningStatus.SIGNED,
},
},
},
],
}))
.with(ExtendedDocumentStatus.COMPLETED, () => ({
OR: [
{
userId,
status: ExtendedDocumentStatus.COMPLETED,
},
{
status: ExtendedDocumentStatus.COMPLETED,
Recipient: {
some: {
email: user.email,
},
},
},
],
}))
.exhaustive();
const [data, count] = await Promise.all([
prisma.document.findMany({
where: {
...termFilters,
...filters,
},
skip: Math.max(page - 1, 0) * perPage,
@@ -50,21 +125,37 @@ export const findDocuments = async ({
[orderByColumn]: orderByDirection,
},
include: {
User: {
select: {
id: true,
name: true,
email: true,
},
},
Recipient: true,
},
}),
prisma.document.count({
where: {
...termFilters,
...filters,
},
}),
]);
const maskedData = data.map((doc) => ({
...doc,
Recipient: doc.Recipient.map((recipient) => ({
...recipient,
token: recipient.email === user.email ? recipient.token : '',
})),
}));
return {
data,
data: maskedData,
count,
currentPage: Math.max(page, 1),
perPage,
totalPages: Math.ceil(count / perPage),
};
} satisfies FindResultSet<typeof maskedData>;
};

View File

@@ -1,30 +1,88 @@
import { prisma } from '@documenso/prisma';
import { DocumentStatus } from '@documenso/prisma/client';
import { SigningStatus, User } from '@documenso/prisma/client';
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
export type GetStatsInput = {
userId: number;
user: User;
};
export const getStats = async ({ userId }: GetStatsInput) => {
const result = await prisma.document.groupBy({
by: ['status'],
_count: {
_all: true,
},
where: {
userId,
},
});
export const getStats = async ({ user }: GetStatsInput) => {
const [ownerCounts, notSignedCounts, hasSignedCounts] = await Promise.all([
prisma.document.groupBy({
by: ['status'],
_count: {
_all: true,
},
where: {
userId: user.id,
},
}),
prisma.document.groupBy({
by: ['status'],
_count: {
_all: true,
},
where: {
status: ExtendedDocumentStatus.PENDING,
Recipient: {
some: {
email: user.email,
signingStatus: SigningStatus.NOT_SIGNED,
},
},
},
}),
prisma.document.groupBy({
by: ['status'],
_count: {
_all: true,
},
where: {
status: {
not: ExtendedDocumentStatus.DRAFT,
},
Recipient: {
some: {
email: user.email,
signingStatus: SigningStatus.SIGNED,
},
},
},
}),
]);
const stats: Record<DocumentStatus, number> = {
[DocumentStatus.DRAFT]: 0,
[DocumentStatus.PENDING]: 0,
[DocumentStatus.COMPLETED]: 0,
const stats: Record<ExtendedDocumentStatus, number> = {
[ExtendedDocumentStatus.DRAFT]: 0,
[ExtendedDocumentStatus.PENDING]: 0,
[ExtendedDocumentStatus.COMPLETED]: 0,
[ExtendedDocumentStatus.INBOX]: 0,
[ExtendedDocumentStatus.ALL]: 0,
};
result.forEach((stat) => {
ownerCounts.forEach((stat) => {
stats[stat.status] = stat._count._all;
});
notSignedCounts.forEach((stat) => {
stats[ExtendedDocumentStatus.INBOX] += stat._count._all;
});
hasSignedCounts.forEach((stat) => {
if (stat.status === ExtendedDocumentStatus.COMPLETED) {
stats[ExtendedDocumentStatus.COMPLETED] += stat._count._all;
}
if (stat.status === ExtendedDocumentStatus.PENDING) {
stats[ExtendedDocumentStatus.PENDING] += stat._count._all;
}
});
Object.keys(stats).forEach((key) => {
if (key !== ExtendedDocumentStatus.ALL && isExtendedDocumentStatus(key)) {
stats[ExtendedDocumentStatus.ALL] += stats[key];
}
});
return stats;
};

View File

@@ -1,4 +1,4 @@
import { hash } from 'bcrypt';
import { compare, hash } from 'bcrypt';
import { prisma } from '@documenso/prisma';
@@ -11,7 +11,7 @@ export type UpdatePasswordOptions = {
export const updatePassword = async ({ userId, password }: UpdatePasswordOptions) => {
// Existence check
await prisma.user.findFirstOrThrow({
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
},
@@ -19,6 +19,13 @@ export const updatePassword = async ({ userId, password }: UpdatePasswordOptions
const hashedPassword = await hash(password, SALT_ROUNDS);
// Compare the new password with the old password
const isSamePassword = await compare(password, user.password as string);
if (isSamePassword) {
throw new Error('Your new password cannot be the same as your old password.');
}
const updatedUser = await prisma.user.update({
where: {
id: userId,

View File

@@ -1,5 +1,5 @@
export type FindResultSet<T> = {
data: T[];
data: T extends Array<any> ? T : T[];
count: number;
currentPage: number;
perPage: number;

View File

@@ -0,0 +1,11 @@
import { ExtendedDocumentStatus } from '../types/extended-document-status';
export const isExtendedDocumentStatus = (value: unknown): value is ExtendedDocumentStatus => {
if (typeof value !== 'string') {
return false;
}
// We're using the assertion for a type-guard so it's safe to ignore the eslint warning
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return Object.values(ExtendedDocumentStatus).includes(value as ExtendedDocumentStatus);
};

View File

@@ -1,5 +1,5 @@
import { Document, Recipient } from '@documenso/prisma/client';
export type DocumentWithReciepient = Document & {
export type DocumentWithRecipient = Document & {
Recipient: Recipient[];
};

View File

@@ -0,0 +1,12 @@
import { Document, Recipient } from '@documenso/prisma/client';
export type DocumentWithRecipientAndSender = Omit<Document, 'document'> & {
recipient: Recipient;
sender: {
id: number;
name: string | null;
email: string;
};
subject: string;
description: string;
};

View File

@@ -0,0 +1,10 @@
import { DocumentStatus } from '@prisma/client';
export const ExtendedDocumentStatus = {
...DocumentStatus,
INBOX: 'INBOX',
ALL: 'ALL',
} as const;
export type ExtendedDocumentStatus =
(typeof ExtendedDocumentStatus)[keyof typeof ExtendedDocumentStatus];

View File

@@ -115,6 +115,11 @@ module.exports = {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
screens: {
'3xl': '1920px',
'4xl': '2560px',
'5xl': '3840px',
},
},
},
plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],

View File

@@ -0,0 +1,9 @@
{
"extends": "@documenso/tsconfig/base.json",
"compilerOptions": {
"allowJs": true,
"noEmit": true,
},
"include": ["**/*.cjs", "**/*.js"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@@ -40,12 +40,16 @@ export const profileRouter = router({
password,
});
} catch (err) {
console.error(err);
let message =
'We were unable to update your profile. Please review the information you provided and try again.';
if (err instanceof Error) {
message = err.message;
}
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'We were unable to update your profile. Please review the information you provided and try again.',
message,
});
}
}),

View File

@@ -10,7 +10,7 @@ const t = initTRPC.context<TrpcContext>().create({
/**
* Middlewares
*/
export const authenticatedMiddleware = t.middleware(({ ctx, next }) => {
export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
if (!ctx.session) {
throw new TRPCError({
code: 'UNAUTHORIZED',
@@ -18,7 +18,7 @@ export const authenticatedMiddleware = t.middleware(({ ctx, next }) => {
});
}
return next({
return await next({
ctx: {
...ctx,

View File

@@ -0,0 +1,8 @@
{
"extends": "./base.json",
"compilerOptions": {
"noEmit": true,
},
"include": ["**/*.ts", "**/*.tsx", "**/*.d.ts", "**/*.json"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@@ -0,0 +1,28 @@
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
export const SignatureIcon: LucideIcon = ({
size = 24,
color = 'currentColor',
strokeWidth = 1.33,
absoluteStrokeWidth,
...props
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M1.5 11H14.5M1.5 14C1.5 14 8.72 2 4.86938 2H4.875C2.01 2 1.97437 14.0694 8 6.51188V6.5C8 6.5 9 11.3631 11.5 7.52375V7.5C11.5 7.5 11.5 9 14.5 9"
stroke={color}
strokeWidth={absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};

View File

@@ -86,7 +86,7 @@ export const DocumentDropzone = ({ className, onDrop, ...props }: DocumentDropzo
multiple: false,
onDrop: ([acceptedFile]) => {
if (acceptedFile && onDrop) {
onDrop(acceptedFile);
void onDrop(acceptedFile);
}
},
});

View File

@@ -88,6 +88,8 @@ export const AddFieldsFormPartial = ({
},
});
const onFormSubmit = handleSubmit(onSubmit);
const {
append,
remove,
@@ -500,7 +502,7 @@ export const AddFieldsFormPartial = ({
loading={isSubmitting}
disabled={isSubmitting}
onGoBackClick={documentFlow.onBackStep}
onGoNextClick={() => handleSubmit(onSubmit)()}
onGoNextClick={() => void onFormSubmit()}
/>
</DocumentFlowFormContainerFooter>
</>

View File

@@ -68,6 +68,8 @@ export const AddSignersFormPartial = ({
},
});
const onFormSubmit = handleSubmit(onSubmit);
const {
append: appendSigner,
fields: signers,
@@ -214,7 +216,7 @@ export const AddSignersFormPartial = ({
loading={isSubmitting}
disabled={isSubmitting}
onGoBackClick={documentFlow.onBackStep}
onGoNextClick={() => handleSubmit(onSubmit)()}
onGoNextClick={() => void onFormSubmit()}
/>
</DocumentFlowFormContainerFooter>
</>

View File

@@ -47,6 +47,8 @@ export const AddSubjectFormPartial = ({
},
});
const onFormSubmit = handleSubmit(onSubmit);
return (
<>
<DocumentFlowFormContainerContent>
@@ -130,7 +132,7 @@ export const AddSubjectFormPartial = ({
disabled={isSubmitting}
goNextLabel={document.status === DocumentStatus.DRAFT ? 'Send' : 'Update'}
onGoBackClick={documentFlow.onBackStep}
onGoNextClick={() => handleSubmit(onSubmit)()}
onGoNextClick={() => void onFormSubmit()}
/>
</DocumentFlowFormContainerFooter>
</>

View File

@@ -66,7 +66,7 @@ export const PDFViewer = ({ className, document, onPageClick, ...props }: PDFVie
const pageY = event.clientY - top;
if (onPageClick) {
onPageClick({
void onPageClick({
pageNumber,
numPages,
originalEvent: event,

View File

@@ -302,8 +302,8 @@ export class Canvas {
/**
* Retrieves the signature as an image blob.
*/
public toBlob(type?: string, quality?: number): Promise<Blob> {
return new Promise((resolve, reject) => {
public async toBlob(type?: string, quality?: number): Promise<Blob> {
const promise = new Promise<Blob>((resolve, reject) => {
this.$canvas.toBlob(
(blob) => {
if (!blob) {
@@ -317,5 +317,7 @@ export class Canvas {
quality,
);
});
return await promise;
}
}