Skip to content

Commit

Permalink
refactor: add ui component for password to follow single responsibili…
Browse files Browse the repository at this point in the history
…ty principle
  • Loading branch information
chertik77 committed Jan 10, 2025
1 parent 1e0485f commit a201130
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 86 deletions.
97 changes: 23 additions & 74 deletions src/components/ui/Field.tsx
Original file line number Diff line number Diff line change
@@ -1,88 +1,37 @@
import type { InputHTMLAttributes } from 'react'
import type { FieldErrors } from 'react-hook-form'

import { forwardRef, useState } from 'react'
import { FiEye, FiEyeOff } from 'react-icons/fi'
import { forwardRef } from 'react'

import { cn } from 'lib'

type FieldProps = InputHTMLAttributes<HTMLInputElement> & {
inputName: string
isPasswordInput?: boolean
inputPasswordPlaceholder?: string
errors: FieldErrors
}

export const Field = forwardRef<HTMLInputElement, FieldProps>(
(
{
className,
inputName,
isPasswordInput,
inputPasswordPlaceholder,
errors,
...props
},
ref
) => {
const [showPassword, setShowPassword] = useState(false)

return (
<>
{isPasswordInput ? (
<div className='relative'>
<input
type={showPassword ? 'text' : 'password'}
placeholder={inputPasswordPlaceholder}
className={cn(
`hide-password-toggle peer h-2xl w-full rounded-lg border border-brand
border-opacity-40 bg-transparent px-lg pr-[35px] outline-none
placeholder:opacity-40 autofill:bg-clip-text autofill:text-fill-black
focus:border-opacity-100 violet:border-brand-secondary violet:border-opacity-40
violet:focus:border-opacity-100 dark:autofill:text-fill-white`,
className,
{
'mb-2': errors[inputName],
'mb-6': !errors[inputName]
}
)}
{...props}
ref={ref}
/>
<button
type='button'
className='focus-visible:styled-outline absolute right-lg top-4 opacity-40
peer-[.text-white]:text-white'
onClick={() => setShowPassword(prev => !prev)}>
{showPassword ? (
<FiEyeOff className='size-lg' />
) : (
<FiEye className='size-lg' />
)}
</button>
</div>
) : (
<input
type='text'
className={cn(
`mb-3.5 h-2xl w-full rounded-lg border border-brand border-opacity-40
bg-transparent px-lg outline-none placeholder:opacity-40 autofill:bg-clip-text
autofill:text-fill-black focus:border-opacity-100 violet:border-brand-secondary
violet:border-opacity-40 violet:focus:border-opacity-100
dark:autofill:text-fill-white`,
className,
errors[inputName] && 'mb-2'
)}
ref={ref}
{...props}
/>
)}
{errors[inputName] && (
<p className='mb-3.5 text-red-600'>
{errors[inputName]?.message as string}
</p>
({ className, inputName, errors, ...props }, ref) => (
<>
<input
type='text'
className={cn(
`mb-3.5 h-2xl w-full rounded-lg border border-brand border-opacity-40
bg-transparent px-lg outline-none placeholder:opacity-40 autofill:bg-clip-text
autofill:text-fill-black focus:border-opacity-100 violet:border-brand-secondary
violet:border-opacity-40 violet:focus:border-opacity-100
dark:autofill:text-fill-white`,
className,
errors[inputName] && 'mb-2'
)}
</>
)
}
ref={ref}
{...props}
/>
{errors[inputName] && (
<p className='mb-3.5 text-red-600'>
{errors[inputName]?.message as string}
</p>
)}
</>
)
)
55 changes: 55 additions & 0 deletions src/components/ui/PasswordField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { InputHTMLAttributes } from 'react'
import type { FieldErrors } from 'react-hook-form'

import { forwardRef, useState } from 'react'
import { FiEye, FiEyeOff } from 'react-icons/fi'

import { cn } from 'lib'

type PasswordFieldProps = InputHTMLAttributes<HTMLInputElement> & {
errors: FieldErrors
}

export const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
({ className, errors, ...props }, ref) => {
const [showPassword, setShowPassword] = useState(false)

return (
<>
<div className='relative'>
<input
type={showPassword ? 'text' : 'password'}
className={cn(
`hide-password-toggle peer h-2xl w-full rounded-lg border border-brand
border-opacity-40 bg-transparent px-lg pr-[35px] outline-none
placeholder:opacity-40 autofill:bg-clip-text autofill:text-fill-black
focus:border-opacity-100 violet:border-brand-secondary violet:border-opacity-40
violet:focus:border-opacity-100 dark:autofill:text-fill-white`,
className,
errors?.password && 'mb-2',
!errors?.password && 'mb-6'
)}
{...props}
ref={ref}
/>
<button
type='button'
className='focus-visible:styled-outline absolute right-lg top-4 opacity-40
peer-[.text-white]:text-white'
onClick={() => setShowPassword(prev => !prev)}>
{showPassword ? (
<FiEyeOff className='size-lg' />
) : (
<FiEye className='size-lg' />
)}
</button>
</div>
{errors?.password && (
<p className='mb-3.5 text-red-600'>
{errors?.password?.message as string}
</p>
)}
</>
)
}
)
7 changes: 3 additions & 4 deletions src/features/auth/components/SigninForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useSigninUser } from 'features/auth/hooks'

import { Button, Field, Loader } from 'components/ui'
import { PasswordField } from 'components/ui/PasswordField'
import { useAppForm } from 'hooks'

import { SigninSchema } from '../auth.schema'
Expand All @@ -21,13 +22,11 @@ export const SigninForm = () => {
autoComplete='email'
errors={formState.errors}
/>
<Field
<PasswordField
{...register('password')}
isPasswordInput
autoComplete='current-password'
className='text-white autofill:text-fill-white'
inputPasswordPlaceholder='Confirm a password'
inputName='password'
placeholder='Confirm a password'
errors={formState.errors}
/>
<Button
Expand Down
7 changes: 3 additions & 4 deletions src/features/auth/components/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useSignupUser } from 'features/auth/hooks'

import { Button, Field, Loader } from 'components/ui'
import { PasswordField } from 'components/ui/PasswordField'
import { useAppForm } from 'hooks'

import { SignupSchema } from '../auth.schema'
Expand Down Expand Up @@ -29,13 +30,11 @@ export const SignupForm = () => {
placeholder='Enter your email'
{...register('email')}
/>
<Field
<PasswordField
errors={formState.errors}
inputName='password'
autoComplete='new-password'
className='text-white autofill:text-fill-white'
inputPasswordPlaceholder='Create a password'
isPasswordInput
placeholder='Create a password'
{...register('password')}
/>
<Button
Expand Down
7 changes: 3 additions & 4 deletions src/features/user/components/modals/EditProfileModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { EditUserSchema } from 'features/user/user.schema'
import { selectUser } from 'features/user/user.slice'

import { Button, Field, Loader, Modal } from 'components/ui'
import { PasswordField } from 'components/ui/PasswordField'
import { useAppForm, useIsFormReadyForSubmit } from 'hooks'
import { useAppSelector } from 'hooks/redux'

Expand Down Expand Up @@ -46,12 +47,10 @@ export const EditProfileModal = () => {
placeholder='Enter your email'
{...register('email', { setValueAs: value => value.trim() })}
/>
<Field
<PasswordField
errors={formState.errors}
inputName='password'
autoComplete='new-password'
inputPasswordPlaceholder='Create a password'
isPasswordInput
placeholder='Create a password'
{...register('password', {
setValueAs: value => (!value ? undefined : value.trim())
})}
Expand Down

0 comments on commit a201130

Please sign in to comment.