Compare commits
63 Commits
blog/upcom
...
feat/compl
| Author | SHA1 | Date | |
|---|---|---|---|
| 68d624ac78 | |||
| 1e5ecd79c2 | |||
| 58f10268e2 | |||
| 43ce76d928 | |||
| 201ba65e1c | |||
| 0d130b17c8 | |||
| 2c90f767fd | |||
| 946b2fe129 | |||
| 2a2dbb65c6 | |||
| ef2300a600 | |||
| eea99ac871 | |||
| f4c7799537 | |||
| 78325d9b39 | |||
| b3f26055d9 | |||
| 1b276a0469 | |||
| 17fbf2673c | |||
| cdc50ec876 | |||
| 181af24b78 | |||
| 4b13a42731 | |||
| e330e90688 | |||
| f2d3c51651 | |||
| c9c111cdf2 | |||
| c247295131 | |||
| d417255910 | |||
| 677a15327b | |||
| d5238939ad | |||
| 6860726e83 | |||
| a55197fb2d | |||
| bcb163693a | |||
| 7771d7acbe | |||
| 4cd56fa98c | |||
| a1ce6f696a | |||
| 776324c875 | |||
| 6f4c280583 | |||
| 2bad1b9f55 | |||
| 73b0dc315e | |||
| 525ff21563 | |||
| 863e53a2d5 | |||
| da2033692c | |||
| dbbe17a0a8 | |||
| 0c145fab0b | |||
| f6e49e3f21 | |||
| 1f027d75d3 | |||
| 1ba7767f8e | |||
| 8220b2f086 | |||
| a74374e39f | |||
| 551918ab9b | |||
| 739f3763dd | |||
| 0cdd980a4b | |||
| b43ddcbea2 | |||
| 75feaefbf2 | |||
| d524ea77ab | |||
| 2524458b0c | |||
| 12c45fb882 | |||
| 118483b6cc | |||
| fd6350b397 | |||
| 8d4df7d3dd | |||
| f1f6e2e40a | |||
| 66dfdc5ad0 | |||
| b45978374b | |||
| 81c3e701e2 | |||
| 874d919a6a | |||
| e8559cecd5 |
@ -1,101 +0,0 @@
|
||||
---
|
||||
title: A week of announcements
|
||||
description: An overview of what's new at Documenso.
|
||||
authorName: 'Flo Merian'
|
||||
authorImage: '/blog/blog-author-flo.jpeg'
|
||||
authorRole: 'Go-to-market'
|
||||
date: 2023-09-20
|
||||
tags:
|
||||
- Announcement
|
||||
- Community
|
||||
---
|
||||
|
||||
We just spent the week announcing something new every day. This blog post gives an overview of what's new at Documenso.
|
||||
|
||||
## A week of announcements
|
||||
|
||||
### Day 1: Documenso Design System
|
||||
|
||||
<figure>
|
||||
<MdxNextImage
|
||||
src="/blog/blog-fig-launch-week-documenso-design-system.webp"
|
||||
width="2000"
|
||||
height="1126"
|
||||
alt="Documenso Design System"
|
||||
/>
|
||||
|
||||
<figcaption className="text-center">Documenso Design System</figcaption>
|
||||
</figure>
|
||||
|
||||
We open-sourced Documenso's design system.
|
||||
|
||||
Documenso isn't just an open-source alternative to DocuSign. It's a beautiful document signing experience.
|
||||
|
||||
We welcome every developer AND designer to contribute to building the product.
|
||||
|
||||
[This Figma file]() serves as an open source guide for the design of the product across website, desktop & mobile apps, and brand.
|
||||
|
||||
It includes design tokens, primitives and components, screens, and brand assets.
|
||||
|
||||
Go duplicate and remix it!
|
||||
|
||||
### Day 2: Malfunction Mania
|
||||
|
||||
Documenso 1.0 just hit the testing environment.
|
||||
|
||||
We want to ensure the best possible release. That's why we started this week a public testing phase, the most significant community project yet, where we invite you to try out the new version, report and fix bugs and give feedback before release.
|
||||
|
||||
We call it "Malfunction Mania."
|
||||
|
||||
Early contributors love it so far:
|
||||
|
||||
"I love the refresh. The UI is much cleaner and the possibility to select the signature spot, date, name, email, and change the size, is a life-saver..."
|
||||
|
||||
Documenso's co-founder and CEO Timur shared the details in [this announcement]().
|
||||
|
||||
### Day 3: Documenso Shop
|
||||
|
||||
Supporting and contributing to open-source projects like Documenso is cool for sure.
|
||||
|
||||
Do you know what's *even* cooler tho? To wear swag from open-source projects.
|
||||
|
||||
We announced the [Documenso Shop](https://documen.so/shop), a place where you can buy some merch. Suit up!
|
||||
|
||||
### Day 4: Early Adopter Plan
|
||||
|
||||
<figure>
|
||||
<MdxNextImage
|
||||
src="/blog/blog-fig-launch-week-early-adopter-plan.webp"
|
||||
width="2000"
|
||||
height="1126"
|
||||
alt="Early Adopter Plan"
|
||||
/>
|
||||
|
||||
<figcaption className="text-center">The Early Adopter Plan. $30/month, forever. Available for the first 100 new signups.</figcaption>
|
||||
</figure>
|
||||
|
||||
As we ramp up development speed, we introduced the [Early Adopter Plan](https://documen.so/pricing), a special, fixed $30/mo offer for 100 early adopters who want to get deep hands-on feedback.
|
||||
|
||||
Timur published a blog post [here]() to explain how we plan to build the core version of [documenso.com](http://documenso.com/).
|
||||
|
||||
### Day 5: Upcoming Launches
|
||||
|
||||
<figure>
|
||||
<MdxNextImage
|
||||
src="/blog/blog-fig-launch-week-upcoming-launches.webp"
|
||||
width="2000"
|
||||
height="1126"
|
||||
alt="Documenso Upcoming Launches"
|
||||
/>
|
||||
|
||||
</figure>
|
||||
|
||||
Say hello! hey! yo! to the public roadmap.
|
||||
|
||||
Go to the repository to find it and preview what's next at Documenso, the features we're working on and upcoming launches.
|
||||
|
||||
Make sure to [star the repo on GitHub](https://documen.so/github) and watch it to get the latest updates.
|
||||
|
||||
## Wrapping up
|
||||
|
||||
So this week was Documenso's first Launch Week. We hope you enjoyed it as much as we did.
|
||||
@ -8,6 +8,7 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"clean": "rimraf .next && rimraf node_modules",
|
||||
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -18,9 +19,9 @@
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"contentlayer": "^0.3.4",
|
||||
"framer-motion": "^10.12.8",
|
||||
"lucide-react": "^0.214.0",
|
||||
"lucide-react": "^0.279.0",
|
||||
"micro": "^10.0.1",
|
||||
"next": "13.4.12",
|
||||
"next": "13.4.19",
|
||||
"next-auth": "4.22.3",
|
||||
"next-contentlayer": "^0.3.4",
|
||||
"next-plausible": "^3.10.1",
|
||||
|
||||
|
Before Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 15 KiB |
65
apps/marketing/src/app/not-found.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
import backgroundPattern from '~/assets/background-pattern.png';
|
||||
|
||||
export default function NotFound() {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className={cn('relative max-w-[100vw] overflow-hidden')}>
|
||||
<div className="absolute -inset-24 -z-10">
|
||||
<motion.div
|
||||
className="flex h-full w-full items-center justify-center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.8, transition: { duration: 0.5, delay: 0.5 } }}
|
||||
>
|
||||
<Image
|
||||
src={backgroundPattern}
|
||||
alt="background pattern"
|
||||
className="-mr-[50vw] -mt-[15vh] h-full scale-100 object-cover md:scale-100 lg:scale-[100%]"
|
||||
priority
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto flex h-full min-h-screen items-center px-6 py-32">
|
||||
<div>
|
||||
<p className="text-muted-foreground font-semibold">404 Page not found</p>
|
||||
|
||||
<h1 className="mt-3 text-2xl font-bold md:text-3xl">Oops! Something went wrong.</h1>
|
||||
|
||||
<p className="text-muted-foreground mt-4 text-sm">
|
||||
The page you are looking for was moved, removed, renamed or might never have existed.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex gap-x-2.5 gap-y-4 md:items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-32"
|
||||
onClick={() => {
|
||||
void router.back();
|
||||
}}
|
||||
>
|
||||
<ChevronLeft className="mr-2 h-4 w-4" />
|
||||
Go Back
|
||||
</Button>
|
||||
|
||||
<Button className="w-32" asChild>
|
||||
<Link href="/">Home</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -4,11 +4,11 @@ import { getBaseUrl } from '@documenso/lib/universal/get-base-url';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: '*',
|
||||
allow: '/*',
|
||||
disallow: ['/_next/*'],
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
userAgent: '*',
|
||||
},
|
||||
],
|
||||
sitemap: `${getBaseUrl()}/sitemap.xml`,
|
||||
};
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"clean": "rimraf .next && rimraf node_modules",
|
||||
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -21,10 +22,10 @@
|
||||
"@tanstack/react-query": "^4.29.5",
|
||||
"formidable": "^2.1.1",
|
||||
"framer-motion": "^10.12.8",
|
||||
"lucide-react": "^0.214.0",
|
||||
"lucide-react": "^0.279.0",
|
||||
"luxon": "^3.4.0",
|
||||
"micro": "^10.0.1",
|
||||
"next": "13.4.12",
|
||||
"next": "13.4.19",
|
||||
"next-auth": "4.22.3",
|
||||
"next-plausible": "^3.10.1",
|
||||
"next-themes": "^0.2.1",
|
||||
|
||||
BIN
apps/web/public/fonts/caveat-regular.ttf
Normal file
BIN
apps/web/public/fonts/inter-bold.ttf
Normal file
BIN
apps/web/public/fonts/inter-regular.ttf
Normal file
BIN
apps/web/public/fonts/inter-semibold.ttf
Normal file
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
BIN
apps/web/public/static/og-share-frame.png
Normal file
|
After Width: | Height: | Size: 743 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
Before Width: | Height: | Size: 629 B |
@ -7,7 +7,11 @@ import { useSession } from 'next-auth/react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { Document, DocumentStatus, Recipient, SigningStatus, User } from '@documenso/prisma/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard';
|
||||
|
||||
export type DataTableActionButtonProps = {
|
||||
row: Document & {
|
||||
@ -18,11 +22,16 @@ export type DataTableActionButtonProps = {
|
||||
|
||||
export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => {
|
||||
const { data: session } = useSession();
|
||||
const { toast } = useToast();
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { mutateAsync: createOrGetShareLink, isLoading: isCreatingShareLink } =
|
||||
trpc.shareLink.createOrGetShareLink.useMutation();
|
||||
|
||||
const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email);
|
||||
|
||||
const isOwner = row.User.id === session.user.id;
|
||||
@ -32,6 +41,20 @@ export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => {
|
||||
const isComplete = row.status === DocumentStatus.COMPLETED;
|
||||
const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
|
||||
|
||||
const onShareClick = async () => {
|
||||
const { slug } = await createOrGetShareLink({
|
||||
token: recipient?.token,
|
||||
documentId: row.id,
|
||||
});
|
||||
|
||||
await copyToClipboard(`${window.location.origin}/share/${slug}`).catch(() => null);
|
||||
|
||||
toast({
|
||||
title: 'Copied to clipboard',
|
||||
description: 'The sharing link has been copied to your clipboard.',
|
||||
});
|
||||
};
|
||||
|
||||
return match({
|
||||
isOwner,
|
||||
isRecipient,
|
||||
@ -57,8 +80,8 @@ export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => {
|
||||
</Button>
|
||||
))
|
||||
.otherwise(() => (
|
||||
<Button className="w-24" disabled>
|
||||
<Share className="-ml-1 mr-2 h-4 w-4" />
|
||||
<Button className="w-24" loading={isCreatingShareLink} onClick={onShareClick}>
|
||||
{!isCreatingShareLink && <Share className="-ml-1 mr-2 h-4 w-4" />}
|
||||
Share
|
||||
</Button>
|
||||
));
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
Download,
|
||||
Edit,
|
||||
History,
|
||||
Loader,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
Share,
|
||||
@ -18,7 +19,8 @@ import { useSession } from 'next-auth/react';
|
||||
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
||||
import { Document, DocumentStatus, Recipient, User } from '@documenso/prisma/client';
|
||||
import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||
import { trpc } from '@documenso/trpc/client';
|
||||
import { trpc as trpcClient } from '@documenso/trpc/client';
|
||||
import { trpc as trpcReact } from '@documenso/trpc/react';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@ -26,6 +28,9 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
} from '@documenso/ui/primitives/dropdown-menu';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard';
|
||||
|
||||
export type DataTableActionDropdownProps = {
|
||||
row: Document & {
|
||||
@ -36,11 +41,16 @@ export type DataTableActionDropdownProps = {
|
||||
|
||||
export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) => {
|
||||
const { data: session } = useSession();
|
||||
const { toast } = useToast();
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { mutateAsync: createOrGetShareLink, isLoading: isCreatingShareLink } =
|
||||
trpcReact.shareLink.createOrGetShareLink.useMutation();
|
||||
|
||||
const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email);
|
||||
|
||||
const isOwner = row.User.id === session.user.id;
|
||||
@ -50,15 +60,29 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
|
||||
const isComplete = row.status === DocumentStatus.COMPLETED;
|
||||
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
|
||||
|
||||
const onShareClick = async () => {
|
||||
const { slug } = await createOrGetShareLink({
|
||||
token: recipient?.token,
|
||||
documentId: row.id,
|
||||
});
|
||||
|
||||
await copyToClipboard(`${window.location.origin}/share/${slug}`).catch(() => null);
|
||||
|
||||
toast({
|
||||
title: 'Copied to clipboard',
|
||||
description: 'The sharing link has been copied to your clipboard.',
|
||||
});
|
||||
};
|
||||
|
||||
const onDownloadClick = async () => {
|
||||
let document: DocumentWithData | null = null;
|
||||
|
||||
if (!recipient) {
|
||||
document = await trpc.document.getDocumentById.query({
|
||||
document = await trpcClient.document.getDocumentById.query({
|
||||
id: row.id,
|
||||
});
|
||||
} else {
|
||||
document = await trpc.document.getDocumentByToken.query({
|
||||
document = await trpcClient.document.getDocumentByToken.query({
|
||||
token: recipient.token,
|
||||
});
|
||||
}
|
||||
@ -135,8 +159,12 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
|
||||
Resend
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem disabled>
|
||||
<Share className="mr-2 h-4 w-4" />
|
||||
<DropdownMenuItem onClick={onShareClick}>
|
||||
{isCreatingShareLink ? (
|
||||
<Loader className="mr-2 h-4 w-4" />
|
||||
) : (
|
||||
<Share className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
Share
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
50
apps/web/src/app/(dashboard)/documents/empty-state.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { Bird, CheckCircle2 } from 'lucide-react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
export type EmptyDocumentProps = { status: ExtendedDocumentStatus };
|
||||
|
||||
export const EmptyDocumentState = ({ status }: EmptyDocumentProps) => {
|
||||
const {
|
||||
title,
|
||||
message,
|
||||
icon: Icon,
|
||||
} = match(status)
|
||||
.with(ExtendedDocumentStatus.COMPLETED, () => ({
|
||||
title: 'Nothing to do',
|
||||
message:
|
||||
'There are no completed documents yet. Documents that you have created or received that become completed will appear here later.',
|
||||
icon: CheckCircle2,
|
||||
}))
|
||||
.with(ExtendedDocumentStatus.DRAFT, () => ({
|
||||
title: 'No active drafts',
|
||||
message:
|
||||
'There are no active drafts at then current moment. You can upload a document to start drafting.',
|
||||
icon: CheckCircle2,
|
||||
}))
|
||||
.with(ExtendedDocumentStatus.ALL, () => ({
|
||||
title: "We're all empty",
|
||||
message:
|
||||
'You have not yet created or received any documents. To create a document please upload one.',
|
||||
icon: Bird,
|
||||
}))
|
||||
.otherwise(() => ({
|
||||
title: 'Nothing to do',
|
||||
message:
|
||||
'All documents are currently actioned. Any new documents are sent or recieved they will start to appear here.',
|
||||
icon: CheckCircle2,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="text-muted-foreground/60 flex h-60 flex-col items-center justify-center gap-y-4">
|
||||
<Icon className="h-12 w-12" strokeWidth={1.5} />
|
||||
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
|
||||
<p className="mt-2 max-w-[60ch]">{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -12,6 +12,7 @@ import { PeriodSelectorValue } from '~/components/(dashboard)/period-selector/ty
|
||||
import { DocumentStatus } from '~/components/formatter/document-status';
|
||||
|
||||
import { DocumentsDataTable } from './data-table';
|
||||
import { EmptyDocumentState } from './empty-state';
|
||||
import { UploadDocument } from './upload-document';
|
||||
|
||||
export type DocumentsPageProps = {
|
||||
@ -96,7 +97,8 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<DocumentsDataTable results={results} />
|
||||
{results.count > 0 && <DocumentsDataTable results={results} />}
|
||||
{results.count === 0 && <EmptyDocumentState status={status} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
153
apps/web/src/app/(share)/share/[slug]/opengraph-image.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import { ImageResponse } from 'next/server';
|
||||
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import { getRecipientOrSenderByShareLinkSlug } from '@documenso/lib/server-only/share/get-recipient-or-sender-by-share-link-slug';
|
||||
|
||||
import { Logo } from '~/components/branding/logo';
|
||||
import { getAssetBuffer } from '~/helpers/get-asset-buffer';
|
||||
|
||||
const CARD_OFFSET_TOP = 152;
|
||||
const CARD_OFFSET_LEFT = 350;
|
||||
const CARD_WIDTH = 500;
|
||||
const CARD_HEIGHT = 250;
|
||||
|
||||
const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
type SharePageOpenGraphImageProps = {
|
||||
params: { slug: string };
|
||||
};
|
||||
|
||||
export default async function Image({ params: { slug } }: SharePageOpenGraphImageProps) {
|
||||
const [interSemiBold, interRegular, caveatRegular, shareFrameImage] = await Promise.all([
|
||||
getAssetBuffer('/fonts/inter-semibold.ttf'),
|
||||
getAssetBuffer('/fonts/inter-regular.ttf'),
|
||||
getAssetBuffer('/fonts/caveat-regular.ttf'),
|
||||
getAssetBuffer('/static/og-share-frame.png'),
|
||||
]);
|
||||
|
||||
const recipientOrSender = await getRecipientOrSenderByShareLinkSlug({ slug }).catch(() => null);
|
||||
|
||||
if (!recipientOrSender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isRecipient = 'Signature' in recipientOrSender;
|
||||
|
||||
const signatureImage = match(recipientOrSender)
|
||||
.with({ Signature: P.array(P._) }, (recipient) => {
|
||||
return recipient.Signature?.[0]?.signatureImageAsBase64 || null;
|
||||
})
|
||||
.otherwise((sender) => {
|
||||
return sender.signature || null;
|
||||
});
|
||||
|
||||
const signatureName = match(recipientOrSender)
|
||||
.with({ Signature: P.array(P._) }, (recipient) => {
|
||||
return recipient.name || recipient.email;
|
||||
})
|
||||
.otherwise((sender) => {
|
||||
return sender.name || sender.email;
|
||||
});
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div tw="relative flex h-full w-full">
|
||||
{/* @ts-expect-error Lack of typing from ImageResponse */}
|
||||
<img src={shareFrameImage} alt="og-share-frame" tw="absolute inset-0 w-full h-full" />
|
||||
|
||||
<div tw="absolute top-20 flex w-full items-center justify-center">
|
||||
{/* @ts-expect-error Lack of typing from ImageResponse */}
|
||||
<Logo tw="h-8 w-60" />
|
||||
</div>
|
||||
|
||||
{signatureImage ? (
|
||||
<div
|
||||
tw="absolute py-6 px-12 flex items-center justify-center text-center"
|
||||
style={{
|
||||
top: `${CARD_OFFSET_TOP}px`,
|
||||
left: `${CARD_OFFSET_LEFT}px`,
|
||||
width: `${CARD_WIDTH}px`,
|
||||
height: `${CARD_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
<img src={signatureImage} alt="signature" tw="opacity-60 h-full max-w-[100%]" />
|
||||
</div>
|
||||
) : (
|
||||
<p
|
||||
tw="absolute py-6 px-12 -mt-2 flex items-center justify-center text-center text-slate-500"
|
||||
style={{
|
||||
fontFamily: 'Caveat',
|
||||
fontSize: `${Math.max(
|
||||
Math.min((CARD_WIDTH * 1.5) / signatureName.length, 80),
|
||||
36,
|
||||
)}px`,
|
||||
top: `${CARD_OFFSET_TOP}px`,
|
||||
left: `${CARD_OFFSET_LEFT}px`,
|
||||
width: `${CARD_WIDTH}px`,
|
||||
height: `${CARD_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
{signatureName}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* <div
|
||||
tw="absolute flex items-center justify-center text-slate-500"
|
||||
style={{
|
||||
top: `${CARD_OFFSET_TOP + CARD_HEIGHT - 45}px`,
|
||||
left: `${CARD_OFFSET_LEFT}`,
|
||||
width: `${CARD_WIDTH}px`,
|
||||
fontSize: '30px',
|
||||
}}
|
||||
>
|
||||
{signatureName}
|
||||
</div> */}
|
||||
|
||||
<div
|
||||
tw="absolute flex flex-col items-center justify-center pt-12 w-full"
|
||||
style={{
|
||||
top: `${CARD_OFFSET_TOP + CARD_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
tw="text-3xl text-slate-500"
|
||||
style={{
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{isRecipient
|
||||
? 'I just signed with Documenso and you can too!'
|
||||
: 'I just sent a document with Documenso and you can too!'}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Caveat',
|
||||
data: caveatRegular,
|
||||
style: 'italic',
|
||||
},
|
||||
{
|
||||
name: 'Inter',
|
||||
data: interRegular,
|
||||
style: 'normal',
|
||||
weight: 400,
|
||||
},
|
||||
{
|
||||
name: 'Inter',
|
||||
data: interSemiBold,
|
||||
style: 'normal',
|
||||
weight: 600,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
11
apps/web/src/app/(share)/share/[slug]/page.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { Metadata } from 'next';
|
||||
|
||||
import { Redirect } from './redirect';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Documenso - Share',
|
||||
};
|
||||
|
||||
export default function SharePage() {
|
||||
return <Redirect />;
|
||||
}
|
||||
11
apps/web/src/app/(share)/share/[slug]/redirect.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Redirect = () => {
|
||||
useEffect(() => {
|
||||
window.location.href = process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001';
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -1,16 +1,16 @@
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { CheckCircle2, Clock8, Share } from 'lucide-react';
|
||||
import { CheckCircle2, Clock8 } from 'lucide-react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||
import { DocumentStatus, FieldType } from '@documenso/prisma/client';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
import { DownloadButton } from './download-button';
|
||||
import { ShareButton } from './share-button';
|
||||
import { SigningCard } from './signing-card';
|
||||
|
||||
export type CompletedSigningPageProps = {
|
||||
@ -88,11 +88,7 @@ export default async function CompletedSigningPage({
|
||||
))}
|
||||
|
||||
<div className="mt-8 flex w-full max-w-sm items-center justify-center gap-4">
|
||||
{/* TODO: Hook this up */}
|
||||
<Button variant="outline" className="flex-1">
|
||||
<Share className="mr-2 h-5 w-5" />
|
||||
Share
|
||||
</Button>
|
||||
<ShareButton documentId={document.id} token={recipient.token} />
|
||||
|
||||
<DownloadButton
|
||||
className="flex-1"
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
'use client';
|
||||
|
||||
import { HTMLAttributes } from 'react';
|
||||
|
||||
import { Share } from 'lucide-react';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard';
|
||||
|
||||
export type ShareButtonProps = HTMLAttributes<HTMLButtonElement> & {
|
||||
token: string;
|
||||
documentId: number;
|
||||
};
|
||||
|
||||
export const ShareButton = ({ token, documentId }: ShareButtonProps) => {
|
||||
const { toast } = useToast();
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
const { mutateAsync: createOrGetShareLink, isLoading } =
|
||||
trpc.shareLink.createOrGetShareLink.useMutation();
|
||||
|
||||
const onShareClick = async () => {
|
||||
const { slug } = await createOrGetShareLink({
|
||||
token: token,
|
||||
documentId,
|
||||
});
|
||||
|
||||
await copyToClipboard(`${window.location.origin}/share/${slug}`).catch(() => null);
|
||||
|
||||
toast({
|
||||
title: 'Copied to clipboard',
|
||||
description: 'The sharing link has been copied to your clipboard.',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
disabled={!token || !documentId}
|
||||
loading={isLoading}
|
||||
onClick={onShareClick}
|
||||
>
|
||||
{!isLoading && <Share className="mr-2 h-5 w-5" />}
|
||||
Share
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@ -38,7 +38,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
||||
}).catch(() => null),
|
||||
getFieldsForToken({ token }),
|
||||
getRecipientByToken({ token }).catch(() => null),
|
||||
viewedDocument({ token }),
|
||||
viewedDocument({ token }).catch(() => null),
|
||||
]);
|
||||
|
||||
if (!document || !document.documentData || !recipient) {
|
||||
|
||||
26
apps/web/src/app/not-found.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
import NotFoundPartial from '~/components/partials/not-found';
|
||||
|
||||
export default async function NotFound() {
|
||||
const session = await getServerComponentSession();
|
||||
|
||||
return (
|
||||
<NotFoundPartial>
|
||||
{session && (
|
||||
<Button className="w-32" asChild>
|
||||
<Link href="/documents">Documents</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!session && (
|
||||
<Button className="w-32" asChild>
|
||||
<Link href="/signin">Sign In</Link>
|
||||
</Button>
|
||||
)}
|
||||
</NotFoundPartial>
|
||||
);
|
||||
}
|
||||
BIN
apps/web/src/assets/background-pattern-og.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
apps/web/src/assets/caveat-regular.ttf
Normal file
BIN
apps/web/src/assets/inter-bold.ttf
Normal file
BIN
apps/web/src/assets/inter-regular.ttf
Normal file
BIN
apps/web/src/assets/inter-semibold.ttf
Normal file
BIN
apps/web/src/assets/og-share-frame.png
Normal file
|
After Width: | Height: | Size: 743 KiB |
@ -10,6 +10,7 @@ import {
|
||||
User as LucideUser,
|
||||
Monitor,
|
||||
Moon,
|
||||
Palette,
|
||||
Sun,
|
||||
UserCog,
|
||||
} from 'lucide-react';
|
||||
@ -26,7 +27,13 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from '@documenso/ui/primitives/dropdown-menu';
|
||||
|
||||
@ -37,8 +44,8 @@ export type ProfileDropdownProps = {
|
||||
};
|
||||
|
||||
export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const { getFlag } = useFeatureFlags();
|
||||
const { theme, setTheme } = useTheme();
|
||||
const isUserAdmin = isAdmin(user);
|
||||
|
||||
const isBillingEnabled = getFlag('app_billing');
|
||||
@ -98,28 +105,30 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{theme === 'light' ? null : (
|
||||
<DropdownMenuItem onClick={() => setTheme('light')}>
|
||||
<Sun className="mr-2 h-4 w-4" />
|
||||
Light Mode
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{theme === 'dark' ? null : (
|
||||
<DropdownMenuItem onClick={() => setTheme('dark')}>
|
||||
<Moon className="mr-2 h-4 w-4" />
|
||||
Dark Mode
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{theme === 'system' ? null : (
|
||||
<DropdownMenuItem onClick={() => setTheme('system')}>
|
||||
<Monitor className="mr-2 h-4 w-4" />
|
||||
System Theme
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Palette className="mr-2 h-4 w-4" />
|
||||
Themes
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
|
||||
<DropdownMenuRadioItem value="light">
|
||||
<Sun className="mr-2 h-4 w-4" /> Light
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="dark">
|
||||
<Moon className="mr-2 h-4 w-4" />
|
||||
Dark
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="system">
|
||||
<Monitor className="mr-2 h-4 w-4" />
|
||||
System
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="https://github.com/documenso/documenso" className="cursor-pointer">
|
||||
<Github className="mr-2 h-4 w-4" />
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use server';
|
||||
|
||||
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
|
||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
|
||||
|
||||
@ -8,12 +9,20 @@ export type CompleteDocumentActionInput = TAddSubjectFormSchema & {
|
||||
documentId: number;
|
||||
};
|
||||
|
||||
export const completeDocument = async ({ documentId }: CompleteDocumentActionInput) => {
|
||||
export const completeDocument = async ({ documentId, email }: CompleteDocumentActionInput) => {
|
||||
'use server';
|
||||
|
||||
const { id: userId } = await getRequiredServerComponentSession();
|
||||
|
||||
await sendDocument({
|
||||
if (email.message || email.subject) {
|
||||
await upsertDocumentMeta({
|
||||
documentId,
|
||||
subject: email.subject,
|
||||
message: email.message,
|
||||
});
|
||||
}
|
||||
|
||||
return await sendDocument({
|
||||
userId,
|
||||
documentId,
|
||||
});
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export * from 'framer-motion';
|
||||
|
||||
export const MotionDiv = motion.div;
|
||||
66
apps/web/src/components/partials/not-found.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
import backgroundPattern from '~/assets/background-pattern.png';
|
||||
|
||||
export type NotFoundPartialProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function NotFoundPartial({ children }: NotFoundPartialProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className={cn('relative max-w-[100vw] overflow-hidden')}>
|
||||
<div className="absolute -inset-24 -z-10">
|
||||
<motion.div
|
||||
className="flex h-full w-full items-center justify-center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.8, transition: { duration: 0.5, delay: 0.5 } }}
|
||||
>
|
||||
<Image
|
||||
src={backgroundPattern}
|
||||
alt="background pattern"
|
||||
className="-mr-[50vw] -mt-[15vh] h-full scale-100 object-cover md:scale-100 lg:scale-[100%]"
|
||||
priority
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto flex h-full min-h-screen items-center px-6 py-32">
|
||||
<div>
|
||||
<p className="text-muted-foreground font-semibold">404 Page not found</p>
|
||||
|
||||
<h1 className="mt-3 text-2xl font-bold md:text-3xl">Oops! Something went wrong.</h1>
|
||||
|
||||
<p className="text-muted-foreground mt-4 text-sm">
|
||||
The page you are looking for was moved, removed, renamed or might never have existed.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex gap-x-2.5 gap-y-4 md:items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-32"
|
||||
onClick={() => {
|
||||
void router.back();
|
||||
}}
|
||||
>
|
||||
<ChevronLeft className="mr-2 h-4 w-4" />
|
||||
Go Back
|
||||
</Button>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
apps/web/src/helpers/get-asset-buffer.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* getAssetBuffer is used to retrieve array buffers for various assets
|
||||
* that are hosted in the `public` folder.
|
||||
*
|
||||
* This exists due to a breakage with `import.meta.url` imports and open graph images,
|
||||
* once we can identify a fix for this, we can remove this helper.
|
||||
*
|
||||
* @param path The path to the asset, relative to the `public` folder.
|
||||
*/
|
||||
export const getAssetBuffer = async (path: string) => {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
|
||||
return fetch(new URL(path, baseUrl)).then(async (res) => res.arrayBuffer());
|
||||
};
|
||||
442
package-lock.json
generated
@ -22,6 +22,7 @@
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^14.0.0",
|
||||
"prettier": "^2.5.1",
|
||||
"rimraf": "^5.0.1",
|
||||
"turbo": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -41,9 +42,9 @@
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"contentlayer": "^0.3.4",
|
||||
"framer-motion": "^10.12.8",
|
||||
"lucide-react": "^0.214.0",
|
||||
"lucide-react": "^0.279.0",
|
||||
"micro": "^10.0.1",
|
||||
"next": "13.4.12",
|
||||
"next": "13.4.19",
|
||||
"next-auth": "4.22.3",
|
||||
"next-contentlayer": "^0.3.4",
|
||||
"next-plausible": "^3.10.1",
|
||||
@ -78,10 +79,10 @@
|
||||
"@tanstack/react-query": "^4.29.5",
|
||||
"formidable": "^2.1.1",
|
||||
"framer-motion": "^10.12.8",
|
||||
"lucide-react": "^0.214.0",
|
||||
"lucide-react": "^0.279.0",
|
||||
"luxon": "^3.4.0",
|
||||
"micro": "^10.0.1",
|
||||
"next": "13.4.12",
|
||||
"next": "13.4.19",
|
||||
"next-auth": "4.22.3",
|
||||
"next-plausible": "^3.10.1",
|
||||
"next-themes": "^0.2.1",
|
||||
@ -2528,6 +2529,96 @@
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
|
||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
@ -2746,6 +2837,20 @@
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@mdx-js/esbuild": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mdx-js/esbuild/-/esbuild-2.3.0.tgz",
|
||||
@ -2818,22 +2923,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.12.tgz",
|
||||
"integrity": "sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ=="
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz",
|
||||
"integrity": "sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ=="
|
||||
},
|
||||
"node_modules/@next/eslint-plugin-next": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.12.tgz",
|
||||
"integrity": "sha512-6rhK9CdxEgj/j1qvXIyLTWEaeFv7zOK8yJMulz3Owel0uek0U9MJCGzmKgYxM3aAUBo3gKeywCZKyQnJKto60A==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.19.tgz",
|
||||
"integrity": "sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==",
|
||||
"dependencies": {
|
||||
"glob": "7.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.12.tgz",
|
||||
"integrity": "sha512-deUrbCXTMZ6ZhbOoloqecnUeNpUOupi8SE2tx4jPfNS9uyUR9zK4iXBvH65opVcA/9F5I/p8vDXSYbUlbmBjZg==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.19.tgz",
|
||||
"integrity": "sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -2846,9 +2951,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.12.tgz",
|
||||
"integrity": "sha512-WRvH7RxgRHlC1yb5oG0ZLx8F7uci9AivM5/HGGv9ZyG2Als8Ij64GC3d+mQ5sJhWjusyU6T6V1WKTUoTmOB0zQ==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.19.tgz",
|
||||
"integrity": "sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -2861,9 +2966,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.12.tgz",
|
||||
"integrity": "sha512-YEKracAWuxp54tKiAvvq73PUs9lok57cc8meYRibTWe/VdPB2vLgkTVWFcw31YDuRXdEhdX0fWS6Q+ESBhnEig==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.19.tgz",
|
||||
"integrity": "sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -2876,9 +2981,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.12.tgz",
|
||||
"integrity": "sha512-LhJR7/RAjdHJ2Isl2pgc/JaoxNk0KtBgkVpiDJPVExVWA1c6gzY57+3zWuxuyWzTG+fhLZo2Y80pLXgIJv7g3g==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.19.tgz",
|
||||
"integrity": "sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -2891,9 +2996,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.12.tgz",
|
||||
"integrity": "sha512-1DWLL/B9nBNiQRng+1aqs3OaZcxC16Nf+mOnpcrZZSdyKHek3WQh6j/fkbukObgNGwmCoVevLUa/p3UFTTqgqg==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.19.tgz",
|
||||
"integrity": "sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -2906,9 +3011,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.12.tgz",
|
||||
"integrity": "sha512-kEAJmgYFhp0VL+eRWmUkVxLVunn7oL9Mdue/FS8yzRBVj7Z0AnIrHpTIeIUl1bbdQq1VaoOztnKicAjfkLTRCQ==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.19.tgz",
|
||||
"integrity": "sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -2921,9 +3026,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.12.tgz",
|
||||
"integrity": "sha512-GMLuL/loR6yIIRTnPRY6UGbLL9MBdw2anxkOnANxvLvsml4F0HNIgvnU3Ej4BjbqMTNjD4hcPFdlEow4XHPdZA==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.19.tgz",
|
||||
"integrity": "sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -2936,9 +3041,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-ia32-msvc": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.12.tgz",
|
||||
"integrity": "sha512-PhgNqN2Vnkm7XaMdRmmX0ZSwZXQAtamBVSa9A/V1dfKQCV1rjIZeiy/dbBnVYGdj63ANfsOR/30XpxP71W0eww==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.19.tgz",
|
||||
"integrity": "sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -2951,9 +3056,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.12.tgz",
|
||||
"integrity": "sha512-Z+56e/Ljt0bUs+T+jPjhFyxYBcdY2RIq9ELFU+qAMQMteHo7ymbV7CKmlcX59RI9C4YzN8PgMgLyAoi916b5HA==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.19.tgz",
|
||||
"integrity": "sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -3646,6 +3751,16 @@
|
||||
"pako": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/utils": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
|
||||
@ -9594,19 +9709,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-next": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.12.tgz",
|
||||
"integrity": "sha512-ZF0r5vxKaVazyZH/37Au/XItiG7qUOBw+HaH3PeyXltIMwXorsn6bdrl0Nn9N5v5v9spc+6GM2ryjugbjF6X2g==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.19.tgz",
|
||||
"integrity": "sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==",
|
||||
"dependencies": {
|
||||
"@next/eslint-plugin-next": "13.4.12",
|
||||
"@next/eslint-plugin-next": "13.4.19",
|
||||
"@rushstack/eslint-patch": "^1.1.3",
|
||||
"@typescript-eslint/parser": "^5.42.0",
|
||||
"@typescript-eslint/parser": "^5.4.2 || ^6.0.0",
|
||||
"eslint-import-resolver-node": "^0.3.6",
|
||||
"eslint-import-resolver-typescript": "^3.5.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-react": "^7.31.7",
|
||||
"eslint-plugin-react-hooks": "5.0.0-canary-7118f5dd7-20230705"
|
||||
"eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^7.23.0 || ^8.0.0",
|
||||
@ -10442,6 +10557,20 @@
|
||||
"node": "^10.12.0 || >=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache/node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
|
||||
@ -10474,6 +10603,34 @@
|
||||
"is-callable": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
|
||||
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.0",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child/node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
@ -11957,6 +12114,24 @@
|
||||
"ws": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.3.tgz",
|
||||
"integrity": "sha512-R2bUw+kVZFS/h1AZqBKrSgDmdmjApzgY0AlCPumopFiAlbUxE2gf+SCuBzQ0cP5hHmUmFYF5yw55T97Th5Kstg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/javascript-natural-sort": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||
@ -12685,9 +12860,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.214.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.214.0.tgz",
|
||||
"integrity": "sha512-/vRi1wnFV2lqyIIkghQ3dDLu0eA9zykRQN9GZBwydzv+kB/2Q3S4X6OYB+aRqLXwl438vfVBqyYov2z0LJeoqA==",
|
||||
"version": "0.279.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.279.0.tgz",
|
||||
"integrity": "sha512-LJ8g66+Bxc3t3x9vKTeK3wn3xucrOQGfJ9ou9GsBwCt2offsrT2BB90XrTrIzE1noYYDe2O8jZaRHi6sAHXNxw==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
@ -13981,11 +14156,11 @@
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "13.4.12",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-13.4.12.tgz",
|
||||
"integrity": "sha512-eHfnru9x6NRmTMcjQp6Nz0J4XH9OubmzOa7CkWL+AUrUxpibub3vWwttjduu9No16dug1kq04hiUUpo7J3m3Xw==",
|
||||
"version": "13.4.19",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-13.4.19.tgz",
|
||||
"integrity": "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==",
|
||||
"dependencies": {
|
||||
"@next/env": "13.4.12",
|
||||
"@next/env": "13.4.19",
|
||||
"@swc/helpers": "0.5.1",
|
||||
"busboy": "1.6.0",
|
||||
"caniuse-lite": "^1.0.30001406",
|
||||
@ -14001,19 +14176,18 @@
|
||||
"node": ">=16.8.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "13.4.12",
|
||||
"@next/swc-darwin-x64": "13.4.12",
|
||||
"@next/swc-linux-arm64-gnu": "13.4.12",
|
||||
"@next/swc-linux-arm64-musl": "13.4.12",
|
||||
"@next/swc-linux-x64-gnu": "13.4.12",
|
||||
"@next/swc-linux-x64-musl": "13.4.12",
|
||||
"@next/swc-win32-arm64-msvc": "13.4.12",
|
||||
"@next/swc-win32-ia32-msvc": "13.4.12",
|
||||
"@next/swc-win32-x64-msvc": "13.4.12"
|
||||
"@next/swc-darwin-arm64": "13.4.19",
|
||||
"@next/swc-darwin-x64": "13.4.19",
|
||||
"@next/swc-linux-arm64-gnu": "13.4.19",
|
||||
"@next/swc-linux-arm64-musl": "13.4.19",
|
||||
"@next/swc-linux-x64-gnu": "13.4.19",
|
||||
"@next/swc-linux-x64-musl": "13.4.19",
|
||||
"@next/swc-win32-arm64-msvc": "13.4.19",
|
||||
"@next/swc-win32-ia32-msvc": "13.4.19",
|
||||
"@next/swc-win32-x64-msvc": "13.4.19"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": "^1.1.0",
|
||||
"fibers": ">= 3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.3.0"
|
||||
@ -14022,9 +14196,6 @@
|
||||
"@opentelemetry/api": {
|
||||
"optional": true
|
||||
},
|
||||
"fibers": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
}
|
||||
@ -14753,6 +14924,31 @@
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
|
||||
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^9.1.1 || ^10.0.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz",
|
||||
"integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "14 || >=16.14"
|
||||
}
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
@ -16821,14 +17017,64 @@
|
||||
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz",
|
||||
"integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
"glob": "^10.2.5"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
"rimraf": "dist/cjs/src/bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/glob": {
|
||||
"version": "10.3.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.5.tgz",
|
||||
"integrity": "sha512-bYUpUD7XDEHI4Q2O5a7PXGvyw4deKR70kHiDxzQbe925wbZknhOzUt2xBgTkYL6RBcVeXYuD9iNYeqoWbBZQnA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.0.3",
|
||||
"minimatch": "^9.0.1",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
|
||||
"path-scurry": "^1.10.1"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/cjs/src/bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/minimatch": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
|
||||
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@ -17439,6 +17685,27 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs": {
|
||||
"name": "string-width",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/string-width/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@ -17528,6 +17795,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs": {
|
||||
"name": "strip-ansi",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-bom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
@ -19290,6 +19570,24 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
"name": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
@ -19439,7 +19737,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||
"@typescript-eslint/parser": "^5.59.2",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-config-next": "13.4.12",
|
||||
"eslint-config-next": "13.4.19",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-config-turbo": "^1.9.3",
|
||||
"eslint-plugin-package-json": "^0.1.4",
|
||||
@ -19466,7 +19764,7 @@
|
||||
"bcrypt": "^5.1.0",
|
||||
"luxon": "^3.4.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"next": "13.4.12",
|
||||
"next": "13.4.19",
|
||||
"next-auth": "4.22.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"react": "18.2.0",
|
||||
@ -19579,8 +19877,8 @@
|
||||
"clsx": "^1.2.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"framer-motion": "^10.12.8",
|
||||
"lucide-react": "^0.214.0",
|
||||
"next": "13.4.12",
|
||||
"lucide-react": "^0.279.0",
|
||||
"next": "13.4.19",
|
||||
"pdfjs-dist": "3.6.172",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-pdf": "^7.3.3",
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
"lint": "turbo run lint",
|
||||
"format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",
|
||||
"prepare": "husky install",
|
||||
"commitlint": "commitlint --edit"
|
||||
"commitlint": "commitlint --edit",
|
||||
"clean": "turbo run clean && rimraf node_modules"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=8.6.0",
|
||||
@ -23,6 +24,7 @@
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^14.0.0",
|
||||
"prettier": "^2.5.1",
|
||||
"rimraf": "^5.0.1",
|
||||
"turbo": "^1.9.3"
|
||||
},
|
||||
"name": "@documenso/root",
|
||||
|
||||
@ -9,7 +9,9 @@
|
||||
"server-only/",
|
||||
"universal/"
|
||||
],
|
||||
"scripts": {},
|
||||
"scripts": {
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@documenso/lib": "*",
|
||||
"@documenso/prisma": "*"
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "email dev --port 3002 --dir templates",
|
||||
"clean": "rimraf node_modules",
|
||||
"worker:test": "tsup worker/index.ts --format esm"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button, Img, Section, Tailwind, Text } from '@react-email/components';
|
||||
import { Button, Column, Img, Row, Section, Tailwind, Text } from '@react-email/components';
|
||||
|
||||
import * as config from '@documenso/tailwind-config';
|
||||
|
||||
@ -29,11 +29,23 @@ export const TemplateDocumentCompleted = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<Section>
|
||||
<Row className="table-fixed">
|
||||
<Column />
|
||||
|
||||
<Column>
|
||||
<Img
|
||||
className="h-42 mx-auto"
|
||||
src={getAssetUrl('/static/document.png')}
|
||||
alt="Documenso"
|
||||
/>
|
||||
</Column>
|
||||
|
||||
<Column />
|
||||
</Row>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button, Img, Section, Tailwind, Text } from '@react-email/components';
|
||||
import { Button, Column, Img, Row, Section, Tailwind, Text } from '@react-email/components';
|
||||
|
||||
import * as config from '@documenso/tailwind-config';
|
||||
|
||||
@ -30,13 +30,26 @@ export const TemplateDocumentInvite = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<Section className="mt-4">
|
||||
<Row className="table-fixed">
|
||||
<Column />
|
||||
|
||||
<Column>
|
||||
<Img
|
||||
className="h-42 mx-auto"
|
||||
src={getAssetUrl('/static/document.png')}
|
||||
alt="Documenso"
|
||||
/>
|
||||
</Column>
|
||||
|
||||
<Column />
|
||||
</Row>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
{inviterName} has invited you to sign "{documentName}"
|
||||
{inviterName} has invited you to sign
|
||||
<br />"{documentName}"
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Img, Section, Tailwind, Text } from '@react-email/components';
|
||||
import { Column, Img, Row, Section, Tailwind, Text } from '@react-email/components';
|
||||
|
||||
import * as config from '@documenso/tailwind-config';
|
||||
|
||||
@ -25,11 +25,23 @@ export const TemplateDocumentPending = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<Section>
|
||||
<Row className="table-fixed">
|
||||
<Column />
|
||||
|
||||
<Column>
|
||||
<Img
|
||||
className="h-42 mx-auto"
|
||||
src={getAssetUrl('/static/document.png')}
|
||||
alt="Documenso"
|
||||
/>
|
||||
</Column>
|
||||
|
||||
<Column />
|
||||
</Row>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<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
|
||||
|
||||
@ -20,7 +20,9 @@ import {
|
||||
} from '../template-components/template-document-invite';
|
||||
import TemplateFooter from '../template-components/template-footer';
|
||||
|
||||
export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInviteProps>;
|
||||
export type DocumentInviteEmailTemplateProps = Partial<TemplateDocumentInviteProps> & {
|
||||
customBody?: string;
|
||||
};
|
||||
|
||||
export const DocumentInviteEmailTemplate = ({
|
||||
inviterName = 'Lucas Smith',
|
||||
@ -28,6 +30,7 @@ export const DocumentInviteEmailTemplate = ({
|
||||
documentName = 'Open Source Pledge.pdf',
|
||||
signDocumentLink = 'https://documenso.com',
|
||||
assetBaseUrl = 'http://localhost:3002',
|
||||
customBody,
|
||||
}: DocumentInviteEmailTemplateProps) => {
|
||||
const previewText = `Completed Document`;
|
||||
|
||||
@ -78,7 +81,11 @@ export const DocumentInviteEmailTemplate = ({
|
||||
</Text>
|
||||
|
||||
<Text className="mt-2 text-base text-slate-400">
|
||||
{inviterName} has invited you to sign the document "{documentName}".
|
||||
{customBody ? (
|
||||
<pre className="font-sans text-base text-slate-400">{customBody}</pre>
|
||||
) : (
|
||||
`${inviterName} has invited you to sign the document "${documentName}".`
|
||||
)}
|
||||
</Text>
|
||||
</Section>
|
||||
</Container>
|
||||
|
||||
@ -3,11 +3,14 @@
|
||||
"version": "0.0.0",
|
||||
"main": "./index.cjs",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||
"@typescript-eslint/parser": "^5.59.2",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-config-next": "13.4.12",
|
||||
"eslint-config-next": "13.4.19",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-config-turbo": "^1.9.3",
|
||||
"eslint-plugin-package-json": "^0.1.4",
|
||||
|
||||
@ -10,7 +10,9 @@
|
||||
"universal/",
|
||||
"next-auth/"
|
||||
],
|
||||
"scripts": {},
|
||||
"scripts": {
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.410.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.410.0",
|
||||
@ -25,7 +27,7 @@
|
||||
"bcrypt": "^5.1.0",
|
||||
"luxon": "^3.4.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"next": "13.4.12",
|
||||
"next": "13.4.19",
|
||||
"next-auth": "4.22.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"react": "18.2.0",
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
'use server';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type CreateDocumentMetaOptions = {
|
||||
documentId: number;
|
||||
subject: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export const upsertDocumentMeta = async ({
|
||||
subject,
|
||||
message,
|
||||
documentId,
|
||||
}: CreateDocumentMetaOptions) => {
|
||||
return await prisma.documentMeta.upsert({
|
||||
where: {
|
||||
documentId,
|
||||
},
|
||||
create: {
|
||||
subject,
|
||||
message,
|
||||
documentId,
|
||||
},
|
||||
update: {
|
||||
subject,
|
||||
message,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -4,6 +4,7 @@ import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { sealDocument } from './seal-document';
|
||||
import { sendPendingEmail } from './send-pending-email';
|
||||
|
||||
export type CompleteDocumentWithTokenOptions = {
|
||||
token: string;
|
||||
@ -69,6 +70,19 @@ export const completeDocumentWithToken = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const pendingRecipients = await prisma.recipient.count({
|
||||
where: {
|
||||
documentId: document.id,
|
||||
signingStatus: {
|
||||
not: SigningStatus.SIGNED,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (pendingRecipients > 0) {
|
||||
await sendPendingEmail({ documentId, recipientId: recipient.id });
|
||||
}
|
||||
|
||||
const documents = await prisma.document.updateMany({
|
||||
where: {
|
||||
id: document.id,
|
||||
|
||||
@ -13,6 +13,7 @@ export const getDocumentById = async ({ id, userId }: GetDocumentByIdOptions) =>
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -9,6 +9,7 @@ import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { putFile } from '../../universal/upload/put-file';
|
||||
import { insertFieldInPDF } from '../pdf/insert-field-in-pdf';
|
||||
import { sendCompletedEmail } from './send-completed-email';
|
||||
|
||||
export type SealDocumentOptions = {
|
||||
documentId: number;
|
||||
@ -86,4 +87,6 @@ export const sealDocument = async ({ documentId }: SealDocumentOptions) => {
|
||||
data: newData,
|
||||
},
|
||||
});
|
||||
|
||||
await sendCompletedEmail({ documentId });
|
||||
};
|
||||
|
||||
57
packages/lib/server-only/document/send-completed-email.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export interface SendDocumentOptions {
|
||||
documentId: number;
|
||||
}
|
||||
|
||||
export const sendCompletedEmail = async ({ documentId }: SendDocumentOptions) => {
|
||||
const document = await prisma.document.findUnique({
|
||||
where: {
|
||||
id: documentId,
|
||||
},
|
||||
include: {
|
||||
Recipient: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
throw new Error('Document not found');
|
||||
}
|
||||
|
||||
if (document.Recipient.length === 0) {
|
||||
throw new Error('Document has no recipients');
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
document.Recipient.map(async (recipient) => {
|
||||
const { email, name, token } = recipient;
|
||||
|
||||
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
|
||||
const template = createElement(DocumentCompletedEmailTemplate, {
|
||||
documentName: document.title,
|
||||
assetBaseUrl,
|
||||
downloadLink: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${token}/complete`,
|
||||
});
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: email,
|
||||
name,
|
||||
},
|
||||
from: {
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Signing Complete!',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
});
|
||||
}),
|
||||
]);
|
||||
};
|
||||
@ -3,13 +3,14 @@ import { createElement } from 'react';
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
|
||||
|
||||
export interface SendDocumentOptions {
|
||||
export type SendDocumentOptions = {
|
||||
documentId: number;
|
||||
userId: number;
|
||||
}
|
||||
};
|
||||
|
||||
export const sendDocument = async ({ documentId, userId }: SendDocumentOptions) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
@ -25,9 +26,12 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
|
||||
},
|
||||
include: {
|
||||
Recipient: true,
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
const customEmail = document?.documentMeta;
|
||||
|
||||
if (!document) {
|
||||
throw new Error('Document not found');
|
||||
}
|
||||
@ -44,6 +48,12 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
|
||||
document.Recipient.map(async (recipient) => {
|
||||
const { email, name } = recipient;
|
||||
|
||||
const customEmailTemplate = {
|
||||
'signer.name': name,
|
||||
'signer.email': email,
|
||||
'document.name': document.title,
|
||||
};
|
||||
|
||||
if (recipient.sendStatus === SendStatus.SENT) {
|
||||
return;
|
||||
}
|
||||
@ -57,6 +67,7 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
|
||||
inviterEmail: user.email,
|
||||
assetBaseUrl,
|
||||
signDocumentLink,
|
||||
customBody: renderCustomEmailTemplate(customEmail?.message || '', customEmailTemplate),
|
||||
});
|
||||
|
||||
await mailer.sendMail({
|
||||
@ -68,7 +79,9 @@ export const sendDocument = async ({ documentId, userId }: SendDocumentOptions)
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Please sign this document',
|
||||
subject: customEmail?.subject
|
||||
? renderCustomEmailTemplate(customEmail.subject, customEmailTemplate)
|
||||
: 'Please sign this document',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
});
|
||||
|
||||
64
packages/lib/server-only/document/send-pending-email.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export interface SendPendingEmailOptions {
|
||||
documentId: number;
|
||||
recipientId: number;
|
||||
}
|
||||
|
||||
export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingEmailOptions) => {
|
||||
const document = await prisma.document.findFirst({
|
||||
where: {
|
||||
id: documentId,
|
||||
Recipient: {
|
||||
some: {
|
||||
id: recipientId,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
Recipient: {
|
||||
where: {
|
||||
id: recipientId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
throw new Error('Document not found');
|
||||
}
|
||||
|
||||
if (document.Recipient.length === 0) {
|
||||
throw new Error('Document has no recipients');
|
||||
}
|
||||
|
||||
const [recipient] = document.Recipient;
|
||||
|
||||
const { email, name } = recipient;
|
||||
|
||||
const assetBaseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
|
||||
|
||||
const template = createElement(DocumentPendingEmailTemplate, {
|
||||
documentName: document.title,
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: email,
|
||||
name,
|
||||
},
|
||||
from: {
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Waiting for others to complete signing.',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
});
|
||||
};
|
||||
21
packages/lib/server-only/document/update-document.ts
Normal file
@ -0,0 +1,21 @@
|
||||
'use server';
|
||||
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type UpdateDocumentOptions = {
|
||||
documentId: number;
|
||||
data: Prisma.DocumentUpdateInput;
|
||||
};
|
||||
|
||||
export const updateDocument = async ({ documentId, data }: UpdateDocumentOptions) => {
|
||||
return await prisma.document.update({
|
||||
where: {
|
||||
id: documentId,
|
||||
},
|
||||
data: {
|
||||
...data,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -50,10 +50,10 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
let imageWidth = image.width;
|
||||
let imageHeight = image.height;
|
||||
|
||||
const initialDimensions = {
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
};
|
||||
// const initialDimensions = {
|
||||
// width: imageWidth,
|
||||
// height: imageHeight,
|
||||
// };
|
||||
|
||||
const scalingFactor = Math.min(fieldWidth / imageWidth, fieldHeight / imageHeight, 1);
|
||||
|
||||
@ -76,10 +76,10 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
let textWidth = font.widthOfTextAtSize(field.customText, fontSize);
|
||||
const textHeight = font.heightAtSize(fontSize);
|
||||
|
||||
const initialDimensions = {
|
||||
width: textWidth,
|
||||
height: textHeight,
|
||||
};
|
||||
// const initialDimensions = {
|
||||
// width: textWidth,
|
||||
// height: textHeight,
|
||||
// };
|
||||
|
||||
const scalingFactor = Math.min(fieldWidth / textWidth, fieldHeight / textHeight, 1);
|
||||
|
||||
|
||||
58
packages/lib/server-only/share/create-or-get-share-link.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { alphaid } from '../../universal/id';
|
||||
|
||||
export type CreateSharingIdOptions =
|
||||
| {
|
||||
documentId: number;
|
||||
token: string;
|
||||
}
|
||||
| {
|
||||
documentId: number;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const createOrGetShareLink = async ({ documentId, ...options }: CreateSharingIdOptions) => {
|
||||
const email = await match(options)
|
||||
.with({ token: P.string }, async ({ token }) => {
|
||||
return await prisma.recipient
|
||||
.findFirst({
|
||||
where: {
|
||||
documentId,
|
||||
token,
|
||||
},
|
||||
})
|
||||
.then((recipient) => recipient?.email);
|
||||
})
|
||||
.with({ userId: P.number }, async ({ userId }) => {
|
||||
return await prisma.user
|
||||
.findFirst({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
})
|
||||
.then((user) => user?.email);
|
||||
})
|
||||
.exhaustive();
|
||||
|
||||
if (!email) {
|
||||
throw new Error('Unable to create share link for document with the given email');
|
||||
}
|
||||
|
||||
return await prisma.documentShareLink.upsert({
|
||||
where: {
|
||||
documentId_email: {
|
||||
email,
|
||||
documentId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
email,
|
||||
documentId,
|
||||
slug: alphaid(14),
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,42 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type GetRecipientOrSenderByShareLinkSlugOptions = {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export const getRecipientOrSenderByShareLinkSlug = async ({
|
||||
slug,
|
||||
}: GetRecipientOrSenderByShareLinkSlugOptions) => {
|
||||
const { documentId, email } = await prisma.documentShareLink.findFirstOrThrow({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
});
|
||||
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
documentId,
|
||||
email,
|
||||
},
|
||||
include: {
|
||||
Signature: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (recipient) {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
const sender = await prisma.user.findFirst({
|
||||
where: {
|
||||
Document: { some: { id: documentId } },
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
if (sender) {
|
||||
return sender;
|
||||
}
|
||||
|
||||
throw new Error('Recipient or sender not found');
|
||||
};
|
||||
13
packages/lib/server-only/share/get-share-link-by-slug.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type GetShareLinkBySlugOptions = {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export const getShareLinkBySlug = async ({ slug }: GetShareLinkBySlugOptions) => {
|
||||
return await prisma.documentShareLink.findFirstOrThrow({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
});
|
||||
};
|
||||
1
packages/lib/types/font.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module '*.ttf';
|
||||
12
packages/lib/utils/render-custom-email-template.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export const renderCustomEmailTemplate = <T extends Record<string, string>>(
|
||||
template: string,
|
||||
variables: T,
|
||||
): string => {
|
||||
return template.replace(/\{(\S+)\}/g, (_, key) => {
|
||||
if (key in variables) {
|
||||
return variables[key];
|
||||
}
|
||||
|
||||
return key;
|
||||
});
|
||||
};
|
||||
@ -3,6 +3,9 @@
|
||||
"version": "0.0.0",
|
||||
"main": "./index.cjs",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
|
||||
"prettier": "^2.8.8",
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Share" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"link" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"documentId" INTEGER,
|
||||
|
||||
CONSTRAINT "Share_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Share_link_key" ON "Share"("link");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Share" ADD CONSTRAINT "Share_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Share" ADD CONSTRAINT "Share_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@ -0,0 +1,16 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `userId` on the `Share` table. All the data in the column will be lost.
|
||||
- Added the required column `recipientId` to the `Share` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Share" DROP CONSTRAINT "Share_userId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Share" DROP COLUMN "userId",
|
||||
ADD COLUMN "recipientId" INTEGER NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Share" ADD CONSTRAINT "Share_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "Recipient"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -0,0 +1,14 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Document" ADD COLUMN "documentMetaId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DocumentMeta" (
|
||||
"id" TEXT NOT NULL,
|
||||
"customEmailSubject" TEXT,
|
||||
"customEmailBody" TEXT,
|
||||
|
||||
CONSTRAINT "DocumentMeta_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Document" ADD CONSTRAINT "Document_documentMetaId_fkey" FOREIGN KEY ("documentMetaId") REFERENCES "DocumentMeta"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -0,0 +1,35 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `Share` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Share" DROP CONSTRAINT "Share_documentId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Share" DROP CONSTRAINT "Share_recipientId_fkey";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Share";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DocumentShareLink" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"documentId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DocumentShareLink_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DocumentShareLink_slug_key" ON "DocumentShareLink"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DocumentShareLink_documentId_email_key" ON "DocumentShareLink"("documentId", "email");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DocumentShareLink" ADD CONSTRAINT "DocumentShareLink_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[documentMetaId]` on the table `Document` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Document_documentMetaId_key" ON "Document"("documentMetaId");
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `documentMetaId` on the `Document` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `customEmailBody` on the `DocumentMeta` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `customEmailSubject` on the `DocumentMeta` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[documentId]` on the table `DocumentMeta` will be added. If there are existing duplicate values, this will fail.
|
||||
- Added the required column `documentId` to the `DocumentMeta` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Document" DROP CONSTRAINT "Document_documentMetaId_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "Document_documentMetaId_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "DocumentMeta"
|
||||
ADD COLUMN "documentId" INTEGER,
|
||||
ADD COLUMN "message" TEXT,
|
||||
ADD COLUMN "subject" TEXT;
|
||||
|
||||
-- Migrate data
|
||||
UPDATE "DocumentMeta" SET "documentId" = (
|
||||
SELECT "id" FROM "Document" WHERE "Document"."documentMetaId" = "DocumentMeta"."id"
|
||||
);
|
||||
|
||||
-- Migrate data
|
||||
UPDATE "DocumentMeta" SET "message" = "customEmailBody";
|
||||
|
||||
-- Migrate data
|
||||
UPDATE "DocumentMeta" SET "subject" = "customEmailSubject";
|
||||
|
||||
-- Prune data
|
||||
DELETE FROM "DocumentMeta" WHERE "documentId" IS NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Document" DROP COLUMN "documentMetaId";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "DocumentMeta"
|
||||
DROP COLUMN "customEmailBody",
|
||||
DROP COLUMN "customEmailSubject";
|
||||
|
||||
-- AlterColumn
|
||||
ALTER TABLE "DocumentMeta" ALTER COLUMN "documentId" SET NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DocumentMeta_documentId_key" ON "DocumentMeta"("documentId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DocumentMeta" ADD CONSTRAINT "DocumentMeta_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -7,6 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "prisma generate",
|
||||
"format": "prisma format",
|
||||
"clean": "rimraf node_modules",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate-dev": "prisma migrate dev",
|
||||
"prisma:migrate-deploy": "prisma migrate deploy",
|
||||
|
||||
@ -101,15 +101,17 @@ enum DocumentStatus {
|
||||
}
|
||||
|
||||
model Document {
|
||||
id Int @id @default(autoincrement())
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
title String
|
||||
status DocumentStatus @default(DRAFT)
|
||||
status DocumentStatus @default(DRAFT)
|
||||
Recipient Recipient[]
|
||||
Field Field[]
|
||||
ShareLink DocumentShareLink[]
|
||||
documentDataId String
|
||||
documentData DocumentData @relation(fields: [documentDataId], references: [id], onDelete: Cascade)
|
||||
documentMeta DocumentMeta?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
@ -130,6 +132,14 @@ model DocumentData {
|
||||
Document Document?
|
||||
}
|
||||
|
||||
model DocumentMeta {
|
||||
id String @id @default(cuid())
|
||||
subject String?
|
||||
message String?
|
||||
documentId Int @unique
|
||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
enum ReadStatus {
|
||||
NOT_OPENED
|
||||
OPENED
|
||||
@ -200,3 +210,16 @@ model Signature {
|
||||
Recipient Recipient @relation(fields: [recipientId], references: [id], onDelete: Cascade)
|
||||
Field Field @relation(fields: [fieldId], references: [id], onDelete: Restrict)
|
||||
}
|
||||
|
||||
model DocumentShareLink {
|
||||
id Int @id @default(autoincrement())
|
||||
email String
|
||||
slug String @unique
|
||||
documentId Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
document Document @relation(fields: [documentId], references: [id])
|
||||
|
||||
@@unique([documentId, email])
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Document, DocumentData } from '@documenso/prisma/client';
|
||||
import { Document, DocumentData, DocumentMeta } from '@documenso/prisma/client';
|
||||
|
||||
export type DocumentWithData = Document & {
|
||||
documentData?: DocumentData | null;
|
||||
documentMeta?: DocumentMeta | null;
|
||||
};
|
||||
|
||||
@ -3,6 +3,9 @@
|
||||
"version": "0.0.0",
|
||||
"main": "index.cjs",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.4.13",
|
||||
"postcss": "^8.4.21",
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
"types": "./index.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@documenso/lib": "*",
|
||||
|
||||
@ -2,6 +2,7 @@ import { authRouter } from './auth-router/router';
|
||||
import { documentRouter } from './document-router/router';
|
||||
import { fieldRouter } from './field-router/router';
|
||||
import { profileRouter } from './profile-router/router';
|
||||
import { shareLinkRouter } from './share-link-router/router';
|
||||
import { procedure, router } from './trpc';
|
||||
|
||||
export const appRouter = router({
|
||||
@ -10,6 +11,7 @@ export const appRouter = router({
|
||||
profile: profileRouter,
|
||||
document: documentRouter,
|
||||
field: fieldRouter,
|
||||
shareLink: shareLinkRouter,
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
35
packages/trpc/server/share-link-router/router.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { createOrGetShareLink } from '@documenso/lib/server-only/share/create-or-get-share-link';
|
||||
|
||||
import { procedure, router } from '../trpc';
|
||||
import { ZCreateOrGetShareLinkMutationSchema } from './schema';
|
||||
|
||||
export const shareLinkRouter = router({
|
||||
createOrGetShareLink: procedure
|
||||
.input(ZCreateOrGetShareLinkMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
const { documentId, token } = input;
|
||||
|
||||
if (token) {
|
||||
return await createOrGetShareLink({ documentId, token });
|
||||
}
|
||||
|
||||
if (!ctx.user?.id) {
|
||||
throw new Error(
|
||||
'You must either provide a token or be logged in to create a sharing link.',
|
||||
);
|
||||
}
|
||||
|
||||
return await createOrGetShareLink({ documentId, userId: ctx.user.id });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'We were unable to create a sharing link.',
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
10
packages/trpc/server/share-link-router/schema.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateOrGetShareLinkMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
token: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TCreateOrGetShareLinkMutationSchema = z.infer<
|
||||
typeof ZCreateOrGetShareLinkMutationSchema
|
||||
>;
|
||||
@ -3,6 +3,9 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"files": [
|
||||
"base.json",
|
||||
"nextjs.json",
|
||||
|
||||
@ -1,28 +1,32 @@
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
export const SignatureIcon: LucideIcon = forwardRef(
|
||||
(
|
||||
{ size = 24, color = 'currentColor', strokeWidth = 1.33, absoluteStrokeWidth, ...props },
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<svg
|
||||
ref={ref}
|
||||
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>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -12,7 +12,8 @@
|
||||
"index.tsx"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint \"**/*.ts*\""
|
||||
"lint": "eslint \"**/*.ts*\"",
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@documenso/tailwind-config": "*",
|
||||
@ -54,8 +55,8 @@
|
||||
"clsx": "^1.2.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"framer-motion": "^10.12.8",
|
||||
"lucide-react": "^0.214.0",
|
||||
"next": "13.4.12",
|
||||
"lucide-react": "^0.279.0",
|
||||
"next": "13.4.19",
|
||||
"pdfjs-dist": "3.6.172",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-pdf": "^7.3.3",
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { Document, DocumentStatus, Field, Recipient } from '@documenso/prisma/client';
|
||||
import { DocumentStatus, Field, Recipient } from '@documenso/prisma/client';
|
||||
import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
@ -21,7 +22,7 @@ export type AddSubjectFormProps = {
|
||||
documentFlow: DocumentFlowStep;
|
||||
recipients: Recipient[];
|
||||
fields: Field[];
|
||||
document: Document;
|
||||
document: DocumentWithData;
|
||||
numberOfSteps: number;
|
||||
onSubmit: (_data: TAddSubjectFormSchema) => void;
|
||||
};
|
||||
@ -41,8 +42,8 @@ export const AddSubjectFormPartial = ({
|
||||
} = useForm<TAddSubjectFormSchema>({
|
||||
defaultValues: {
|
||||
email: {
|
||||
subject: '',
|
||||
message: '',
|
||||
subject: document.documentMeta?.subject ?? '',
|
||||
message: document.documentMeta?.message ?? '',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -11,6 +11,9 @@
|
||||
]
|
||||
},
|
||||
"lint": {},
|
||||
"clean": {
|
||||
"cache": false
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
|
||||