diff --git a/.cursorrules b/.cursorrules
index 61a306ff0..a40eec787 100644
--- a/.cursorrules
+++ b/.cursorrules
@@ -1,4 +1,7 @@
+You are an expert in TypeScript, Node.js, Remix, React, Shadcn UI and Tailwind.
+
Code Style and Structure:
+
- Write concise, technical TypeScript code with accurate examples
- Use functional and declarative programming patterns; avoid classes
- Prefer iteration and modularization over code duplication
@@ -6,20 +9,25 @@ Code Style and Structure:
- Structure files: exported component, subcomponents, helpers, static content, types
Naming Conventions:
+
- Use lowercase with dashes for directories (e.g., components/auth-wizard)
- Favor named exports for components
TypeScript Usage:
-- Use TypeScript for all code; prefer interfaces over types
-- Avoid enums; use maps instead
+
+- Use TypeScript for all code; prefer types over interfaces
- Use functional components with TypeScript interfaces
Syntax and Formatting:
-- Use the "function" keyword for pure functions
+
+- Create functions using `const fn = () => {}`
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
- Use declarative JSX
+- Never use 'use client'
+- Never use 1 line if statements
Error Handling and Validation:
+
- Prioritize error handling: handle errors and edge cases early
- Use early returns and guard clauses
- Implement proper error logging and user-friendly messages
@@ -28,21 +36,40 @@ Error Handling and Validation:
- Use error boundaries for unexpected errors
UI and Styling:
+
- Use Shadcn UI, Radix, and Tailwind Aria for components and styling
- Implement responsive design with Tailwind CSS; use a mobile-first approach
+- When using Lucide icons, prefer the longhand names, for example HomeIcon instead of Home
-Performance Optimization:
-- Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC)
-- Wrap client components in Suspense with fallback
-- Use dynamic loading for non-critical components
-- Optimize images: use WebP format, include size data, implement lazy loading
+React forms
-Key Conventions:
-- Use 'nuqs' for URL search parameter state management
-- Optimize Web Vitals (LCP, CLS, FID)
-- Limit 'use client':
- - Favor server components and Next.js SSR
- - Use only for Web API access in small components
- - Avoid for data fetching or state management
+- Use zod for form validation react-hook-form for forms
+- Look at TeamCreateDialog.tsx as an example of form usage
+- Use
+
+
+ );
+};
diff --git a/apps/remix/app/components/dialogs/admin-user-reset-two-factor-dialog.tsx b/apps/remix/app/components/dialogs/admin-user-reset-two-factor-dialog.tsx
new file mode 100644
index 000000000..f95657d9f
--- /dev/null
+++ b/apps/remix/app/components/dialogs/admin-user-reset-two-factor-dialog.tsx
@@ -0,0 +1,159 @@
+import { useState } from 'react';
+
+import { msg } from '@lingui/core/macro';
+import { useLingui } from '@lingui/react';
+import { Trans } from '@lingui/react/macro';
+import type { User } from '@prisma/client';
+import { useRevalidator } from 'react-router';
+import { match } from 'ts-pattern';
+
+import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
+import { trpc } from '@documenso/trpc/react';
+import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
+import { Button } from '@documenso/ui/primitives/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@documenso/ui/primitives/dialog';
+import { Input } from '@documenso/ui/primitives/input';
+import { useToast } from '@documenso/ui/primitives/use-toast';
+
+export type AdminUserResetTwoFactorDialogProps = {
+ className?: string;
+ user: User;
+};
+
+export const AdminUserResetTwoFactorDialog = ({
+ className,
+ user,
+}: AdminUserResetTwoFactorDialogProps) => {
+ const { _ } = useLingui();
+ const { toast } = useToast();
+ const { revalidate } = useRevalidator();
+ const [email, setEmail] = useState('');
+ const [open, setOpen] = useState(false);
+
+ const { mutateAsync: resetTwoFactor, isPending: isResettingTwoFactor } =
+ trpc.admin.user.resetTwoFactor.useMutation();
+
+ const onResetTwoFactor = async () => {
+ try {
+ await resetTwoFactor({
+ userId: user.id,
+ });
+
+ toast({
+ title: _(msg`2FA Reset`),
+ description: _(msg`The user's two factor authentication has been reset successfully.`),
+ duration: 5000,
+ });
+
+ await revalidate();
+ setOpen(false);
+ } catch (err) {
+ const error = AppError.parseError(err);
+
+ const errorMessage = match(error.code)
+ .with(AppErrorCode.NOT_FOUND, () => msg`User not found.`)
+ .with(
+ AppErrorCode.UNAUTHORIZED,
+ () => msg`You are not authorized to reset two factor authentcation for this user.`,
+ )
+ .otherwise(
+ () => msg`An error occurred while resetting two factor authentication for the user.`,
+ );
+
+ toast({
+ title: _(msg`Error`),
+ description: _(errorMessage),
+ variant: 'destructive',
+ duration: 7500,
+ });
+ }
+ };
+
+ const handleOpenChange = (newOpen: boolean) => {
+ setOpen(newOpen);
+
+ if (!newOpen) {
+ setEmail('');
+ }
+ };
+
+ return (
+
+
+
+ Reset Two Factor Authentication
+
+
+ Reset the users two factor authentication. This action is irreversible and will
+ disable two factor authentication for the user.
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx b/apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
index 1240a0dc0..7f6bee7bb 100644
--- a/apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
+++ b/apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx
@@ -1,3 +1,5 @@
+import { useState } from 'react';
+
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans } from '@lingui/react/macro';
import { useForm } from 'react-hook-form';
@@ -55,6 +57,8 @@ export function AssistantConfirmationDialog({
allowDictateNextSigner = false,
defaultNextSigner,
}: ConfirmationDialogProps) {
+ const [isEditingNextSigner, setIsEditingNextSigner] = useState(false);
+
const form = useForm({
resolver: zodResolver(ZNextSignerFormSchema),
defaultValues: {
@@ -107,53 +111,72 @@ export function AssistantConfirmationDialog({
{allowDictateNextSigner && (
-
-
- The next recipient to sign this document will be{' '}
-
+
+ {!isEditingNextSigner && (
+
+
+ The next recipient to sign this document will be{' '}
+ {form.watch('name')} (
+ {form.watch('email')}).
+
+ toast({ title: t`Copied to clipboard` })}
+ />
+
+
+
+
+
+
+
+
+
+
+ toast({ title: t`Copied to clipboard` })}
+ />
+
+
+
+
+
+
+
+
+
+
+ toast({ title: t`Copied to clipboard` })}
+ />
+
+
+
+
+ ))}
+
+
+
+
+
+ Once you update your DNS records, it may take up to 48 hours for it to be propogated.
+ Once the DNS propagation is complete you will need to come back and press the "Sync"
+ domains button
+
+
+
+
+
+
+
+
+
+