-
Billing
+
Billing
{isMissingOrInactiveOrFreePlan && (
diff --git a/apps/web/src/app/(dashboard)/settings/password/page.tsx b/apps/web/src/app/(dashboard)/settings/password/page.tsx
index 90fcbe25d..dd344a1d1 100644
--- a/apps/web/src/app/(dashboard)/settings/password/page.tsx
+++ b/apps/web/src/app/(dashboard)/settings/password/page.tsx
@@ -1,19 +1,5 @@
-import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { redirect } from 'next/navigation';
-import { PasswordForm } from '~/components/forms/password';
-
-export default async function PasswordSettingsPage() {
- const { user } = await getRequiredServerComponentSession();
-
- return (
-
-
Password
-
-
Here you can update your password.
-
-
-
-
-
- );
+export default function PasswordSettingsPage() {
+ redirect('/settings/security');
}
diff --git a/apps/web/src/app/(dashboard)/settings/profile/page.tsx b/apps/web/src/app/(dashboard)/settings/profile/page.tsx
index 716f3c39c..cb64fb9cd 100644
--- a/apps/web/src/app/(dashboard)/settings/profile/page.tsx
+++ b/apps/web/src/app/(dashboard)/settings/profile/page.tsx
@@ -1,4 +1,4 @@
-import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { ProfileForm } from '~/components/forms/profile';
@@ -7,7 +7,7 @@ export default async function ProfileSettingsPage() {
return (
-
Profile
+
Profile
Here you can edit your personal details.
diff --git a/apps/web/src/app/(dashboard)/settings/security/page.tsx b/apps/web/src/app/(dashboard)/settings/security/page.tsx
new file mode 100644
index 000000000..9e99b73e8
--- /dev/null
+++ b/apps/web/src/app/(dashboard)/settings/security/page.tsx
@@ -0,0 +1,46 @@
+import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
+
+import { AuthenticatorApp } from '~/components/forms/2fa/authenticator-app';
+import { RecoveryCodes } from '~/components/forms/2fa/recovery-codes';
+import { PasswordForm } from '~/components/forms/password';
+
+export default async function SecuritySettingsPage() {
+ const { user } = await getRequiredServerComponentSession();
+
+ return (
+
+
Security
+
+
+ Here you can manage your password and security settings.
+
+
+
+
+
+
+
+
+
Two Factor Authentication
+
+
+ Add and manage your two factor security settings to add an extra layer of security to your
+ account!
+
+
+
+
Two-factor methods
+
+
+
+
+ {user.twoFactorEnabled && (
+
+
Recovery methods
+
+
+
+ )}
+
+ );
+}
diff --git a/apps/web/src/app/(share)/share/[slug]/opengraph/route.tsx b/apps/web/src/app/(share)/share/[slug]/opengraph/route.tsx
index 7cd7059f7..dfa715fb0 100644
--- a/apps/web/src/app/(share)/share/[slug]/opengraph/route.tsx
+++ b/apps/web/src/app/(share)/share/[slug]/opengraph/route.tsx
@@ -56,7 +56,7 @@ export async function GET(_request: Request, { params: { slug } }: SharePageOpen
return new ImageResponse(
(
-
+
{/* @ts-expect-error Lack of typing from ImageResponse */}
@@ -149,6 +149,10 @@ export async function GET(_request: Request, { params: { slug } }: SharePageOpen
weight: 600,
},
],
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS',
+ },
},
);
}
diff --git a/apps/web/src/app/(signing)/sign/[token]/form.tsx b/apps/web/src/app/(signing)/sign/[token]/form.tsx
index 1e5b916e3..ecdca8f93 100644
--- a/apps/web/src/app/(signing)/sign/[token]/form.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/form.tsx
@@ -28,6 +28,7 @@ import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
import { DATE_FORMATS } from '~/helpers/constants';
import { useRequiredSigningContext } from './provider';
+import { SignDialog } from './sign-dialog';
export type SigningFormProps = {
document: Document;
@@ -57,6 +58,7 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
const onFormSubmit = async () => {
setValidateUninsertedFields(true);
+
const isFieldsValid = validateFieldsInserted(fields);
if (!isFieldsValid) {
@@ -168,9 +170,12 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
Cancel
-
- Complete
-
+
diff --git a/apps/web/src/app/(signing)/sign/[token]/layout.tsx b/apps/web/src/app/(signing)/sign/[token]/layout.tsx
index a25c16c0d..cfec41cdf 100644
--- a/apps/web/src/app/(signing)/sign/[token]/layout.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/layout.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header';
import { NextAuthProvider } from '~/providers/next-auth';
diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx
index a1ad0b170..67e679412 100644
--- a/apps/web/src/app/(signing)/sign/[token]/page.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx
@@ -3,7 +3,7 @@ import { notFound, redirect } from 'next/navigation';
import { match } from 'ts-pattern';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
-import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
diff --git a/apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx b/apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx
new file mode 100644
index 000000000..0ce750a39
--- /dev/null
+++ b/apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx
@@ -0,0 +1,77 @@
+import { useState } from 'react';
+
+import { Document, Field } from '@documenso/prisma/client';
+import { Button } from '@documenso/ui/primitives/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogTrigger,
+} from '@documenso/ui/primitives/dialog';
+
+export type SignDialogProps = {
+ isSubmitting: boolean;
+ document: Document;
+ fields: Field[];
+ onSignatureComplete: () => void | Promise
;
+};
+
+export const SignDialog = ({
+ isSubmitting,
+ document,
+ fields,
+ onSignatureComplete,
+}: SignDialogProps) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const isComplete = fields.every((field) => field.inserted);
+
+ return (
+
+
+
+ Complete
+
+
+
+
+
Sign Document
+
+ You are about to finish signing "{document.title}". Are you sure?
+
+
+
+
+
+ {
+ setShowDialog(false);
+ }}
+ >
+ Cancel
+
+
+
+ Sign
+
+
+
+
+
+ );
+};
diff --git a/apps/web/src/app/(unauthenticated)/forgot-password/page.tsx b/apps/web/src/app/(unauthenticated)/forgot-password/page.tsx
index 4f0617f7c..20ecddf4d 100644
--- a/apps/web/src/app/(unauthenticated)/forgot-password/page.tsx
+++ b/apps/web/src/app/(unauthenticated)/forgot-password/page.tsx
@@ -5,7 +5,7 @@ import { ForgotPasswordForm } from '~/components/forms/forgot-password';
export default function ForgotPasswordPage() {
return (
-
Forgotten your password?
+
Forgot your password?
No worries, it happens! Enter your email and we'll email you a special link to reset your
diff --git a/apps/web/src/app/(unauthenticated)/signin/page.tsx b/apps/web/src/app/(unauthenticated)/signin/page.tsx
index 868b0471d..a4890d849 100644
--- a/apps/web/src/app/(unauthenticated)/signin/page.tsx
+++ b/apps/web/src/app/(unauthenticated)/signin/page.tsx
@@ -25,7 +25,7 @@ export default function SignInPage() {
href="/forgot-password"
className="text-muted-foreground text-sm duration-200 hover:opacity-70"
>
- Forgotten your password?
+ Forgot your password?
diff --git a/apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx b/apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx
new file mode 100644
index 000000000..f671fb101
--- /dev/null
+++ b/apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx
@@ -0,0 +1,97 @@
+import Link from 'next/link';
+
+import { AlertTriangle, CheckCircle2, XCircle, XOctagon } from 'lucide-react';
+
+import { verifyEmail } from '@documenso/lib/server-only/user/verify-email';
+import { Button } from '@documenso/ui/primitives/button';
+
+export type PageProps = {
+ params: {
+ token: string;
+ };
+};
+
+export default async function VerifyEmailPage({ params: { token } }: PageProps) {
+ if (!token) {
+ return (
+
+
+
+
+
+
No token provided
+
+ It seems that there is no token provided. Please check your email and try again.
+
+
+ );
+ }
+
+ const verified = await verifyEmail({ token });
+
+ if (verified === null) {
+ return (
+
+
+
+
+
Something went wrong
+
+
+ We were unable to verify your email. If your email is not verified already, please try
+ again.
+
+
+
+ Go back home
+
+
+
+ );
+ }
+
+ if (!verified) {
+ return (
+
+
+
+
+
+
+
Your token has expired!
+
+
+ It seems that the provided token has expired. We've just sent you another token, please
+ check your email and try again.
+
+
+
+ Go back home
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
Email Confirmed!
+
+
+ Your email has been successfully confirmed! You can now use all features of Documenso.
+
+
+
+ Go back home
+
+
+
+ );
+}
diff --git a/apps/web/src/app/(unauthenticated)/verify-email/page.tsx b/apps/web/src/app/(unauthenticated)/verify-email/page.tsx
new file mode 100644
index 000000000..04202d19b
--- /dev/null
+++ b/apps/web/src/app/(unauthenticated)/verify-email/page.tsx
@@ -0,0 +1,28 @@
+import Link from 'next/link';
+
+import { XCircle } from 'lucide-react';
+
+import { Button } from '@documenso/ui/primitives/button';
+
+export default function EmailVerificationWithoutTokenPage() {
+ return (
+
+
+
+
+
+
+
Uh oh! Looks like you're missing a token
+
+
+ It seems that there is no token provided, if you are trying to verify your email please
+ follow the link in your email.
+
+
+
+ Go back home
+
+
+
+ );
+}
diff --git a/apps/web/src/app/not-found.tsx b/apps/web/src/app/not-found.tsx
index f580655af..76017c121 100644
--- a/apps/web/src/app/not-found.tsx
+++ b/apps/web/src/app/not-found.tsx
@@ -1,6 +1,6 @@
import Link from 'next/link';
-import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { Button } from '@documenso/ui/primitives/button';
import NotFoundPartial from '~/components/partials/not-found';
diff --git a/apps/web/src/components/(dashboard)/common/command-menu.tsx b/apps/web/src/components/(dashboard)/common/command-menu.tsx
new file mode 100644
index 000000000..cc597cfeb
--- /dev/null
+++ b/apps/web/src/components/(dashboard)/common/command-menu.tsx
@@ -0,0 +1,133 @@
+'use client';
+
+import { useCallback, useMemo, useState } from 'react';
+
+import { useRouter } from 'next/navigation';
+
+import { Monitor, Moon, Sun } from 'lucide-react';
+import { useTheme } from 'next-themes';
+import { useHotkeys } from 'react-hotkeys-hook';
+
+import {
+ DOCUMENTS_PAGE_SHORTCUT,
+ SETTINGS_PAGE_SHORTCUT,
+} from '@documenso/lib/constants/keyboard-shortcuts';
+import {
+ CommandDialog,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandShortcut,
+} from '@documenso/ui/primitives/command';
+
+const DOCUMENTS_PAGES = [
+ {
+ label: 'All documents',
+ path: '/documents?status=ALL',
+ shortcut: DOCUMENTS_PAGE_SHORTCUT.replace('+', ''),
+ },
+ { label: 'Draft documents', path: '/documents?status=DRAFT' },
+ { label: 'Completed documents', path: '/documents?status=COMPLETED' },
+ { label: 'Pending documents', path: '/documents?status=PENDING' },
+ { label: 'Inbox documents', path: '/documents?status=INBOX' },
+];
+
+const SETTINGS_PAGES = [
+ { label: 'Settings', path: '/settings', shortcut: SETTINGS_PAGE_SHORTCUT.replace('+', '') },
+ { label: 'Profile', path: '/settings/profile' },
+ { label: 'Password', path: '/settings/password' },
+];
+
+export function CommandMenu() {
+ const { setTheme } = useTheme();
+ const { push } = useRouter();
+ const [open, setOpen] = useState(false);
+ const [search, setSearch] = useState('');
+ const [pages, setPages] = useState([]);
+ const currentPage = pages[pages.length - 1];
+
+ const toggleOpen = () => {
+ setOpen((open) => !open);
+ };
+
+ const goToSettings = useCallback(() => push(SETTINGS_PAGES[0].path), [push]);
+ const goToDocuments = useCallback(() => push(DOCUMENTS_PAGES[0].path), [push]);
+
+ useHotkeys('ctrl+k', toggleOpen);
+ useHotkeys(SETTINGS_PAGE_SHORTCUT, goToSettings);
+ useHotkeys(DOCUMENTS_PAGE_SHORTCUT, goToDocuments);
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ // Escape goes to previous page
+ // Backspace goes to previous page when search is empty
+ if (e.key === 'Escape' || (e.key === 'Backspace' && !search)) {
+ e.preventDefault();
+ if (currentPage === undefined) {
+ setOpen(false);
+ }
+ setPages((pages) => pages.slice(0, -1));
+ }
+ };
+
+ return (
+
+
+
+ No results found.
+ {!currentPage && (
+ <>
+
+
+
+
+
+
+
+ setPages([...pages, 'theme'])}>Change theme
+
+ >
+ )}
+ {currentPage === 'theme' && }
+
+
+ );
+}
+
+const Commands = ({
+ push,
+ pages,
+}: {
+ push: (_path: string) => void;
+ pages: { label: string; path: string; shortcut?: string }[];
+}) => {
+ return pages.map((page) => (
+ push(page.path)}>
+ {page.label}
+ {page.shortcut && {page.shortcut} }
+
+ ));
+};
+
+const ThemeCommands = ({ setTheme }: { setTheme: (_theme: string) => void }) => {
+ const THEMES = useMemo(
+ () => [
+ { label: 'Light Mode', theme: 'light', icon: Sun },
+ { label: 'Dark Mode', theme: 'dark', icon: Moon },
+ { label: 'System Theme', theme: 'system', icon: Monitor },
+ ],
+ [],
+ );
+
+ return THEMES.map((theme) => (
+ setTheme(theme.theme)}>
+
+ {theme.label}
+
+ ));
+};
diff --git a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx
index d699dea4b..99761a0d3 100644
--- a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx
+++ b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx
@@ -4,7 +4,7 @@ import Link from 'next/link';
import {
CreditCard,
- Key,
+ Lock,
LogOut,
User as LucideUser,
Monitor,
@@ -87,9 +87,9 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
-
-
- Password
+
+
+ Security
diff --git a/apps/web/src/components/(dashboard)/layout/verify-email-banner.tsx b/apps/web/src/components/(dashboard)/layout/verify-email-banner.tsx
new file mode 100644
index 000000000..24e47c186
--- /dev/null
+++ b/apps/web/src/components/(dashboard)/layout/verify-email-banner.tsx
@@ -0,0 +1,123 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+
+import { AlertTriangle } from 'lucide-react';
+
+import { ONE_SECOND } from '@documenso/lib/constants/time';
+import { trpc } from '@documenso/trpc/react';
+import { Button } from '@documenso/ui/primitives/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogTitle,
+} from '@documenso/ui/primitives/dialog';
+import { useToast } from '@documenso/ui/primitives/use-toast';
+
+export type VerifyEmailBannerProps = {
+ email: string;
+};
+
+const RESEND_CONFIRMATION_EMAIL_TIMEOUT = 20 * ONE_SECOND;
+
+export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
+ const { toast } = useToast();
+ const [isOpen, setIsOpen] = useState(false);
+
+ const [isButtonDisabled, setIsButtonDisabled] = useState(false);
+
+ const { mutateAsync: sendConfirmationEmail, isLoading } =
+ trpc.profile.sendConfirmationEmail.useMutation();
+
+ const onResendConfirmationEmail = async () => {
+ try {
+ setIsButtonDisabled(true);
+
+ await sendConfirmationEmail({ email: email });
+
+ toast({
+ title: 'Success',
+ description: 'Verification email sent successfully.',
+ });
+
+ setIsOpen(false);
+ setTimeout(() => setIsButtonDisabled(false), RESEND_CONFIRMATION_EMAIL_TIMEOUT);
+ } catch (err) {
+ setIsButtonDisabled(false);
+
+ toast({
+ title: 'Error',
+ description: 'Something went wrong while sending the confirmation email.',
+ variant: 'destructive',
+ });
+ }
+ };
+
+ useEffect(() => {
+ // Check localStorage to see if we've recently automatically displayed the dialog
+ // if it was within the past 24 hours, don't show it again
+ // otherwise, show it again and update the localStorage timestamp
+ const emailVerificationDialogLastShown = localStorage.getItem(
+ 'emailVerificationDialogLastShown',
+ );
+
+ if (emailVerificationDialogLastShown) {
+ const lastShownTimestamp = parseInt(emailVerificationDialogLastShown);
+
+ if (Date.now() - lastShownTimestamp < 24 * 60 * 60 * 1000) {
+ return;
+ }
+ }
+
+ setIsOpen(true);
+
+ localStorage.setItem('emailVerificationDialogLastShown', Date.now().toString());
+ }, []);
+
+ return (
+ <>
+
+
+
+
+ Verify your email address to unlock all features.
+
+
+
+ setIsOpen(true)}
+ size="sm"
+ >
+ {isButtonDisabled ? 'Verification Email Sent' : 'Verify Now'}
+
+
+
+
+
+
+
+ Verify your email address
+
+
+ We've sent a confirmation email to {email} . Please check your inbox and
+ click the link in the email to verify your account.
+
+
+
+
+ {isLoading ? 'Sending...' : 'Resend Confirmation Email'}
+
+
+
+
+ >
+ );
+};
diff --git a/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx b/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx
index 901c6a5ae..f4b2aae5e 100644
--- a/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx
+++ b/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx
@@ -5,7 +5,7 @@ import { HTMLAttributes } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
-import { CreditCard, Key, User } from 'lucide-react';
+import { CreditCard, Lock, User } from 'lucide-react';
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
import { cn } from '@documenso/ui/lib/utils';
@@ -35,16 +35,16 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
-
+
-
- Password
+
+ Security
diff --git a/apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx b/apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx
index ffe2b0d80..28ffc960f 100644
--- a/apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx
+++ b/apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx
@@ -5,7 +5,7 @@ import { HTMLAttributes } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
-import { CreditCard, Key, User } from 'lucide-react';
+import { CreditCard, Lock, User } from 'lucide-react';
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
import { cn } from '@documenso/ui/lib/utils';
@@ -38,16 +38,16 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
-
+
-
- Password
+
+ Security
diff --git a/apps/web/src/components/forms/2fa/authenticator-app.tsx b/apps/web/src/components/forms/2fa/authenticator-app.tsx
new file mode 100644
index 000000000..1d164bd22
--- /dev/null
+++ b/apps/web/src/components/forms/2fa/authenticator-app.tsx
@@ -0,0 +1,58 @@
+'use client';
+
+import { useState } from 'react';
+
+import { Button } from '@documenso/ui/primitives/button';
+
+import { DisableAuthenticatorAppDialog } from './disable-authenticator-app-dialog';
+import { EnableAuthenticatorAppDialog } from './enable-authenticator-app-dialog';
+
+type AuthenticatorAppProps = {
+ isTwoFactorEnabled: boolean;
+};
+
+export const AuthenticatorApp = ({ isTwoFactorEnabled }: AuthenticatorAppProps) => {
+ const [modalState, setModalState] = useState<'enable' | 'disable' | null>(null);
+
+ const isEnableDialogOpen = modalState === 'enable';
+ const isDisableDialogOpen = modalState === 'disable';
+
+ return (
+ <>
+
+
+
Authenticator app
+
+
+ Create one-time passwords that serve as a secondary authentication method for confirming
+ your identity when requested during the sign-in process.
+
+
+
+
+ {isTwoFactorEnabled ? (
+ setModalState('disable')} size="sm">
+ Disable 2FA
+
+ ) : (
+ setModalState('enable')} size="sm">
+ Enable 2FA
+
+ )}
+
+
+
+ !open && setModalState(null)}
+ />
+
+ !open && setModalState(null)}
+ />
+ >
+ );
+};
diff --git a/apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx b/apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx
new file mode 100644
index 000000000..eac574181
--- /dev/null
+++ b/apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx
@@ -0,0 +1,161 @@
+import { useRouter } from 'next/navigation';
+
+import { zodResolver } from '@hookform/resolvers/zod';
+import { flushSync } from 'react-dom';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+
+import { trpc } from '@documenso/trpc/react';
+import { Button } from '@documenso/ui/primitives/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@documenso/ui/primitives/dialog';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@documenso/ui/primitives/form/form';
+import { Input } from '@documenso/ui/primitives/input';
+import { useToast } from '@documenso/ui/primitives/use-toast';
+
+export const ZDisableTwoFactorAuthenticationForm = z.object({
+ password: z.string().min(6).max(72),
+ backupCode: z.string(),
+});
+
+export type TDisableTwoFactorAuthenticationForm = z.infer<
+ typeof ZDisableTwoFactorAuthenticationForm
+>;
+
+export type DisableAuthenticatorAppDialogProps = {
+ open: boolean;
+ onOpenChange: (_open: boolean) => void;
+};
+
+export const DisableAuthenticatorAppDialog = ({
+ open,
+ onOpenChange,
+}: DisableAuthenticatorAppDialogProps) => {
+ const router = useRouter();
+ const { toast } = useToast();
+
+ const { mutateAsync: disableTwoFactorAuthentication } =
+ trpc.twoFactorAuthentication.disable.useMutation();
+
+ const disableTwoFactorAuthenticationForm = useForm({
+ defaultValues: {
+ password: '',
+ backupCode: '',
+ },
+ resolver: zodResolver(ZDisableTwoFactorAuthenticationForm),
+ });
+
+ const { isSubmitting: isDisableTwoFactorAuthenticationSubmitting } =
+ disableTwoFactorAuthenticationForm.formState;
+
+ const onDisableTwoFactorAuthenticationFormSubmit = async ({
+ password,
+ backupCode,
+ }: TDisableTwoFactorAuthenticationForm) => {
+ try {
+ await disableTwoFactorAuthentication({ password, backupCode });
+
+ toast({
+ title: 'Two-factor authentication disabled',
+ description:
+ 'Two-factor authentication has been disabled for your account. You will no longer be required to enter a code from your authenticator app when signing in.',
+ });
+
+ flushSync(() => {
+ onOpenChange(false);
+ });
+
+ router.refresh();
+ } catch (_err) {
+ toast({
+ title: 'Unable to disable two-factor authentication',
+ description:
+ 'We were unable to disable two-factor authentication for your account. Please ensure that you have entered your password and backup code correctly and try again.',
+ variant: 'destructive',
+ });
+ }
+ };
+
+ return (
+
+
+
+ Disable Authenticator App
+
+
+ To disable the Authenticator App for your account, please enter your password and a
+ backup code. If you do not have a backup code available, please contact support.
+
+
+
+
+
+
+
+ );
+};
diff --git a/apps/web/src/components/forms/2fa/enable-authenticator-app-dialog.tsx b/apps/web/src/components/forms/2fa/enable-authenticator-app-dialog.tsx
new file mode 100644
index 000000000..8bf835ef5
--- /dev/null
+++ b/apps/web/src/components/forms/2fa/enable-authenticator-app-dialog.tsx
@@ -0,0 +1,283 @@
+import { useMemo } from 'react';
+
+import { useRouter } from 'next/navigation';
+
+import { zodResolver } from '@hookform/resolvers/zod';
+import { flushSync } from 'react-dom';
+import { useForm } from 'react-hook-form';
+import { match } from 'ts-pattern';
+import { renderSVG } from 'uqr';
+import { z } from 'zod';
+
+import { trpc } from '@documenso/trpc/react';
+import { Button } from '@documenso/ui/primitives/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@documenso/ui/primitives/dialog';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@documenso/ui/primitives/form/form';
+import { Input } from '@documenso/ui/primitives/input';
+import { useToast } from '@documenso/ui/primitives/use-toast';
+
+import { RecoveryCodeList } from './recovery-code-list';
+
+export const ZSetupTwoFactorAuthenticationForm = z.object({
+ password: z.string().min(6).max(72),
+});
+
+export type TSetupTwoFactorAuthenticationForm = z.infer;
+
+export const ZEnableTwoFactorAuthenticationForm = z.object({
+ token: z.string(),
+});
+
+export type TEnableTwoFactorAuthenticationForm = z.infer;
+
+export type EnableAuthenticatorAppDialogProps = {
+ open: boolean;
+ onOpenChange: (_open: boolean) => void;
+};
+
+export const EnableAuthenticatorAppDialog = ({
+ open,
+ onOpenChange,
+}: EnableAuthenticatorAppDialogProps) => {
+ const router = useRouter();
+ const { toast } = useToast();
+
+ const { mutateAsync: setupTwoFactorAuthentication, data: setupTwoFactorAuthenticationData } =
+ trpc.twoFactorAuthentication.setup.useMutation();
+
+ const { mutateAsync: enableTwoFactorAuthentication, data: enableTwoFactorAuthenticationData } =
+ trpc.twoFactorAuthentication.enable.useMutation();
+
+ const setupTwoFactorAuthenticationForm = useForm({
+ defaultValues: {
+ password: '',
+ },
+ resolver: zodResolver(ZSetupTwoFactorAuthenticationForm),
+ });
+
+ const { isSubmitting: isSetupTwoFactorAuthenticationSubmitting } =
+ setupTwoFactorAuthenticationForm.formState;
+
+ const enableTwoFactorAuthenticationForm = useForm({
+ defaultValues: {
+ token: '',
+ },
+ resolver: zodResolver(ZEnableTwoFactorAuthenticationForm),
+ });
+
+ const { isSubmitting: isEnableTwoFactorAuthenticationSubmitting } =
+ enableTwoFactorAuthenticationForm.formState;
+
+ const step = useMemo(() => {
+ if (!setupTwoFactorAuthenticationData || isSetupTwoFactorAuthenticationSubmitting) {
+ return 'setup';
+ }
+
+ if (!enableTwoFactorAuthenticationData || isEnableTwoFactorAuthenticationSubmitting) {
+ return 'enable';
+ }
+
+ return 'view';
+ }, [
+ setupTwoFactorAuthenticationData,
+ isSetupTwoFactorAuthenticationSubmitting,
+ enableTwoFactorAuthenticationData,
+ isEnableTwoFactorAuthenticationSubmitting,
+ ]);
+
+ const onSetupTwoFactorAuthenticationFormSubmit = async ({
+ password,
+ }: TSetupTwoFactorAuthenticationForm) => {
+ try {
+ await setupTwoFactorAuthentication({ password });
+ } catch (_err) {
+ toast({
+ title: 'Unable to setup two-factor authentication',
+ description:
+ 'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your password correctly and try again.',
+ variant: 'destructive',
+ });
+ }
+ };
+
+ const onEnableTwoFactorAuthenticationFormSubmit = async ({
+ token,
+ }: TEnableTwoFactorAuthenticationForm) => {
+ try {
+ await enableTwoFactorAuthentication({ code: token });
+
+ toast({
+ title: 'Two-factor authentication enabled',
+ description:
+ 'Two-factor authentication has been enabled for your account. You will now be required to enter a code from your authenticator app when signing in.',
+ });
+ } catch (_err) {
+ toast({
+ title: 'Unable to setup two-factor authentication',
+ description:
+ 'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your password correctly and try again.',
+ variant: 'destructive',
+ });
+ }
+ };
+
+ const onCompleteClick = () => {
+ flushSync(() => {
+ onOpenChange(false);
+ });
+
+ router.refresh();
+ };
+
+ return (
+
+
+
+ Enable Authenticator App
+
+ {step === 'setup' && (
+
+ To enable two-factor authentication, please enter your password below.
+
+ )}
+
+ {step === 'view' && (
+
+ Your recovery codes are listed below. Please store them in a safe place.
+
+ )}
+
+
+ {match(step)
+ .with('setup', () => {
+ return (
+
+
+ );
+ })
+ .with('enable', () => (
+
+
+ ))
+ .with('view', () => (
+
+ {enableTwoFactorAuthenticationData?.recoveryCodes && (
+
+ )}
+
+
+ onCompleteClick()}>
+ Complete
+
+
+
+ ))
+ .exhaustive()}
+
+
+ );
+};
diff --git a/apps/web/src/components/forms/2fa/recovery-code-list.tsx b/apps/web/src/components/forms/2fa/recovery-code-list.tsx
new file mode 100644
index 000000000..d2efb0b4b
--- /dev/null
+++ b/apps/web/src/components/forms/2fa/recovery-code-list.tsx
@@ -0,0 +1,57 @@
+import { Copy } from 'lucide-react';
+
+import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
+import { useToast } from '@documenso/ui/primitives/use-toast';
+
+export type RecoveryCodeListProps = {
+ recoveryCodes: string[];
+};
+
+export const RecoveryCodeList = ({ recoveryCodes }: RecoveryCodeListProps) => {
+ const { toast } = useToast();
+ const [, copyToClipboard] = useCopyToClipboard();
+
+ const onCopyRecoveryCodeClick = async (code: string) => {
+ try {
+ const result = await copyToClipboard(code);
+
+ if (!result) {
+ throw new Error('Unable to copy recovery code');
+ }
+
+ toast({
+ title: 'Recovery code copied',
+ description: 'Your recovery code has been copied to your clipboard.',
+ });
+ } catch (_err) {
+ toast({
+ title: 'Unable to copy recovery code',
+ description:
+ 'We were unable to copy your recovery code to your clipboard. Please try again.',
+ variant: 'destructive',
+ });
+ }
+ };
+
+ return (
+
+ {recoveryCodes.map((code) => (
+
+
{code}
+
+
+ void onCopyRecoveryCodeClick(code)}
+ >
+
+
+
+
+ ))}
+
+ );
+};
diff --git a/apps/web/src/components/forms/2fa/recovery-codes.tsx b/apps/web/src/components/forms/2fa/recovery-codes.tsx
new file mode 100644
index 000000000..7e8950227
--- /dev/null
+++ b/apps/web/src/components/forms/2fa/recovery-codes.tsx
@@ -0,0 +1,43 @@
+'use client';
+
+import { useState } from 'react';
+
+import { Button } from '@documenso/ui/primitives/button';
+
+import { ViewRecoveryCodesDialog } from './view-recovery-codes-dialog';
+
+type RecoveryCodesProps = {
+ // backupCodes: string[] | null;
+ isTwoFactorEnabled: boolean;
+};
+
+export const RecoveryCodes = ({ isTwoFactorEnabled }: RecoveryCodesProps) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+ <>
+
+
+
Recovery Codes
+
+
+ Recovery codes are used to access your account in the event that you lose access to your
+ authenticator app.
+
+
+
+
+ setIsOpen(true)} disabled={!isTwoFactorEnabled} size="sm">
+ View Codes
+
+
+
+
+
+ >
+ );
+};
diff --git a/apps/web/src/components/forms/2fa/view-recovery-codes-dialog.tsx b/apps/web/src/components/forms/2fa/view-recovery-codes-dialog.tsx
new file mode 100644
index 000000000..6275f16d6
--- /dev/null
+++ b/apps/web/src/components/forms/2fa/view-recovery-codes-dialog.tsx
@@ -0,0 +1,151 @@
+import { useMemo } from 'react';
+
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { match } from 'ts-pattern';
+import { z } from 'zod';
+
+import { trpc } from '@documenso/trpc/react';
+import { Button } from '@documenso/ui/primitives/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@documenso/ui/primitives/dialog';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@documenso/ui/primitives/form/form';
+import { Input } from '@documenso/ui/primitives/input';
+import { useToast } from '@documenso/ui/primitives/use-toast';
+
+import { RecoveryCodeList } from './recovery-code-list';
+
+export const ZViewRecoveryCodesForm = z.object({
+ password: z.string().min(6).max(72),
+});
+
+export type TViewRecoveryCodesForm = z.infer;
+
+export type ViewRecoveryCodesDialogProps = {
+ open: boolean;
+ onOpenChange: (_open: boolean) => void;
+};
+
+export const ViewRecoveryCodesDialog = ({ open, onOpenChange }: ViewRecoveryCodesDialogProps) => {
+ const { toast } = useToast();
+
+ const { mutateAsync: viewRecoveryCodes, data: viewRecoveryCodesData } =
+ trpc.twoFactorAuthentication.viewRecoveryCodes.useMutation();
+
+ const viewRecoveryCodesForm = useForm({
+ defaultValues: {
+ password: '',
+ },
+ resolver: zodResolver(ZViewRecoveryCodesForm),
+ });
+
+ const { isSubmitting: isViewRecoveryCodesSubmitting } = viewRecoveryCodesForm.formState;
+
+ const step = useMemo(() => {
+ if (!viewRecoveryCodesData || isViewRecoveryCodesSubmitting) {
+ return 'authenticate';
+ }
+
+ return 'view';
+ }, [viewRecoveryCodesData, isViewRecoveryCodesSubmitting]);
+
+ const onViewRecoveryCodesFormSubmit = async ({ password }: TViewRecoveryCodesForm) => {
+ try {
+ await viewRecoveryCodes({ password });
+ } catch (_err) {
+ toast({
+ title: 'Unable to view recovery codes',
+ description:
+ 'We were unable to view your recovery codes. Please ensure that you have entered your password correctly and try again.',
+ variant: 'destructive',
+ });
+ }
+ };
+
+ return (
+
+
+
+ View Recovery Codes
+
+ {step === 'authenticate' && (
+
+ To view your recovery codes, please enter your password below.
+
+ )}
+
+ {step === 'view' && (
+
+ Your recovery codes are listed below. Please store them in a safe place.
+
+ )}
+
+
+ {match(step)
+ .with('authenticate', () => {
+ return (
+
+
+ );
+ })
+ .with('view', () => (
+
+ {viewRecoveryCodesData?.recoveryCodes && (
+
+ )}
+
+
+ onOpenChange(false)}>Complete
+
+
+ ))
+ .exhaustive()}
+
+
+ );
+};
diff --git a/apps/web/src/components/forms/edit-document/add-fields.action.ts b/apps/web/src/components/forms/edit-document/add-fields.action.ts
index c07758b9f..edc5e7e39 100644
--- a/apps/web/src/components/forms/edit-document/add-fields.action.ts
+++ b/apps/web/src/components/forms/edit-document/add-fields.action.ts
@@ -1,8 +1,8 @@
'use server';
-import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
-import { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
+import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
export type AddFieldsActionInput = TAddFieldsFormSchema & {
documentId: number;
diff --git a/apps/web/src/components/forms/edit-document/add-signers.action.ts b/apps/web/src/components/forms/edit-document/add-signers.action.ts
index 05151498a..c36d51c41 100644
--- a/apps/web/src/components/forms/edit-document/add-signers.action.ts
+++ b/apps/web/src/components/forms/edit-document/add-signers.action.ts
@@ -1,8 +1,8 @@
'use server';
-import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
-import { TAddSignersFormSchema } from '@documenso/ui/primitives/document-flow/add-signers.types';
+import type { TAddSignersFormSchema } from '@documenso/ui/primitives/document-flow/add-signers.types';
export type AddSignersActionInput = TAddSignersFormSchema & {
documentId: number;
diff --git a/apps/web/src/components/forms/edit-document/add-subject.action.ts b/apps/web/src/components/forms/edit-document/add-subject.action.ts
index 8fe37fecc..56d6f694d 100644
--- a/apps/web/src/components/forms/edit-document/add-subject.action.ts
+++ b/apps/web/src/components/forms/edit-document/add-subject.action.ts
@@ -1,9 +1,9 @@
'use server';
-import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-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';
+import type { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
export type CompleteDocumentActionInput = TAddSubjectFormSchema & {
documentId: number;
diff --git a/apps/web/src/components/forms/signin.tsx b/apps/web/src/components/forms/signin.tsx
index 43801038d..0d7dd723f 100644
--- a/apps/web/src/components/forms/signin.tsx
+++ b/apps/web/src/components/forms/signin.tsx
@@ -3,7 +3,6 @@
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
-import { Eye, EyeOff } from 'lucide-react';
import { signIn } from 'next-auth/react';
import { useForm } from 'react-hook-form';
import { FcGoogle } from 'react-icons/fc';
@@ -12,23 +11,30 @@ import { z } from 'zod';
import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
-import { Input } from '@documenso/ui/primitives/input';
+import { Input, PasswordInput } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
-const ERROR_MESSAGES = {
+const ERROR_MESSAGES: Partial> = {
[ErrorCode.CREDENTIALS_NOT_FOUND]: 'The email or password provided is incorrect',
[ErrorCode.INCORRECT_EMAIL_PASSWORD]: 'The email or password provided is incorrect',
[ErrorCode.USER_MISSING_PASSWORD]:
'This account appears to be using a social login method, please sign in using that method',
+ [ErrorCode.INCORRECT_TWO_FACTOR_CODE]: 'The two-factor authentication code provided is incorrect',
+ [ErrorCode.INCORRECT_TWO_FACTOR_BACKUP_CODE]: 'The backup code provided is incorrect',
};
+const TwoFactorEnabledErrorCode = ErrorCode.TWO_FACTOR_MISSING_CREDENTIALS;
+
const LOGIN_REDIRECT_PATH = '/documents';
export const ZSignInFormSchema = z.object({
email: z.string().email().min(1),
password: z.string().min(6).max(72),
+ totpCode: z.string().trim().optional(),
+ backupCode: z.string().trim().optional(),
});
export type TSignInFormSchema = z.infer;
@@ -39,33 +45,84 @@ export type SignInFormProps = {
export const SignInForm = ({ className }: SignInFormProps) => {
const { toast } = useToast();
- const [showPassword, setShowPassword] = useState(false);
+ const [isTwoFactorAuthenticationDialogOpen, setIsTwoFactorAuthenticationDialogOpen] =
+ useState(false);
+
+ const [twoFactorAuthenticationMethod, setTwoFactorAuthenticationMethod] = useState<
+ 'totp' | 'backup'
+ >('totp');
const {
register,
handleSubmit,
+ setValue,
formState: { errors, isSubmitting },
} = useForm({
values: {
email: '',
password: '',
+ totpCode: '',
+ backupCode: '',
},
resolver: zodResolver(ZSignInFormSchema),
});
- const onFormSubmit = async ({ email, password }: TSignInFormSchema) => {
+ const onCloseTwoFactorAuthenticationDialog = () => {
+ setValue('totpCode', '');
+ setValue('backupCode', '');
+
+ setIsTwoFactorAuthenticationDialogOpen(false);
+ };
+
+ const onToggleTwoFactorAuthenticationMethodClick = () => {
+ const method = twoFactorAuthenticationMethod === 'totp' ? 'backup' : 'totp';
+
+ if (method === 'totp') {
+ setValue('backupCode', '');
+ }
+
+ if (method === 'backup') {
+ setValue('totpCode', '');
+ }
+
+ setTwoFactorAuthenticationMethod(method);
+ };
+
+ const onFormSubmit = async ({ email, password, totpCode, backupCode }: TSignInFormSchema) => {
try {
- const result = await signIn('credentials', {
+ const credentials: Record = {
email,
password,
+ };
+
+ if (totpCode) {
+ credentials.totpCode = totpCode;
+ }
+
+ if (backupCode) {
+ credentials.backupCode = backupCode;
+ }
+
+ const result = await signIn('credentials', {
+ ...credentials,
+
callbackUrl: LOGIN_REDIRECT_PATH,
redirect: false,
});
if (result?.error && isErrorCode(result.error)) {
+ if (result.error === TwoFactorEnabledErrorCode) {
+ setIsTwoFactorAuthenticationDialogOpen(true);
+
+ return;
+ }
+
+ const errorMessage = ERROR_MESSAGES[result.error];
+
toast({
variant: 'destructive',
- description: ERROR_MESSAGES[result.error],
+ title: 'Unable to sign in',
+ description: errorMessage ?? 'An unknown error occurred',
});
return;
@@ -118,31 +175,14 @@ export const SignInForm = ({ className }: SignInFormProps) => {
Password
-
-
-
- setShowPassword((show) => !show)}
- >
- {showPassword ? (
-
- ) : (
-
- )}
-
-
+
@@ -173,6 +213,67 @@ export const SignInForm = ({ className }: SignInFormProps) => {
Google
+
+
+
+
+ Two-Factor Authentication
+
+
+
+
+
);
};
diff --git a/docker/Dockerfile b/docker/Dockerfile
index a50726eff..ecdd3b91b 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,53 +1,86 @@
+###########################
+# BASE CONTAINER #
+###########################
FROM node:18-alpine AS base
-# Install dependencies only when needed
-FROM base AS production_deps
-WORKDIR /app
-
-# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
-RUN apk add --no-cache libc6-compat
-
-# Copy our current monorepo
-COPY . .
-
-RUN npm ci --production
-
-# Install dependencies only when needed
+###########################
+# BUILDER CONTAINER #
+###########################
FROM base AS builder
-WORKDIR /app
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
+RUN apk add --no-cache jq
+WORKDIR /app
-# Copy our current monorepo
COPY . .
+RUN TURBO_VERSION="$(npm list --package-lock-only --json turbo | jq -r '.dependencies.turbo.version')"
+RUN npm install -g "turbo@$TURBO_VERSION"
+
+# Outputs to the /out folder
+# source: https://turbo.build/repo/docs/reference/command-line-reference/prune#--docker
+RUN turbo prune --scope=@documenso/web --docker
+
+###########################
+# INSTALLER CONTAINER #
+###########################
+FROM base AS installer
+
+# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
+RUN apk add --no-cache libc6-compat
+RUN apk add --no-cache jq
+# Required for node_modules/aws-crt
+RUN apk add --no-cache make cmake g++
+WORKDIR /app
+
# Disable husky from installing hooks
ENV HUSKY 0
+ENV DOCKER_OUTPUT 1
+ENV NEXT_TELEMETRY_DISABLED 1
+
+# Uncomment and use build args to enable remote caching
+# ARG TURBO_TEAM
+# ENV TURBO_TEAM=$TURBO_TEAM
+# ARG TURBO_TOKEN
+# ENV TURBO_TOKEN=$TURBO_TOKEN
+
+# First install the dependencies (as they change less often)
+COPY .gitignore .gitignore
+COPY --from=builder /app/out/json/ .
+COPY --from=builder /app/out/package-lock.json ./package-lock.json
RUN npm ci
-RUN npm run build --workspaces
+# Then copy all the source code (as it changes more often)
+COPY --from=builder /app/out/full/ .
+# Finally copy the turbo.json file so that we can run turbo commands
+COPY turbo.json turbo.json
-# Production image, copy all the files and run next
+RUN TURBO_VERSION="$(npm list --package-lock-only --json turbo | jq -r '.dependencies.turbo.version')"
+RUN npm install -g "turbo@$TURBO_VERSION"
+
+RUN turbo run build --filter=@documenso/web...
+
+###########################
+# RUNNER CONTAINER #
+###########################
FROM base AS runner
+
WORKDIR /app
-ENV NODE_ENV production
-ENV NEXT_TELEMETRY_DISABLED 1
-
+# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
+USER nextjs
-COPY --from=production_deps --chown=nextjs:nodejs /app/node_modules ./node_modules
-COPY --from=production_deps --chown=nextjs:nodejs /app/package-lock.json ./package-lock.json
+COPY --from=installer /app/apps/web/next.config.js .
+COPY --from=installer /app/apps/web/package.json .
-COPY --from=builder --chown=nextjs:nodejs /app/apps/web/package.json ./package.json
-COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./public
-COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next ./.next
+# Automatically leverage output traces to reduce image size
+# https://nextjs.org/docs/advanced-features/output-file-tracing
+COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
+COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
+COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
-EXPOSE 3000
-
-ENV PORT 3000
-
-CMD ["npm", "run", "start"]
+CMD node apps/web/server.js
diff --git a/docker/compose-test.yml b/docker/compose-test.yml
new file mode 100644
index 000000000..e401aaf8f
--- /dev/null
+++ b/docker/compose-test.yml
@@ -0,0 +1,32 @@
+name: documenso_test
+services:
+ database:
+ image: postgres:15
+ environment:
+ - POSTGRES_USER=documenso
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=documenso
+ ports:
+ - 54322:5432
+
+ inbucket:
+ image: inbucket/inbucket
+ # ports:
+ # - 9000:9000
+ # - 2500:2500
+ # - 1100:1100
+
+ documenso:
+ build:
+ context: ../
+ dockerfile: docker/Dockerfile
+ depends_on:
+ - database
+ - inbucket
+ env_file:
+ - ../.env.example
+ environment:
+ - NEXT_PRIVATE_DATABASE_URL=postgres://documenso:password@database:5432/documenso
+ - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgres://documenso:password@database:5432/documenso
+ ports:
+ - 3000:3000
diff --git a/package-lock.json b/package-lock.json
index 47b528347..f4baf14bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"packages/*"
],
"dependencies": {
+ "react-hotkeys-hook": "^4.4.1",
"recharts": "^2.7.2"
},
"devDependencies": {
@@ -43,6 +44,7 @@
"contentlayer": "^0.3.4",
"framer-motion": "^10.12.8",
"lucide-react": "^0.279.0",
+ "luxon": "^3.4.0",
"micro": "^10.0.1",
"next": "14.0.0",
"next-auth": "4.24.3",
@@ -101,11 +103,13 @@
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.43.9",
+ "react-hotkeys-hook": "^4.4.1",
"react-icons": "^4.11.0",
"react-rnd": "^10.4.1",
"sharp": "0.32.5",
"ts-pattern": "^5.0.5",
"typescript": "5.2.2",
+ "uqr": "^0.1.2",
"zod": "^3.22.4"
},
"devDependencies": {
@@ -1703,11 +1707,11 @@
"link": true
},
"node_modules/@documenso/nodemailer-resend": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@documenso/nodemailer-resend/-/nodemailer-resend-1.0.0.tgz",
- "integrity": "sha512-rG+jBbBEsVJUBU6v/2hb+OQD1m3Lhn49TOzQjln73zzL1B/sZsHhYOKpNPlTX0/FafCP7P9fKerndEeIKn54Vw==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@documenso/nodemailer-resend/-/nodemailer-resend-2.0.0.tgz",
+ "integrity": "sha512-fbcRrJ9cWJ7/GQIXe8j5HKPpu5TB29jEvpG3H2OZWYlTF3kWoVPixd9wQ9uZNyilyYxqSYxJ4r4WVnAmxNseYA==",
"dependencies": {
- "resend": "^1.1.0"
+ "resend": "^2.0.0"
},
"peerDependencies": {
"nodemailer": "^6.9.3"
@@ -2851,6 +2855,465 @@
"node": ">= 10"
}
},
+ "node_modules/@noble/ciphers": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.0.tgz",
+ "integrity": "sha512-xaUaUUDWbHIFSxaQ/pIe+33VG2mfJp6N/KxKLmZr5biWdNznCAmfu24QRhX10BbVAuqOahAoyp0S4M9md6GPDw==",
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
+ "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@node-rs/argon2": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.5.2.tgz",
+ "integrity": "sha512-qq7wOSsdP2b4rXEapWNmsCjpaTGZWtp9kZmri98GYCDZqN8UJUG5zSue4XtYWWJMWKJVE/hkaIwk+BgN1ZUn0Q==",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@node-rs/argon2-android-arm-eabi": "1.5.2",
+ "@node-rs/argon2-android-arm64": "1.5.2",
+ "@node-rs/argon2-darwin-arm64": "1.5.2",
+ "@node-rs/argon2-darwin-x64": "1.5.2",
+ "@node-rs/argon2-freebsd-x64": "1.5.2",
+ "@node-rs/argon2-linux-arm-gnueabihf": "1.5.2",
+ "@node-rs/argon2-linux-arm64-gnu": "1.5.2",
+ "@node-rs/argon2-linux-arm64-musl": "1.5.2",
+ "@node-rs/argon2-linux-x64-gnu": "1.5.2",
+ "@node-rs/argon2-linux-x64-musl": "1.5.2",
+ "@node-rs/argon2-win32-arm64-msvc": "1.5.2",
+ "@node-rs/argon2-win32-ia32-msvc": "1.5.2",
+ "@node-rs/argon2-win32-x64-msvc": "1.5.2"
+ }
+ },
+ "node_modules/@node-rs/argon2-android-arm-eabi": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.5.2.tgz",
+ "integrity": "sha512-vVZec4ITr9GumAy0p8Zj8ozie362gtbZrTkLp9EqvuFZ/HrZzR09uS2IsDgm4mAstg/rc4A1gLRrHI8jDdbjkA==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-android-arm64": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.5.2.tgz",
+ "integrity": "sha512-SwhnsXyrpgtWDTwYds1WUnxLA/kVP8HVaImYwQ3Wemqj1lkzcSoIaNyjNWkyrYGqO1tVc1YUrqsbd5eCHh+3sg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-darwin-arm64": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-1.5.2.tgz",
+ "integrity": "sha512-+1ZMKiCCv2pip/o1Xg09piQru2LOIBPQ1vS4is86f55N3jjZnSfP+db5mYCSRuB0gRYqui98he7su7OGXlF4gQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-darwin-x64": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.5.2.tgz",
+ "integrity": "sha512-mQ57mORlsxpfjcEsVpiHyHCOp6Ljrz/rVNWk8ihnPWw0qt0EqF1zbHRxTEPemL1iBHL9UyXpXrKS4JKq6xMn5w==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-freebsd-x64": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.5.2.tgz",
+ "integrity": "sha512-UjKbFd3viYcpiwflkU4haEdNUMk1V2fVCJImWLWQns/hVval9BrDv5xsBwgdynbPHDlPOiWj816LBQwhWLGVWA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-arm-gnueabihf": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.5.2.tgz",
+ "integrity": "sha512-36GJjJBnVuscV9CTn8RVDeJysnmIzr6Lp7QBCDczYHi6eKFuA8udCJb4SRyJqdvIuzycKG1RL56FbcFBJYCYIA==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-arm64-gnu": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.5.2.tgz",
+ "integrity": "sha512-sE0ydb2gp6xC+5vbVz8l3paaiBbFQIB2Rwp5wx9MmKiYdTfcO5WkGeADuSgoFiTcSEz1RsHXqrdVy6j/LtSqtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-arm64-musl": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.5.2.tgz",
+ "integrity": "sha512-LhE0YHB0aJCwlbsQrwePik/KFWUc9qMriJIL5KiejK3bDoTVY4ihH587QT56JyaLvl3nBJaAV8l5yMqQdHnouA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-x64-gnu": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.5.2.tgz",
+ "integrity": "sha512-MnKLiBlyg05pxvKXe3lNgBL9El9ThD74hvVEiWH1Xk40RRrJ507NCOWXVmQ0FDq1mjTeGFxbIvk+AcoF0NSLIQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-linux-x64-musl": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.5.2.tgz",
+ "integrity": "sha512-tzLgASY0Ng2OTW7Awwl9UWzjbWx8/uD6gXcZ/k/nYGSZE5Xp8EOD2NUqHLbK6KZE3775A0R25ShpiSxCadYqkg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-win32-arm64-msvc": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.5.2.tgz",
+ "integrity": "sha512-vpTwSvv3oUXTpWZh0/HxdJ5wFMlmS7aVDwL4ATWepTZhMG4n+TO0+tVLdcPHCbg0oc6hCWBjWNPlSn9mW+YIgA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-win32-ia32-msvc": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.5.2.tgz",
+ "integrity": "sha512-KPpZR15ui7uQWQXKmtaKyUQRs4UJdXnIIfiyFLGmLWCdEKlr3MtIGFt0fdziu4BF5ZObD8Ic6QvT0VXK4OJiww==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/argon2-win32-x64-msvc": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-1.5.2.tgz",
+ "integrity": "sha512-/pGuwixJS8ZlpwhX9iM6g6JEeZYo1TtnNf8exwsOi7gxcUoTUfw5it+5GfbY/n+xRBz/DIU4bzUmXmh+7Gh0ug==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt/-/bcrypt-1.7.3.tgz",
+ "integrity": "sha512-BF6u9CBPUiyk1zU+5iwikezf+xM4MFSu5cmrrg/PLKffGgIM13ZsY6DHftcTraETB04ryasjM/5IejotH+sO5Q==",
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "optionalDependencies": {
+ "@node-rs/bcrypt-android-arm-eabi": "1.7.3",
+ "@node-rs/bcrypt-android-arm64": "1.7.3",
+ "@node-rs/bcrypt-darwin-arm64": "1.7.3",
+ "@node-rs/bcrypt-darwin-x64": "1.7.3",
+ "@node-rs/bcrypt-freebsd-x64": "1.7.3",
+ "@node-rs/bcrypt-linux-arm-gnueabihf": "1.7.3",
+ "@node-rs/bcrypt-linux-arm64-gnu": "1.7.3",
+ "@node-rs/bcrypt-linux-arm64-musl": "1.7.3",
+ "@node-rs/bcrypt-linux-x64-gnu": "1.7.3",
+ "@node-rs/bcrypt-linux-x64-musl": "1.7.3",
+ "@node-rs/bcrypt-win32-arm64-msvc": "1.7.3",
+ "@node-rs/bcrypt-win32-ia32-msvc": "1.7.3",
+ "@node-rs/bcrypt-win32-x64-msvc": "1.7.3"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-android-arm-eabi": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm-eabi/-/bcrypt-android-arm-eabi-1.7.3.tgz",
+ "integrity": "sha512-l53RuBqnqNvBN2jx09Ws6jpLmuQdSDx10n0GeaTfwh1svxsC8bPpVmxkfBExsT2Tu7KF38gTnPZvwsxysZQyPQ==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-android-arm64": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm64/-/bcrypt-android-arm64-1.7.3.tgz",
+ "integrity": "sha512-TZpm4VbiViqDMvusrcYzLr1b1M5FDF0cDNiTUciLeBSsKtU5lNdEZGAU7gvCnrKoUWpGuOblHU7613zuB7SiNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-darwin-arm64": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-arm64/-/bcrypt-darwin-arm64-1.7.3.tgz",
+ "integrity": "sha512-SiUuAabynVsmixZMjh5xrn8w47EnV0HzbW9st4DPoVhn/wzdUcksIXDY75aoQG2EIzKLN8IGb+CIVnPGmRyhxw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-darwin-x64": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-x64/-/bcrypt-darwin-x64-1.7.3.tgz",
+ "integrity": "sha512-R+81Z0eX4hZPvCXY5Z6l0l+JrTU3WcSYGHP0QYV9uwdaafOz6EhrCXUzZ02AIcAbNoVR8eucYVruq9PiasXoVw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-freebsd-x64": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-freebsd-x64/-/bcrypt-freebsd-x64-1.7.3.tgz",
+ "integrity": "sha512-0pItU/5K3e83JjcJj9fZv+78txUoZ3hHCT7n/UMdu9mkpUzhX/rqb4jmQpJpD+UQoR76xp3qDo5RMgQBffBVNg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-linux-arm-gnueabihf": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm-gnueabihf/-/bcrypt-linux-arm-gnueabihf-1.7.3.tgz",
+ "integrity": "sha512-HTSybWUjNe8rWuXkTkMeFDiQNHc6VioRcgv6AeHZphIxiT6dFbnhXNkfz4Hr0zxvyPhZ3NrYjT2AmPVFT6VW3Q==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-linux-arm64-gnu": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-gnu/-/bcrypt-linux-arm64-gnu-1.7.3.tgz",
+ "integrity": "sha512-rWep6Y+v/c4bZHaM8LmSsrMwMmDR9wG4/q+3Z9VzR8xdnt5VCbuQdYWpf3sgGRGjTRdTBAdSK8x1reOjqsJ3Jg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-linux-arm64-musl": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-musl/-/bcrypt-linux-arm64-musl-1.7.3.tgz",
+ "integrity": "sha512-TyWEKhxr+yfGcMKzVV/ARZw+Hrky2yl91bo0XYU2ZW6I6LDC0emNsXugdWjwz8ADI4OWhhrOjXD8GCilxiB2Rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-linux-x64-gnu": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-gnu/-/bcrypt-linux-x64-gnu-1.7.3.tgz",
+ "integrity": "sha512-PofxM1Qg7tZKj1oP0I7tBTSSLr8Xc2uxx+P3pBCPmYzaBwWqGteNHJlF7n2q5xiH7YOlguH4w5CmcEjsiA3K4A==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-linux-x64-musl": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-musl/-/bcrypt-linux-x64-musl-1.7.3.tgz",
+ "integrity": "sha512-D5V6/dDVKP8S/ieDBLGhTn4oTo3upbrpWInynbhOMjJvPiIxVG1PiI3MXkWBtG9qtfleDk7gUkEKtAOxlIxDTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-win32-arm64-msvc": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-arm64-msvc/-/bcrypt-win32-arm64-msvc-1.7.3.tgz",
+ "integrity": "sha512-b4gH2Yj5R4TwULrfMHd1Qqr+MrnFjVRUAJujDKPqi+PppSqezW8QF6DRSOL4GjnBmz5JEd64wxgeidvy7dsbGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-win32-ia32-msvc": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-ia32-msvc/-/bcrypt-win32-ia32-msvc-1.7.3.tgz",
+ "integrity": "sha512-E91ro+ybI0RhNc89aGaZQGll0YhPoHr8JacoWrNKwhg9zwNOYeuO0tokdMZdm6nF0/8obll0Mq7wO9AXO9iffw==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@node-rs/bcrypt-win32-x64-msvc": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-x64-msvc/-/bcrypt-win32-x64-msvc-1.7.3.tgz",
+ "integrity": "sha512-LO/p9yjPODj/pQvPnowBuwpDdqiyUXQbqL1xb1RSP3NoyCFAGmjL5h0plSQrhLh8hskQiozBRXNaQurtsM7o0Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -4886,6 +5349,35 @@
}
}
},
+ "node_modules/@radix-ui/react-toggle-group": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz",
+ "integrity": "sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.1",
+ "@radix-ui/react-context": "1.0.1",
+ "@radix-ui/react-direction": "1.0.1",
+ "@radix-ui/react-primitive": "1.0.3",
+ "@radix-ui/react-roving-focus": "1.0.4",
+ "@radix-ui/react-toggle": "1.0.3",
+ "@radix-ui/react-use-controllable-state": "1.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-tooltip": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz",
@@ -5074,237 +5566,18 @@
"@babel/runtime": "^7.13.10"
}
},
- "node_modules/@react-email/body": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.0.2.tgz",
- "integrity": "sha512-SqZrZdxZlH7viwnrLvrMnVzOKpiofVAtho09bmm2siDzy0VMDGItXRzUPLcpg9vcbVJCHZRCIKoNXqA+PtokzQ==",
- "dependencies": {
- "react": "18.2.0"
- }
- },
- "node_modules/@react-email/button": {
- "version": "0.0.9",
- "resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.0.9.tgz",
- "integrity": "sha512-eYWQ1X4RFlkKYYSPgSrT6rk98wuLOieEAGENrp9j37t1v/1C+jMmBu0UjZvwHsHWdbOMRjbVDFeMI/+MxWKSEg==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/column": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/@react-email/column/-/column-0.0.7.tgz",
- "integrity": "sha512-B29wVXyIcuVprgGpLkR23waPh/twlqmugZQsCKk05JlMCQ80/Puv4Lgj4dRsIJzgyTLMwG6xq17+Uxc5iGfuaQ==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/components": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.7.tgz",
- "integrity": "sha512-GpRKV8E7EvK9OPf61f5Z8hliB3p0hTot8tslmEUVCTtX7tdL0wM2YEcZiDWU4PJcudJ/QWHJ7Y5wGzNEARcooA==",
- "dependencies": {
- "@react-email/body": "0.0.2",
- "@react-email/button": "0.0.9",
- "@react-email/column": "0.0.7",
- "@react-email/container": "0.0.8",
- "@react-email/font": "0.0.2",
- "@react-email/head": "0.0.5",
- "@react-email/heading": "0.0.8",
- "@react-email/hr": "0.0.5",
- "@react-email/html": "0.0.4",
- "@react-email/img": "0.0.5",
- "@react-email/link": "0.0.5",
- "@react-email/preview": "0.0.6",
- "@react-email/render": "0.0.7",
- "@react-email/row": "0.0.5",
- "@react-email/section": "0.0.9",
- "@react-email/tailwind": "0.0.8",
- "@react-email/text": "0.0.5",
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/container": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.8.tgz",
- "integrity": "sha512-MQZQxvTOoLWjJR+Jm689jltm0I/mtZbEaDnwZbNkkHKgccr++wwb9kOKMgXG777Y7tGa1JATAsZpvFYiCITwUg==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/font": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/@react-email/font/-/font-0.0.2.tgz",
- "integrity": "sha512-mmkyOCAcbgytE7DfIuOBVG1YVDUZY9rPCor4o7pUEzGJiU2y/TNuV8CgNPSU/VgXeBKL/94QDjB62OrGHlFNMQ==",
- "dependencies": {
- "react": "18.2.0"
- }
- },
- "node_modules/@react-email/head": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/@react-email/head/-/head-0.0.5.tgz",
- "integrity": "sha512-s84OxJxZMee2z5b1a+RVwY1NOSUNNf1ecjPf6n64aZmMNcNUyn4gOl7RO6xbfBrZko7TigBwsFB1Cgjxtn/ydg==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/heading": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/@react-email/heading/-/heading-0.0.8.tgz",
- "integrity": "sha512-7atATmoHBHTk7hFYFsFFzOIBV3u1zPpsSOWkLBojdjSUdenpk2SbX8GP8/3aBhWl/tuFX9RBGcu1Xes+ZijFLg==",
- "dependencies": {
- "@radix-ui/react-slot": "1.0.0",
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/heading/node_modules/@radix-ui/react-compose-refs": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
- "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
- "dependencies": {
- "@babel/runtime": "^7.13.10"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@react-email/heading/node_modules/@radix-ui/react-slot": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
- "integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@react-email/hr": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/@react-email/hr/-/hr-0.0.5.tgz",
- "integrity": "sha512-nwB8GmSdvPlR/bWjDS07yHtgdfJqtvCaPXee3SVUY69YYP7NeDO/VACJlgrS9V2l79bj1lUpH0MJMU6MNAk5FQ==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/html": {
- "version": "0.0.4",
- "resolved": "https://registry.npmjs.org/@react-email/html/-/html-0.0.4.tgz",
- "integrity": "sha512-7tRYSnudYAWez+NkPWOM8yLZH7EuYFtYdiLPnzpD+pf4cdk16Gz4up531DaIX6dNBbfbyEFpQxhXZxGeJ5ZkfQ==",
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/img": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/@react-email/img/-/img-0.0.5.tgz",
- "integrity": "sha512-9ziFgBfrIAL+DpVlsraFcd2KwsTRyobLpqTnoiBYCcVZGod59xbYkmsmB3CbUosmLwPYg6AeD7Q7e+hCiwkWgg==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/link": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.5.tgz",
- "integrity": "sha512-z+QW9f4gXBdyfhl7iYMY3td+rXKeZYK/2AGElEMsxVoywn5D0b6cF8m5w2jbf0U2V3enT+zy9yc1R6AyT59NOg==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/preview": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/@react-email/preview/-/preview-0.0.6.tgz",
- "integrity": "sha512-mXDCc3NGpm/4W7gowBtjsTxYXowLNOLsJsYhIfrsjNJWGlVhVFB9uEHm55LjBLpxSG020g6/8LIrpJU6g22qvg==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
"node_modules/@react-email/render": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/@react-email/render/-/render-0.0.7.tgz",
- "integrity": "sha512-hMMhxk6TpOcDC5qnKzXPVJoVGEwfm+U5bGOPH+MyTTlx0F02RLQygcATBKsbP7aI/mvkmBAZoFbgPIHop7ovug==",
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/@react-email/render/-/render-0.0.9.tgz",
+ "integrity": "sha512-nrim7wiACnaXsGtL7GF6jp3Qmml8J6vAjAH88jkC8lIbfNZaCyuPQHANjyYIXlvQeAbsWADQJFZgOHUqFqjh/A==",
"dependencies": {
- "html-to-text": "9.0.3",
+ "html-to-text": "9.0.5",
"pretty": "2.0.0",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/row": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/@react-email/row/-/row-0.0.5.tgz",
- "integrity": "sha512-dir5l1M7Z/1BQqQkUrKUPIIDPt6ueEf6ScMGoBOcUh+VNNqmnqJE2Q2CH5X3w2uo6a5X7tnVhoJHGa2KTKe8Sw==",
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/section": {
- "version": "0.0.9",
- "resolved": "https://registry.npmjs.org/@react-email/section/-/section-0.0.9.tgz",
- "integrity": "sha512-3EbcWJ1jUZrzquWSvXrv8Hbk9V+BGvLcMWQIli4NdIpQlddmlGKUYfXU2mB2d2pf+5ojqkGcFZZ9fWxycB84jQ==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/tailwind": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-0.0.8.tgz",
- "integrity": "sha512-0BLjD5GpiyBK7YDlaDrjHIpj9eTrrZrMJud3f1UPoCZhS+0S/M8LcR8WMbQsR+8/aLGmiy4F4TGZuRQcsJEsFw==",
- "dependencies": {
- "html-react-parser": "3.0.9",
- "react": "18.2.0",
- "react-dom": "18.2.0",
- "tw-to-css": "0.0.11"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@react-email/text": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.0.5.tgz",
- "integrity": "sha512-LXhHiaC6oRRsNAfOzJDos4wQA22eIdVJvR6G7uu4QzUvYNOAatDMf89jRQcKGrxX7InkS640v8sHd9jl5ztM5w==",
- "dependencies": {
- "react": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
+ "node": ">=18.0.0"
}
},
"node_modules/@rushstack/eslint-patch": {
@@ -5321,12 +5594,12 @@
}
},
"node_modules/@selderee/plugin-htmlparser2": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.10.0.tgz",
- "integrity": "sha512-gW69MEamZ4wk1OsOq1nG1jcyhXIQcnrsX5JwixVw/9xaiav8TCyjESAruu1Rz9yyInhgBXxkNwMeygKnN2uxNA==",
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
+ "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==",
"dependencies": {
"domhandler": "^5.0.3",
- "selderee": "^0.10.0"
+ "selderee": "^0.11.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
@@ -6303,9 +6576,9 @@
}
},
"node_modules/@types/json-schema": {
- "version": "7.0.13",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
- "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ=="
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
@@ -6410,9 +6683,9 @@
"integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ=="
},
"node_modules/@types/semver": {
- "version": "7.5.3",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz",
- "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw=="
+ "version": "7.5.5",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz",
+ "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg=="
},
"node_modules/@types/unist": {
"version": "2.0.8",
@@ -6471,6 +6744,86 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz",
+ "integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "6.8.0",
+ "@typescript-eslint/utils": "6.8.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz",
+ "integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==",
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz",
+ "integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==",
+ "dependencies": {
+ "@typescript-eslint/types": "6.8.0",
+ "@typescript-eslint/visitor-keys": "6.8.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz",
+ "integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==",
+ "dependencies": {
+ "@typescript-eslint/types": "6.8.0",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
"node_modules/@typescript-eslint/types": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
@@ -6511,6 +6864,100 @@
}
}
},
+ "node_modules/@typescript-eslint/utils": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz",
+ "integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@types/json-schema": "^7.0.12",
+ "@types/semver": "^7.5.0",
+ "@typescript-eslint/scope-manager": "6.8.0",
+ "@typescript-eslint/types": "6.8.0",
+ "@typescript-eslint/typescript-estree": "6.8.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz",
+ "integrity": "sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==",
+ "dependencies": {
+ "@typescript-eslint/types": "6.8.0",
+ "@typescript-eslint/visitor-keys": "6.8.0"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz",
+ "integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==",
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz",
+ "integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==",
+ "dependencies": {
+ "@typescript-eslint/types": "6.8.0",
+ "@typescript-eslint/visitor-keys": "6.8.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz",
+ "integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==",
+ "dependencies": {
+ "@typescript-eslint/types": "6.8.0",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
@@ -6560,35 +7007,6 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/acorn-node": {
- "version": "1.8.2",
- "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
- "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
- "dependencies": {
- "acorn": "^7.0.0",
- "acorn-walk": "^7.0.0",
- "xtend": "^4.0.2"
- }
- },
- "node_modules/acorn-node/node_modules/acorn": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
- "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-node/node_modules/acorn-walk": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
- "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
@@ -8577,14 +8995,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/defined": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz",
- "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -8643,22 +9053,6 @@
"node": ">=12"
}
},
- "node_modules/detective": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz",
- "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==",
- "dependencies": {
- "acorn-node": "^1.8.2",
- "defined": "^1.0.0",
- "minimist": "^1.2.6"
- },
- "bin": {
- "detective": "bin/detective.js"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/dezalgo": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
@@ -9611,6 +10005,14 @@
"node": ">=12"
}
},
+ "node_modules/eslint-rule-composer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
+ "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -10967,57 +11369,25 @@
"node": ">=10"
}
},
- "node_modules/html-dom-parser": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-3.1.3.tgz",
- "integrity": "sha512-fI0yyNlIeSboxU+jnrA4v8qj4+M8SI9/q6AKYdwCY2qki22UtKCDTxvagHniECu7sa5/o2zFRdLleA67035lsA==",
- "dependencies": {
- "domhandler": "5.0.3",
- "htmlparser2": "8.0.1"
- }
- },
- "node_modules/html-react-parser": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-3.0.9.tgz",
- "integrity": "sha512-gOPZmaCMXNYu7Y9+58k2tLhTMXQ+QN8ctNFijzLuBxJaLZ6TsN+tUpN+MhbI+6nGaBCRGT2rpw6y/AqkTFZckg==",
- "dependencies": {
- "domhandler": "5.0.3",
- "html-dom-parser": "3.1.3",
- "react-property": "2.0.0",
- "style-to-js": "1.1.3"
- },
- "peerDependencies": {
- "react": "0.14 || 15 || 16 || 17 || 18"
- }
- },
"node_modules/html-to-text": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.3.tgz",
- "integrity": "sha512-hxDF1kVCF2uw4VUJ3vr2doc91pXf2D5ngKcNviSitNkhP9OMOaJkDrFIFL6RMvko7NisWTEiqGpQ9LAxcVok1w==",
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
+ "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==",
"dependencies": {
- "@selderee/plugin-htmlparser2": "^0.10.0",
- "deepmerge": "^4.2.2",
+ "@selderee/plugin-htmlparser2": "^0.11.0",
+ "deepmerge": "^4.3.1",
"dom-serializer": "^2.0.0",
- "htmlparser2": "^8.0.1",
- "selderee": "^0.10.0"
+ "htmlparser2": "^8.0.2",
+ "selderee": "^0.11.0"
},
"engines": {
"node": ">=14"
}
},
- "node_modules/html-void-elements": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
- "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/htmlparser2": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz",
- "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==",
+ "node_modules/html-to-text/node_modules/htmlparser2": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
+ "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
@@ -11027,9 +11397,18 @@
],
"dependencies": {
"domelementtype": "^2.3.0",
- "domhandler": "^5.0.2",
+ "domhandler": "^5.0.3",
"domutils": "^3.0.1",
- "entities": "^4.3.0"
+ "entities": "^4.4.0"
+ }
+ },
+ "node_modules/html-void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
+ "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/http-errors": {
@@ -11128,6 +11507,13 @@
"node": ">=14.0.0"
}
},
+ "node_modules/immutable": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
+ "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
+ "optional": true,
+ "peer": true
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -14327,6 +14713,15 @@
"node": ">=8"
}
},
+ "node_modules/oslo": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/oslo/-/oslo-0.17.0.tgz",
+ "integrity": "sha512-UJHew6zFEkJYGWjO4/ARHnX+M7umhJ6IXc6cJA2AQ3BpFwqEqaKjySOfXYuNFQddzfP2zk1aG+xYQG1ODHKwfQ==",
+ "dependencies": {
+ "@node-rs/argon2": "^1.5.2",
+ "@node-rs/bcrypt": "^1.7.3"
+ }
+ },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -14432,12 +14827,12 @@
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
"node_modules/parseley": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.11.0.tgz",
- "integrity": "sha512-VfcwXlBWgTF+unPcr7yu3HSSA6QUdDaDnrHcytVfj5Z8azAyKBDrYnSIfeSxlrEayndNcLmrXzg+Vxbo6DWRXQ==",
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz",
+ "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==",
"dependencies": {
"leac": "^0.6.0",
- "peberminta": "^0.8.0"
+ "peberminta": "^0.9.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
@@ -14547,9 +14942,9 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/peberminta": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.8.0.tgz",
- "integrity": "sha512-YYEs+eauIjDH5nUEGi18EohWE0nV2QbGTqmxQcqgZ/0g+laPCQmuIqq7EBLVi9uim9zMgfJv0QBZEnQ3uHw/Tw==",
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
+ "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==",
"funding": {
"url": "https://ko-fi.com/killymxi"
}
@@ -15747,20 +16142,6 @@
"node": ">=12"
}
},
- "node_modules/react-email/node_modules/@react-email/render": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/@react-email/render/-/render-0.0.6.tgz",
- "integrity": "sha512-6zs7WZbd37TcPT1OmMPH/kcBpv0QSi+k3om7LyDnbdIcrbwOO/OstVwUaa/6zgvDvnq9Y2wOosbru7j5kUrW9A==",
- "dependencies": {
- "html-to-text": "9.0.3",
- "pretty": "2.0.0",
- "react": "18.2.0",
- "react-dom": "18.2.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
"node_modules/react-email/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -15857,6 +16238,15 @@
"react": "^16.8.0 || ^17 || ^18"
}
},
+ "node_modules/react-hotkeys-hook": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.4.1.tgz",
+ "integrity": "sha512-sClBMBioFEgFGYLTWWRKvhxcCx1DRznd+wkFHwQZspnRBkHTgruKIHptlK/U/2DPX8BhHoRGzpMVWUXMmdZlmw==",
+ "peerDependencies": {
+ "react": ">=16.8.1",
+ "react-dom": ">=16.8.1"
+ }
+ },
"node_modules/react-icons": {
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz",
@@ -15875,11 +16265,6 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
- "node_modules/react-property": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz",
- "integrity": "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw=="
- },
"node_modules/react-remove-scroll": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
@@ -16458,28 +16843,16 @@
}
},
"node_modules/resend": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/resend/-/resend-1.1.0.tgz",
- "integrity": "sha512-it8TIDVT+/gAiJsUlv2tdHuvzwCCv4Zwu+udDqIm/dIuByQwe68TtFDcPccxqpSVVrNCBxxXLzsdT1tsV+P3GA==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/resend/-/resend-2.0.0.tgz",
+ "integrity": "sha512-jAh0DN84ZjjmzGM2vMjJ1hphPBg1mG98dzopF7kJzmin62v8ESg4og2iCKWdkAboGOT2SeO5exbr/8Xh8gLddw==",
"dependencies": {
- "@react-email/render": "0.0.7",
- "type-fest": "3.13.0"
+ "@react-email/render": "0.0.9"
},
"engines": {
"node": ">=18"
}
},
- "node_modules/resend/node_modules/type-fest": {
- "version": "3.13.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.0.tgz",
- "integrity": "sha512-Gur3yQGM9qiLNs0KPP7LPgeRbio2QTt4xXouobMCarR0/wyW3F+F/+OWwshg3NG0Adon7uQfSZBpB46NfhoF1A==",
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -16748,6 +17121,24 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
+ "node_modules/sass": {
+ "version": "1.69.4",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.4.tgz",
+ "integrity": "sha512-+qEreVhqAy8o++aQfCJwp0sklr2xyEzkm9Pp/Igu9wNPoe7EZEQ8X/MBvvXggI2ql607cxKg/RKOwDj6pp2XDA==",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
@@ -16769,11 +17160,11 @@
}
},
"node_modules/selderee": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.10.0.tgz",
- "integrity": "sha512-DEL/RW/f4qLw/NrVg97xKaEBC8IpzIG2fvxnzCp3Z4yk4jQ3MXom+Imav9wApjxX2dfS3eW7x0DXafJr85i39A==",
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
+ "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==",
"dependencies": {
- "parseley": "^0.11.0"
+ "parseley": "^0.12.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
@@ -17437,22 +17828,6 @@
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
},
- "node_modules/style-to-js": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.3.tgz",
- "integrity": "sha512-zKI5gN/zb7LS/Vm0eUwjmjrXWw8IMtyA8aPBJZdYiQTXj4+wQ3IucOLIOnF7zCHxvW8UhIGh/uZh/t9zEHXNTQ==",
- "dependencies": {
- "style-to-object": "0.4.1"
- }
- },
- "node_modules/style-to-js/node_modules/style-to-object": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz",
- "integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==",
- "dependencies": {
- "inline-style-parser": "0.1.1"
- }
- },
"node_modules/style-to-object": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz",
@@ -18161,199 +18536,6 @@
"win32"
]
},
- "node_modules/tw-to-css": {
- "version": "0.0.11",
- "resolved": "https://registry.npmjs.org/tw-to-css/-/tw-to-css-0.0.11.tgz",
- "integrity": "sha512-uIJuEBIwyHzZg9xyGyEgDWHIkbAwEC4bhEHQ4THPuN5SToR7Zlhes5ffMjqtrv+WdtTmudTHTdc9VwUldy0FxQ==",
- "dependencies": {
- "postcss": "8.4.21",
- "postcss-css-variables": "0.18.0",
- "tailwindcss": "3.2.7"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/tw-to-css/node_modules/arg": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
- },
- "node_modules/tw-to-css/node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/tw-to-css/node_modules/object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/tw-to-css/node_modules/postcss": {
- "version": "8.4.21",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
- "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.4",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/tw-to-css/node_modules/postcss-import": {
- "version": "14.1.0",
- "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz",
- "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==",
- "dependencies": {
- "postcss-value-parser": "^4.0.0",
- "read-cache": "^1.0.0",
- "resolve": "^1.1.7"
- },
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "postcss": "^8.0.0"
- }
- },
- "node_modules/tw-to-css/node_modules/postcss-load-config": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
- "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
- "dependencies": {
- "lilconfig": "^2.0.5",
- "yaml": "^1.10.2"
- },
- "engines": {
- "node": ">= 10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": ">=8.0.9",
- "ts-node": ">=9.0.0"
- },
- "peerDependenciesMeta": {
- "postcss": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
- }
- },
- "node_modules/tw-to-css/node_modules/postcss-nested": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz",
- "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==",
- "dependencies": {
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": ">=12.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.2.14"
- }
- },
- "node_modules/tw-to-css/node_modules/postcss-selector-parser": {
- "version": "6.0.13",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
- "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/tw-to-css/node_modules/quick-lru": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
- "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/tw-to-css/node_modules/tailwindcss": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz",
- "integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==",
- "dependencies": {
- "arg": "^5.0.2",
- "chokidar": "^3.5.3",
- "color-name": "^1.1.4",
- "detective": "^5.2.1",
- "didyoumean": "^1.2.2",
- "dlv": "^1.1.3",
- "fast-glob": "^3.2.12",
- "glob-parent": "^6.0.2",
- "is-glob": "^4.0.3",
- "lilconfig": "^2.0.6",
- "micromatch": "^4.0.5",
- "normalize-path": "^3.0.0",
- "object-hash": "^3.0.0",
- "picocolors": "^1.0.0",
- "postcss": "^8.0.9",
- "postcss-import": "^14.1.0",
- "postcss-js": "^4.0.0",
- "postcss-load-config": "^3.1.4",
- "postcss-nested": "6.0.0",
- "postcss-selector-parser": "^6.0.11",
- "postcss-value-parser": "^4.2.0",
- "quick-lru": "^5.1.1",
- "resolve": "^1.22.1"
- },
- "bin": {
- "tailwind": "lib/cli.js",
- "tailwindcss": "lib/cli.js"
- },
- "engines": {
- "node": ">=12.13.0"
- },
- "peerDependencies": {
- "postcss": "^8.0.9"
- }
- },
- "node_modules/tw-to-css/node_modules/yaml": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
- "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/tween-functions": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
@@ -18676,6 +18858,11 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/uqr": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.2.tgz",
+ "integrity": "sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA=="
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -19360,11 +19547,11 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
- "@documenso/nodemailer-resend": "1.0.0",
- "@react-email/components": "^0.0.7",
+ "@documenso/nodemailer-resend": "2.0.0",
+ "@react-email/components": "^0.0.11",
"nodemailer": "^6.9.3",
- "react-email": "^1.9.4",
- "resend": "^1.1.0"
+ "react-email": "^1.9.5",
+ "resend": "^2.0.0"
},
"devDependencies": {
"@documenso/tailwind-config": "*",
@@ -19373,6 +19560,298 @@
"tsup": "^7.1.0"
}
},
+ "packages/email/node_modules/@react-email/body": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.0.4.tgz",
+ "integrity": "sha512-NmHOumdmyjWvOXomqhQt06KbgRxhHrVznxQp/oWiPWes8nAJo2Y4L27aPHR9nTcs7JF7NmcJe9YSN42pswK+GQ==",
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/button": {
+ "version": "0.0.11",
+ "resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.0.11.tgz",
+ "integrity": "sha512-mB5ySfZifwE5ybtIWwXGbmKk1uKkH4655gftL4+mMxZAZCkINVa2KXTi5pO+xZhMtJI9xtAsikOrOEU1gTDoww==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/column": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/@react-email/column/-/column-0.0.8.tgz",
+ "integrity": "sha512-blChqGU8e/L6KZiB5EPww8bkZfdyHDuS0vKIvU+iS14uK+xfAw+5P5CU9BYXccEuJh2Gftfngu1bWMFp2Sc6ag==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/components": {
+ "version": "0.0.11",
+ "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.11.tgz",
+ "integrity": "sha512-wj9Sra/AGQvadb3ZABz44ll9Fb9FvXPEmODXRWbNRSc8pJTFGWorrsm4M/yj8dnewd4HtnbLkV1eDOvuiLAVLA==",
+ "dependencies": {
+ "@react-email/body": "0.0.4",
+ "@react-email/button": "0.0.11",
+ "@react-email/column": "0.0.8",
+ "@react-email/container": "0.0.10",
+ "@react-email/font": "0.0.4",
+ "@react-email/head": "0.0.6",
+ "@react-email/heading": "0.0.9",
+ "@react-email/hr": "0.0.6",
+ "@react-email/html": "0.0.6",
+ "@react-email/img": "0.0.6",
+ "@react-email/link": "0.0.6",
+ "@react-email/preview": "0.0.7",
+ "@react-email/render": "0.0.9",
+ "@react-email/row": "0.0.6",
+ "@react-email/section": "0.0.10",
+ "@react-email/tailwind": "0.0.12",
+ "@react-email/text": "0.0.6"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/container": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.10.tgz",
+ "integrity": "sha512-goishY7ocq+lord0043/LZK268bqvMFW/sxpUt/dSCPJyrrZZNCbpW2t8w8HztU38cYj0qGQLxO5Qvpn/RER3w==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/font": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@react-email/font/-/font-0.0.4.tgz",
+ "integrity": "sha512-rN/pFlAcDNmfYFxpufT/rFRrM5KYBJM4nTA2uylTehlVOro6fb/q6n0zUwLF6OmQ4QIuRbqdEy7DI9mmJiNHxA==",
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/head": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@react-email/head/-/head-0.0.6.tgz",
+ "integrity": "sha512-9BrBDalb34nBOmmQVQc7/pjJotcuAeC3rhBl4G88Ohiipuv15vPIKqwy8vPJcFNi4l7yGlitfG3EESIjkLkoIw==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/heading": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/@react-email/heading/-/heading-0.0.9.tgz",
+ "integrity": "sha512-xzkcGlm+/aFrNlJZBKzxRKkRYJ2cRx92IqmSKAuGnwuKQ/uMKomXzPsHPu3Dclmnhn3wVKj4uprkgQOoxP6uXQ==",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.0.2",
+ "react": "18.2.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/hr": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@react-email/hr/-/hr-0.0.6.tgz",
+ "integrity": "sha512-W+wINBz7z7BRv3i9GS+QoJBae1PESNhv6ZY6eLnEpqtBI/2++suuRNJOU/KpZzE6pykeTp6I/Z7UcL0LEYKgyg==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/html": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@react-email/html/-/html-0.0.6.tgz",
+ "integrity": "sha512-8Fo20VOqxqc087gGEPjT8uos06fTXIC8NSoiJxpiwAkwiKtQnQH/jOdoLv6XaWh5Zt2clj1uokaoklnaM5rY1w==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/img": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@react-email/img/-/img-0.0.6.tgz",
+ "integrity": "sha512-Wd7xKI3b1Jvb2ZEHyVpJ9D98u0GHrRl+578b8LV24PavM/65V61Q5LN5Fr9sAhj+4VGqnHDIVeXIYEzVbWaa3Q==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/link": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.6.tgz",
+ "integrity": "sha512-bYYHroWGS//nDl9yhh8V6K2BrNwAsyX7N/XClSCRku3x56NrZ6D0nBKWewYDPlJ9rW9TIaJm1jDYtO9XBzLlkQ==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/preview": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/@react-email/preview/-/preview-0.0.7.tgz",
+ "integrity": "sha512-YLfIwHdexPi8IgP1pSuVXdAmKzMQ8ctCCLEjkMttT2vkSFqT6m/e6UFWK2l30rKm2dDsLvQyEvo923mPXjnNzg==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/row": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@react-email/row/-/row-0.0.6.tgz",
+ "integrity": "sha512-msJ2TnDJNwpgDfDzUO63CvhusJHeaGLMM+8Zz86VPvxzwe/DkT7N48QKRWRCkt8urxVz5U+EgivORA9Dum9p3Q==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/section": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@react-email/section/-/section-0.0.10.tgz",
+ "integrity": "sha512-x9B2KYFqj+d8I1fK9bgeVm/3mLE4Qgn4mm/GbDtcJeSzKU/G7bTb7/3+BMDk9SARPGkg5XAuZm1XgcqQQutt2A==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/tailwind": {
+ "version": "0.0.12",
+ "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-0.0.12.tgz",
+ "integrity": "sha512-s8Ch7GL30qRKScn9NWwItMqxjtzbyUtCnXfC6sL2YTVtulbfvZZ06W+aA0S6f7fdrVlOOlQzZuK/sVaQCHhcSw==",
+ "dependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "tw-to-css": "0.0.12"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/@react-email/text": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.0.6.tgz",
+ "integrity": "sha512-PDUTAD1PjlzXFOIUrR1zuV2xxguL62yne5YLcn1k+u/dVUyzn6iU/5lFShxCfzuh3QDWCf4+JRNnXN9rmV6jzw==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "packages/email/node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
+ },
+ "packages/email/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "packages/email/node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "packages/email/node_modules/postcss-selector-parser": {
+ "version": "6.0.13",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
+ "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "packages/email/node_modules/tailwindcss": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
+ "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.2.12",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.18.2",
+ "lilconfig": "^2.1.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.23",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.1",
+ "postcss-nested": "^6.0.1",
+ "postcss-selector-parser": "^6.0.11",
+ "postcss-value-parser": "^4.2.0",
+ "resolve": "^1.22.2",
+ "sucrase": "^3.32.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "packages/email/node_modules/tw-to-css": {
+ "version": "0.0.12",
+ "resolved": "https://registry.npmjs.org/tw-to-css/-/tw-to-css-0.0.12.tgz",
+ "integrity": "sha512-rQAsQvOtV1lBkyCw+iypMygNHrShYAItES5r8fMsrhhaj5qrV2LkZyXc8ccEH+u5bFjHjQ9iuxe90I7Kykf6pw==",
+ "dependencies": {
+ "postcss": "8.4.31",
+ "postcss-css-variables": "0.18.0",
+ "tailwindcss": "3.3.2"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"packages/eslint-config": {
"name": "@documenso/eslint-config",
"version": "0.0.0",
@@ -19387,6 +19866,7 @@
"eslint-plugin-package-json": "^0.1.4",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
+ "eslint-plugin-unused-imports": "^3.0.0",
"typescript": "5.2.2"
}
},
@@ -19489,32 +19969,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
- "packages/eslint-config/node_modules/@typescript-eslint/type-utils": {
- "version": "6.8.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz",
- "integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==",
- "dependencies": {
- "@typescript-eslint/typescript-estree": "6.8.0",
- "@typescript-eslint/utils": "6.8.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^1.0.1"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
"packages/eslint-config/node_modules/@typescript-eslint/types": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz",
@@ -19567,44 +20021,6 @@
"node": ">=10"
}
},
- "packages/eslint-config/node_modules/@typescript-eslint/utils": {
- "version": "6.8.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz",
- "integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@types/json-schema": "^7.0.12",
- "@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "6.8.0",
- "@typescript-eslint/types": "6.8.0",
- "@typescript-eslint/typescript-estree": "6.8.0",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
- }
- },
- "packages/eslint-config/node_modules/@typescript-eslint/utils/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"packages/eslint-config/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz",
@@ -19710,6 +20126,26 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
}
},
+ "packages/eslint-config/node_modules/eslint-plugin-unused-imports": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz",
+ "integrity": "sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==",
+ "dependencies": {
+ "eslint-rule-composer": "^0.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "eslint": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/eslint-plugin": {
+ "optional": true
+ }
+ }
+ },
"packages/eslint-config/node_modules/resolve": {
"version": "2.0.0-next.5",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
@@ -19747,6 +20183,8 @@
"@documenso/prisma": "*",
"@documenso/signing": "*",
"@next-auth/prisma-adapter": "1.0.7",
+ "@noble/ciphers": "0.4.0",
+ "@noble/hashes": "1.3.2",
"@pdf-lib/fontkit": "^1.1.1",
"@scure/base": "^1.1.3",
"@sindresorhus/slugify": "^2.2.1",
@@ -19756,6 +20194,7 @@
"nanoid": "^4.0.2",
"next": "14.0.0",
"next-auth": "4.24.3",
+ "oslo": "^0.17.0",
"pdf-lib": "^1.17.1",
"react": "18.2.0",
"remeda": "^1.27.1",
@@ -19855,7 +20294,8 @@
"@trpc/server": "^10.36.0",
"superjson": "^1.13.1",
"zod": "^3.22.4"
- }
+ },
+ "devDependencies": {}
},
"packages/tsconfig": {
"name": "@documenso/tsconfig",
@@ -19894,6 +20334,7 @@
"@radix-ui/react-tabs": "^1.0.3",
"@radix-ui/react-toast": "^1.1.3",
"@radix-ui/react-toggle": "^1.0.2",
+ "@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.6",
"@tanstack/react-table": "^8.9.1",
"class-variance-authority": "^0.6.0",
diff --git a/package.json b/package.json
index 5bbd4f431..d21af733e 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"dev": "turbo run dev --filter=@documenso/web --filter=@documenso/marketing",
"start": "cd apps && cd web && next start",
"lint": "turbo run lint",
+ "lint:fix": "turbo run lint:fix",
"format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",
"prepare": "husky install",
"commitlint": "commitlint --edit",
@@ -46,6 +47,7 @@
"packages/*"
],
"dependencies": {
- "recharts": "^2.7.2"
+ "recharts": "^2.7.2",
+ "react-hotkeys-hook": "^4.4.1"
}
}
diff --git a/packages/ee/server-only/limits/handler.ts b/packages/ee/server-only/limits/handler.ts
index f4b1bd4af..69f77db75 100644
--- a/packages/ee/server-only/limits/handler.ts
+++ b/packages/ee/server-only/limits/handler.ts
@@ -30,7 +30,7 @@ export const limitsHandler = async (
});
}
- res.status(500).json({
+ return res.status(500).json({
error: ERROR_CODES.UNKNOWN,
});
}
diff --git a/packages/email/package.json b/packages/email/package.json
index 4b23512ce..6366c67ed 100644
--- a/packages/email/package.json
+++ b/packages/email/package.json
@@ -17,11 +17,11 @@
"worker:test": "tsup worker/index.ts --format esm"
},
"dependencies": {
- "@documenso/nodemailer-resend": "1.0.0",
- "@react-email/components": "^0.0.7",
+ "@documenso/nodemailer-resend": "2.0.0",
+ "@react-email/components": "^0.0.11",
"nodemailer": "^6.9.3",
- "react-email": "^1.9.4",
- "resend": "^1.1.0"
+ "react-email": "^1.9.5",
+ "resend": "^2.0.0"
},
"devDependencies": {
"@documenso/tailwind-config": "*",
diff --git a/packages/email/template-components/template-confirmation-email.tsx b/packages/email/template-components/template-confirmation-email.tsx
new file mode 100644
index 000000000..e46582f54
--- /dev/null
+++ b/packages/email/template-components/template-confirmation-email.tsx
@@ -0,0 +1,52 @@
+import { Button, Section, Tailwind, Text } from '@react-email/components';
+
+import * as config from '@documenso/tailwind-config';
+
+import { TemplateDocumentImage } from './template-document-image';
+
+export type TemplateConfirmationEmailProps = {
+ confirmationLink: string;
+ assetBaseUrl: string;
+};
+
+export const TemplateConfirmationEmail = ({
+ confirmationLink,
+ assetBaseUrl,
+}: TemplateConfirmationEmailProps) => {
+ return (
+
+
+
+
+
+ Welcome to Documenso!
+
+
+
+ Before you get started, please confirm your email address by clicking the button below:
+
+
+
+
+ Confirm email
+
+
+ You can also copy and paste this link into your browser: {confirmationLink} (link
+ expires in 1 hour)
+
+
+
+
+ );
+};
diff --git a/packages/email/templates/confirm-email.tsx b/packages/email/templates/confirm-email.tsx
new file mode 100644
index 000000000..5e917f0a3
--- /dev/null
+++ b/packages/email/templates/confirm-email.tsx
@@ -0,0 +1,69 @@
+import {
+ Body,
+ Container,
+ Head,
+ Html,
+ Img,
+ Preview,
+ Section,
+ Tailwind,
+} from '@react-email/components';
+
+import config from '@documenso/tailwind-config';
+
+import {
+ TemplateConfirmationEmail,
+ TemplateConfirmationEmailProps,
+} from '../template-components/template-confirmation-email';
+import { TemplateFooter } from '../template-components/template-footer';
+
+export const ConfirmEmailTemplate = ({
+ confirmationLink,
+ assetBaseUrl,
+}: TemplateConfirmationEmailProps) => {
+ const previewText = `Please confirm your email address`;
+
+ const getAssetUrl = (path: string) => {
+ return new URL(path, assetBaseUrl).toString();
+ };
+
+ return (
+
+
+