diff --git a/.vscode/settings.json b/.vscode/settings.json index 97d5d1948..82aa3c1a3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "typescript.tsdk": "node_modules/typescript/lib", "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "eslint.validate": ["typescript", "typescriptreact", "javascript", "javascriptreact"], "javascript.preferences.importModuleSpecifier": "non-relative", 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 index eac574181..eafed5500 100644 --- a/apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx +++ b/apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx @@ -23,6 +23,7 @@ import { FormMessage, } from '@documenso/ui/primitives/form/form'; import { Input } from '@documenso/ui/primitives/input'; +import { PasswordInput } from '@documenso/ui/primitives/password-input'; import { useToast } from '@documenso/ui/primitives/use-toast'; export const ZDisableTwoFactorAuthenticationForm = z.object({ @@ -107,38 +108,42 @@ export const DisableAuthenticatorAppDialog = ({ )} className="flex flex-col gap-y-4" > - ( - - Password - - - - - - )} - /> +
+ ( + + Password + + + + + + )} + /> - ( - - Backup Code - - - - - - )} - /> + ( + + Backup Code + + + + + + )} + /> +
- + + + ); }; diff --git a/apps/web/src/components/forms/password.tsx b/apps/web/src/components/forms/password.tsx index 47cba1e88..0eb491537 100644 --- a/apps/web/src/components/forms/password.tsx +++ b/apps/web/src/components/forms/password.tsx @@ -1,23 +1,25 @@ 'use client'; -import { useState } from 'react'; - import { zodResolver } from '@hookform/resolvers/zod'; -import { Eye, EyeOff, Loader } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; -import { User } from '@documenso/prisma/client'; +import type { User } from '@documenso/prisma/client'; import { TRPCClientError } from '@documenso/trpc/client'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; -import { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@documenso/ui/primitives/form/form'; +import { PasswordInput } from '@documenso/ui/primitives/password-input'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { FormErrorMessage } from '../form/form-error-message'; - export const ZPasswordFormSchema = z .object({ currentPassword: z @@ -48,16 +50,7 @@ export type PasswordFormProps = { export const PasswordForm = ({ className }: PasswordFormProps) => { const { toast } = useToast(); - const [showPassword, setShowPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const [showCurrentPassword, setShowCurrentPassword] = useState(false); - - const { - register, - handleSubmit, - reset, - formState: { errors, isSubmitting }, - } = useForm({ + const form = useForm({ values: { currentPassword: '', password: '', @@ -66,6 +59,8 @@ export const PasswordForm = ({ className }: PasswordFormProps) => { resolver: zodResolver(ZPasswordFormSchema), }); + const isSubmitting = form.formState.isSubmitting; + const { mutateAsync: updatePassword } = trpc.profile.updatePassword.useMutation(); const onFormSubmit = async ({ currentPassword, password }: TPasswordFormSchema) => { @@ -75,7 +70,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => { password, }); - reset(); + form.reset(); toast({ title: 'Password updated', @@ -101,117 +96,61 @@ export const PasswordForm = ({ className }: PasswordFormProps) => { }; return ( -
-
- - -
- + +
+ ( + + Current Password + + + + + + )} /> - -
- - -
-
- - -
- - -
- - -
- -
- - -
- + -
- - -
- -
- -
-
+ + ); }; diff --git a/apps/web/src/components/forms/profile.tsx b/apps/web/src/components/forms/profile.tsx index 6f611bed9..0ce5c7f3d 100644 --- a/apps/web/src/components/forms/profile.tsx +++ b/apps/web/src/components/forms/profile.tsx @@ -3,22 +3,27 @@ import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; -import { Loader } from 'lucide-react'; -import { Controller, useForm } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import { z } from 'zod'; -import { User } from '@documenso/prisma/client'; +import type { User } from '@documenso/prisma/client'; import { TRPCClientError } from '@documenso/trpc/client'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@documenso/ui/primitives/form/form'; import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { FormErrorMessage } from '../form/form-error-message'; - export const ZProfileFormSchema = z.object({ name: z.string().trim().min(1, { message: 'Please enter a valid name.' }), signature: z.string().min(1, 'Signature Pad cannot be empty'), @@ -36,12 +41,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => { const { toast } = useToast(); - const { - register, - control, - handleSubmit, - formState: { errors, isSubmitting }, - } = useForm({ + const form = useForm({ values: { name: user.name ?? '', signature: user.signature || '', @@ -49,6 +49,8 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => { resolver: zodResolver(ZProfileFormSchema), }); + const isSubmitting = form.formState.isSubmitting; + const { mutateAsync: updateProfile } = trpc.profile.updateProfile.useMutation(); const onFormSubmit = async ({ name, signature }: TProfileFormSchema) => { @@ -84,56 +86,57 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => { }; return ( -
-
- - - - - -
- -
- - - -
- -
- - -
- ( - onChange(v ?? '')} - /> + + +
+ ( + + Full Name + + + + + )} /> - -
-
-
- -
-
+ + ); }; diff --git a/apps/web/src/components/forms/reset-password.tsx b/apps/web/src/components/forms/reset-password.tsx index 47f423d76..354584f6e 100644 --- a/apps/web/src/components/forms/reset-password.tsx +++ b/apps/web/src/components/forms/reset-password.tsx @@ -1,11 +1,8 @@ 'use client'; -import { useState } from 'react'; - import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; -import { Eye, EyeOff } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; @@ -13,9 +10,15 @@ import { TRPCClientError } from '@documenso/trpc/client'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; -import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; -import { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@documenso/ui/primitives/form/form'; +import { PasswordInput } from '@documenso/ui/primitives/password-input'; import { useToast } from '@documenso/ui/primitives/use-toast'; export const ZResetPasswordFormSchema = z @@ -40,15 +43,7 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps) const { toast } = useToast(); - const [showPassword, setShowPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = useState(false); - - const { - register, - reset, - handleSubmit, - formState: { errors, isSubmitting }, - } = useForm({ + const form = useForm({ values: { password: '', repeatedPassword: '', @@ -56,6 +51,8 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps) resolver: zodResolver(ZResetPasswordFormSchema), }); + const isSubmitting = form.formState.isSubmitting; + const { mutateAsync: resetPassword } = trpc.profile.resetPassword.useMutation(); const onFormSubmit = async ({ password }: Omit) => { @@ -65,7 +62,7 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps) token, }); - reset(); + form.reset(); toast({ title: 'Password updated', @@ -93,81 +90,45 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps) }; return ( -
-
- - -
- + +
+ ( + + Password + + + + + + )} /> - -
- - -
- -
- - -
- + - -
- - -
- - -
+ + + ); }; diff --git a/apps/web/src/components/forms/signin.tsx b/apps/web/src/components/forms/signin.tsx index 0d7dd723f..4e671a569 100644 --- a/apps/web/src/components/forms/signin.tsx +++ b/apps/web/src/components/forms/signin.tsx @@ -12,9 +12,16 @@ 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, PasswordInput } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@documenso/ui/primitives/form/form'; +import { Input } from '@documenso/ui/primitives/input'; +import { PasswordInput } from '@documenso/ui/primitives/password-input'; import { useToast } from '@documenso/ui/primitives/use-toast'; const ERROR_MESSAGES: Partial> = { @@ -52,12 +59,7 @@ export const SignInForm = ({ className }: SignInFormProps) => { 'totp' | 'backup' >('totp'); - const { - register, - handleSubmit, - setValue, - formState: { errors, isSubmitting }, - } = useForm({ + const form = useForm({ values: { email: '', password: '', @@ -67,9 +69,11 @@ export const SignInForm = ({ className }: SignInFormProps) => { resolver: zodResolver(ZSignInFormSchema), }); + const isSubmitting = form.formState.isSubmitting; + const onCloseTwoFactorAuthenticationDialog = () => { - setValue('totpCode', ''); - setValue('backupCode', ''); + form.setValue('totpCode', ''); + form.setValue('backupCode', ''); setIsTwoFactorAuthenticationDialogOpen(false); }; @@ -78,11 +82,11 @@ export const SignInForm = ({ className }: SignInFormProps) => { const method = twoFactorAuthenticationMethod === 'totp' ? 'backup' : 'totp'; if (method === 'totp') { - setValue('backupCode', ''); + form.setValue('backupCode', ''); } if (method === 'backup') { - setValue('totpCode', ''); + form.setValue('totpCode', ''); } setTwoFactorAuthenticationMethod(method); @@ -113,7 +117,6 @@ export const SignInForm = ({ className }: SignInFormProps) => { if (result?.error && isErrorCode(result.error)) { if (result.error === TwoFactorEnabledErrorCode) { setIsTwoFactorAuthenticationDialogOpen(true); - return; } @@ -156,64 +159,68 @@ export const SignInForm = ({ className }: SignInFormProps) => { }; return ( -
-
- - - - - -
- -
- - - - - -
- - +
+ ( + + Email + + + + + + )} + /> -
-
- Or continue with -
-
+ ( + + Password + + + + + + )} + /> +
- + +
+
+ Or continue with +
+
+ + + { Two-Factor Authentication -
- {twoFactorAuthenticationMethod === 'totp' && ( -
- - - +
+ {twoFactorAuthenticationMethod === 'totp' && ( + ( + + Authentication Token + + + + + + )} /> + )} - -
- )} - - {twoFactorAuthenticationMethod === 'backup' && ( -
- - - ( + + Backup Code + + + + + + )} /> - - -
- )} + )} +
- + ); }; diff --git a/apps/web/src/components/forms/signup.tsx b/apps/web/src/components/forms/signup.tsx index 862f4f83e..b91b4a9fd 100644 --- a/apps/web/src/components/forms/signup.tsx +++ b/apps/web/src/components/forms/signup.tsx @@ -1,11 +1,8 @@ 'use client'; -import { useState } from 'react'; - import { zodResolver } from '@hookform/resolvers/zod'; -import { Eye, EyeOff } from 'lucide-react'; import { signIn } from 'next-auth/react'; -import { Controller, useForm } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; @@ -13,9 +10,16 @@ import { TRPCClientError } from '@documenso/trpc/client'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; -import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@documenso/ui/primitives/form/form'; import { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; +import { PasswordInput } from '@documenso/ui/primitives/password-input'; import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { useToast } from '@documenso/ui/primitives/use-toast'; @@ -38,14 +42,8 @@ export type SignUpFormProps = { export const SignUpForm = ({ className }: SignUpFormProps) => { const { toast } = useToast(); const analytics = useAnalytics(); - const [showPassword, setShowPassword] = useState(false); - const { - control, - register, - handleSubmit, - formState: { errors, isSubmitting }, - } = useForm({ + const form = useForm({ values: { name: '', email: '', @@ -55,6 +53,8 @@ export const SignUpForm = ({ className }: SignUpFormProps) => { resolver: zodResolver(ZSignUpFormSchema), }); + const isSubmitting = form.formState.isSubmitting; + const { mutateAsync: signup } = trpc.auth.signup.useMutation(); const onFormSubmit = async ({ name, email, password, signature }: TSignUpFormSchema) => { @@ -90,93 +90,83 @@ export const SignUpForm = ({ className }: SignUpFormProps) => { }; return ( -
-
- - - - - {errors.name && {errors.name.message}} -
- -
- - - - - {errors.email && {errors.email.message}} -
- -
- - -
- + +
+ ( + + Name + + + + + + )} /> - -
- -
+ /> -
- + ( + + Password + + + + + + )} + /> -
- ( - onChange(v ?? '')} - /> + + Sign Here + + onChange(v ?? '')} + /> + + + + )} /> -
+ - -
- - -
+ + + ); }; diff --git a/packages/ui/primitives/button.tsx b/packages/ui/primitives/button.tsx index 31df69dee..5754b35a5 100644 --- a/packages/ui/primitives/button.tsx +++ b/packages/ui/primitives/button.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import { Slot } from '@radix-ui/react-slot'; -import { VariantProps, cva } from 'class-variance-authority'; +import type { VariantProps } from 'class-variance-authority'; +import { cva } from 'class-variance-authority'; import { Loader } from 'lucide-react'; import { cn } from '../lib/utils'; @@ -63,8 +64,8 @@ const Button = React.forwardRef( ); } - const showLoader = loading === true; - const isDisabled = props.disabled || showLoader; + const isLoading = loading === true; + const isDisabled = props.disabled || isLoading; return ( ); diff --git a/packages/ui/primitives/input.tsx b/packages/ui/primitives/input.tsx index ac739c984..1a5fba1bb 100644 --- a/packages/ui/primitives/input.tsx +++ b/packages/ui/primitives/input.tsx @@ -1,9 +1,6 @@ import * as React from 'react'; -import { Eye, EyeOff } from 'lucide-react'; - import { cn } from '../lib/utils'; -import { Button } from './button'; export type InputProps = React.InputHTMLAttributes; @@ -28,38 +25,4 @@ const Input = React.forwardRef( Input.displayName = 'Input'; -const PasswordInput = React.forwardRef( - ({ className, ...props }, ref) => { - const [showPassword, setShowPassword] = React.useState(false); - - return ( -
- - - -
- ); - }, -); - -PasswordInput.displayName = 'Input'; - -export { Input, PasswordInput }; +export { Input }; diff --git a/packages/ui/primitives/password-input.tsx b/packages/ui/primitives/password-input.tsx new file mode 100644 index 000000000..502344a02 --- /dev/null +++ b/packages/ui/primitives/password-input.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; + +import { Eye, EyeOff } from 'lucide-react'; + +import { cn } from '../lib/utils'; +import { Button } from './button'; +import { Input, InputProps } from './input'; + +const PasswordInput = React.forwardRef>( + ({ className, ...props }, ref) => { + const [showPassword, setShowPassword] = React.useState(false); + + return ( +
+ + + +
+ ); + }, +); + +PasswordInput.displayName = 'PasswordInput'; + +export { PasswordInput };