diff --git a/apps/marketing/package.json b/apps/marketing/package.json index c76c5e631..8e61ad51f 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -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", diff --git a/apps/web/package.json b/apps/web/package.json index d3ab34f96..347a39d35 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -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", diff --git a/apps/web/public/fonts/caveat-regular.ttf b/apps/web/public/fonts/caveat-regular.ttf new file mode 100644 index 000000000..96540955a Binary files /dev/null and b/apps/web/public/fonts/caveat-regular.ttf differ diff --git a/apps/web/public/fonts/inter-bold.ttf b/apps/web/public/fonts/inter-bold.ttf new file mode 100644 index 000000000..8e82c70d1 Binary files /dev/null and b/apps/web/public/fonts/inter-bold.ttf differ diff --git a/apps/web/public/fonts/inter-regular.ttf b/apps/web/public/fonts/inter-regular.ttf new file mode 100644 index 000000000..8d4eebf20 Binary files /dev/null and b/apps/web/public/fonts/inter-regular.ttf differ diff --git a/apps/web/public/fonts/inter-semibold.ttf b/apps/web/public/fonts/inter-semibold.ttf new file mode 100644 index 000000000..c6aeeb16a Binary files /dev/null and b/apps/web/public/fonts/inter-semibold.ttf differ diff --git a/apps/web/public/next.svg b/apps/web/public/next.svg deleted file mode 100644 index 5174b28c5..000000000 --- a/apps/web/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/web/public/static/og-share-frame.png b/apps/web/public/static/og-share-frame.png new file mode 100644 index 000000000..72bf7dd96 Binary files /dev/null and b/apps/web/public/static/og-share-frame.png differ diff --git a/apps/web/public/vercel.svg b/apps/web/public/vercel.svg deleted file mode 100644 index d2f842227..000000000 --- a/apps/web/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx index 7c1d42d2b..4443981f8 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx @@ -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) => { )) .otherwise(() => ( - )); diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx index dd028e0b2..e0eac175e 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx @@ -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 - - + + {isCreatingShareLink ? ( + + ) : ( + + )} Share diff --git a/apps/web/src/app/(share)/share/[slug]/opengraph-image.tsx b/apps/web/src/app/(share)/share/[slug]/opengraph-image.tsx new file mode 100644 index 000000000..8751f407e --- /dev/null +++ b/apps/web/src/app/(share)/share/[slug]/opengraph-image.tsx @@ -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( + ( +
+ {/* @ts-expect-error Lack of typing from ImageResponse */} + og-share-frame + +
+ {/* @ts-expect-error Lack of typing from ImageResponse */} + +
+ + {signatureImage ? ( +
+ signature +
+ ) : ( +

+ {signatureName} +

+ )} + + {/*
+ {signatureName} +
*/} + +
+

+ {isRecipient + ? 'I just signed with Documenso and you can too!' + : 'I just sent a document with Documenso and you can too!'} +

+
+
+ ), + { + ...size, + fonts: [ + { + name: 'Caveat', + data: caveatRegular, + style: 'italic', + }, + { + name: 'Inter', + data: interRegular, + style: 'normal', + weight: 400, + }, + { + name: 'Inter', + data: interSemiBold, + style: 'normal', + weight: 600, + }, + ], + }, + ); +} diff --git a/apps/web/src/app/(share)/share/[slug]/page.tsx b/apps/web/src/app/(share)/share/[slug]/page.tsx new file mode 100644 index 000000000..63449f29f --- /dev/null +++ b/apps/web/src/app/(share)/share/[slug]/page.tsx @@ -0,0 +1,11 @@ +import { Metadata } from 'next'; + +import { Redirect } from './redirect'; + +export const metadata: Metadata = { + title: 'Documenso - Share', +}; + +export default function SharePage() { + return ; +} diff --git a/apps/web/src/app/(share)/share/[slug]/redirect.tsx b/apps/web/src/app/(share)/share/[slug]/redirect.tsx new file mode 100644 index 000000000..5b3af0771 --- /dev/null +++ b/apps/web/src/app/(share)/share/[slug]/redirect.tsx @@ -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; +}; diff --git a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx index 71a368da5..af9b2ab06 100644 --- a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx @@ -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({ ))}
- {/* TODO: Hook this up */} - + & { + 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 ( + + ); +}; diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx index d2c14a524..2d61f096e 100644 --- a/apps/web/src/app/(signing)/sign/[token]/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx @@ -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) { diff --git a/apps/web/src/assets/background-pattern-og.png b/apps/web/src/assets/background-pattern-og.png new file mode 100644 index 000000000..363bbd2e2 Binary files /dev/null and b/apps/web/src/assets/background-pattern-og.png differ diff --git a/apps/web/src/assets/caveat-regular.ttf b/apps/web/src/assets/caveat-regular.ttf new file mode 100644 index 000000000..96540955a Binary files /dev/null and b/apps/web/src/assets/caveat-regular.ttf differ diff --git a/apps/web/src/assets/inter-bold.ttf b/apps/web/src/assets/inter-bold.ttf new file mode 100644 index 000000000..8e82c70d1 Binary files /dev/null and b/apps/web/src/assets/inter-bold.ttf differ diff --git a/apps/web/src/assets/inter-regular.ttf b/apps/web/src/assets/inter-regular.ttf new file mode 100644 index 000000000..8d4eebf20 Binary files /dev/null and b/apps/web/src/assets/inter-regular.ttf differ diff --git a/apps/web/src/assets/inter-semibold.ttf b/apps/web/src/assets/inter-semibold.ttf new file mode 100644 index 000000000..c6aeeb16a Binary files /dev/null and b/apps/web/src/assets/inter-semibold.ttf differ diff --git a/apps/web/src/assets/og-share-frame.png b/apps/web/src/assets/og-share-frame.png new file mode 100644 index 000000000..72bf7dd96 Binary files /dev/null and b/apps/web/src/assets/og-share-frame.png differ diff --git a/apps/web/src/helpers/get-asset-buffer.ts b/apps/web/src/helpers/get-asset-buffer.ts new file mode 100644 index 000000000..85887071e --- /dev/null +++ b/apps/web/src/helpers/get-asset-buffer.ts @@ -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()); +}; diff --git a/package-lock.json b/package-lock.json index c6558b466..254855629 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 3b2fbf6ca..6de670d99 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/ee/package.json b/packages/ee/package.json index 4c810a235..b6d237fd3 100644 --- a/packages/ee/package.json +++ b/packages/ee/package.json @@ -9,7 +9,9 @@ "server-only/", "universal/" ], - "scripts": {}, + "scripts": { + "clean": "rimraf node_modules" + }, "dependencies": { "@documenso/lib": "*", "@documenso/prisma": "*" diff --git a/packages/email/package.json b/packages/email/package.json index 20e214fee..7bc6d9e5a 100644 --- a/packages/email/package.json +++ b/packages/email/package.json @@ -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": { diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index d9b53c540..cfa86f06d 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -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", diff --git a/packages/lib/package.json b/packages/lib/package.json index 757ab7932..e8c6bcee3 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -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", diff --git a/packages/lib/server-only/share/create-or-get-share-link.ts b/packages/lib/server-only/share/create-or-get-share-link.ts new file mode 100644 index 000000000..1569b98af --- /dev/null +++ b/packages/lib/server-only/share/create-or-get-share-link.ts @@ -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: {}, + }); +}; diff --git a/packages/lib/server-only/share/get-recipient-or-sender-by-share-link-slug.ts b/packages/lib/server-only/share/get-recipient-or-sender-by-share-link-slug.ts new file mode 100644 index 000000000..fb320c224 --- /dev/null +++ b/packages/lib/server-only/share/get-recipient-or-sender-by-share-link-slug.ts @@ -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'); +}; diff --git a/packages/lib/server-only/share/get-share-link-by-slug.ts b/packages/lib/server-only/share/get-share-link-by-slug.ts new file mode 100644 index 000000000..4486de81a --- /dev/null +++ b/packages/lib/server-only/share/get-share-link-by-slug.ts @@ -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, + }, + }); +}; diff --git a/packages/lib/types/font.d.ts b/packages/lib/types/font.d.ts new file mode 100644 index 000000000..1764f41e0 --- /dev/null +++ b/packages/lib/types/font.d.ts @@ -0,0 +1 @@ +declare module '*.ttf'; diff --git a/packages/prettier-config/package.json b/packages/prettier-config/package.json index d66524a29..ee5d28dad 100644 --- a/packages/prettier-config/package.json +++ b/packages/prettier-config/package.json @@ -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", diff --git a/packages/prisma/migrations/20230829165148_user_sharing_link/migration.sql b/packages/prisma/migrations/20230829165148_user_sharing_link/migration.sql new file mode 100644 index 000000000..793d125b3 --- /dev/null +++ b/packages/prisma/migrations/20230829165148_user_sharing_link/migration.sql @@ -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; diff --git a/packages/prisma/migrations/20230829180915_recipient_id_for_user_id/migration.sql b/packages/prisma/migrations/20230829180915_recipient_id_for_user_id/migration.sql new file mode 100644 index 000000000..51008f8ec --- /dev/null +++ b/packages/prisma/migrations/20230829180915_recipient_id_for_user_id/migration.sql @@ -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; diff --git a/packages/prisma/migrations/20230920060743_update_share_link_schema/migration.sql b/packages/prisma/migrations/20230920060743_update_share_link_schema/migration.sql new file mode 100644 index 000000000..dd25e5ff8 --- /dev/null +++ b/packages/prisma/migrations/20230920060743_update_share_link_schema/migration.sql @@ -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; diff --git a/packages/prisma/package.json b/packages/prisma/package.json index 958bcde17..1b12a18a4 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -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", diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 6576da8e2..c4f034ba2 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -101,13 +101,14 @@ 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? @@ -209,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]) +} diff --git a/packages/tailwind-config/package.json b/packages/tailwind-config/package.json index 7f10b8d76..7a2e63139 100644 --- a/packages/tailwind-config/package.json +++ b/packages/tailwind-config/package.json @@ -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", diff --git a/packages/trpc/package.json b/packages/trpc/package.json index c3eb49088..bb7c81b9b 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -5,6 +5,7 @@ "types": "./index.ts", "license": "MIT", "scripts": { + "clean": "rimraf node_modules" }, "dependencies": { "@documenso/lib": "*", diff --git a/packages/trpc/server/router.ts b/packages/trpc/server/router.ts index 61dfcb40f..6f9fc7660 100644 --- a/packages/trpc/server/router.ts +++ b/packages/trpc/server/router.ts @@ -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; diff --git a/packages/trpc/server/share-link-router/router.ts b/packages/trpc/server/share-link-router/router.ts new file mode 100644 index 000000000..00dd2a8ef --- /dev/null +++ b/packages/trpc/server/share-link-router/router.ts @@ -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.', + }); + } + }), +}); diff --git a/packages/trpc/server/share-link-router/schema.ts b/packages/trpc/server/share-link-router/schema.ts new file mode 100644 index 000000000..9ea599042 --- /dev/null +++ b/packages/trpc/server/share-link-router/schema.ts @@ -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 +>; diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json index 881ec485c..b22d7d2ed 100644 --- a/packages/tsconfig/package.json +++ b/packages/tsconfig/package.json @@ -3,6 +3,9 @@ "version": "0.0.0", "license": "MIT", "private": true, + "scripts": { + "clean": "rimraf node_modules" + }, "files": [ "base.json", "nextjs.json", diff --git a/packages/ui/icons/signature.tsx b/packages/ui/icons/signature.tsx index b91998bb5..0d172bab4 100644 --- a/packages/ui/icons/signature.tsx +++ b/packages/ui/icons/signature.tsx @@ -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 ( - - - - ); -}; +export const SignatureIcon: LucideIcon = forwardRef( + ( + { size = 24, color = 'currentColor', strokeWidth = 1.33, absoluteStrokeWidth, ...props }, + ref, + ) => { + return ( + + + + ); + }, +); diff --git a/packages/ui/package.json b/packages/ui/package.json index 5a60f6c07..53751f5a7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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", diff --git a/turbo.json b/turbo.json index 01b8bd487..f1556f229 100644 --- a/turbo.json +++ b/turbo.json @@ -11,6 +11,9 @@ ] }, "lint": {}, + "clean": { + "cache": false + }, "dev": { "cache": false, "persistent": true