Skip to content

Commit

Permalink
feat: add hook for disabling button when modal values are the same
Browse files Browse the repository at this point in the history
  • Loading branch information
chertik77 committed Dec 26, 2024
1 parent b5d048b commit 212caf8
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 95 deletions.
19 changes: 11 additions & 8 deletions src/blocks/sidebar/SidebarListActiveItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { Board } from 'features/kanban/board/board.types'
import type {
Board,
EditBoardModalProps
} from 'features/kanban/board/board.types'

import { useModal } from 'react-modal-state'

Expand All @@ -7,20 +10,20 @@ import { useDeleteBoard } from 'features/kanban/board/hooks'

import { SidebarMobileModal } from './SidebarMobileModal'

export const SidebarListActiveItem = ({
board: { icon, title, background }
}: {
board: Board
}) => {
export const SidebarListActiveItem = ({ board }: { board: Board }) => {
const { open: openEditBoardModal } = useModal(EditBoardModal)

const { close: closeSidebarMobileModal } = useModal(SidebarMobileModal)

const { mutate: deleteBoard } = useDeleteBoard()

const handleBoardEdit = () => {
close()
openEditBoardModal({ icon, title, background: background.identifier })
closeSidebarMobileModal()
openEditBoardModal<EditBoardModalProps>({
title: board.title,
icon: board.icon,
background: board.background.identifier
})
}

return (
Expand Down
5 changes: 4 additions & 1 deletion src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@ import { SpeedInsights } from '@vercel/speed-insights/react'
import { Outlet } from 'react-router-dom'
import { Toaster } from 'sonner'

import { useTabletAndBelowMediaQuery } from 'hooks'
import { useAppSelector } from 'hooks/redux'

import { selectUserTheme } from 'redux/user.slice'

export const Layout = () => {
const theme = useAppSelector(selectUserTheme)

const isTabletAndBelow = useTabletAndBelowMediaQuery()

return (
<>
<Outlet />
<Analytics />
<SpeedInsights />
<Toaster
position='top-right'
position={isTabletAndBelow ? 'top-center' : 'bottom-right'}
richColors
closeButton
duration={5000}
Expand Down
9 changes: 8 additions & 1 deletion src/features/kanban/board/board.types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import type { Column } from '../column/column.types'
import type { Icon } from './board.constants'

export type Board = {
id: string
title: string
icon: string
icon: Icon
background: {
hasWhiteTextColor: boolean
identifier: string
url: string
}
columns: Column[]
}

export type EditBoardModalProps = {
title: string
icon: Icon
background: string
}
35 changes: 11 additions & 24 deletions src/features/kanban/board/components/modals/EditBoardModal.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,33 @@
import type { Icon } from 'features/kanban/board/board.constants'
import type { EditBoardModalProps } from 'features/kanban/board/board.types'

import { useEffect } from 'react'
import { useModalInstance } from 'react-modal-state'
import { keyof } from 'valibot'

import { BoardSchema } from 'features/kanban/board/board.schema'
import { useEditBoard } from 'features/kanban/board/hooks'

import { Button, Field, Modal } from 'components/ui'
import { useAppForm } from 'hooks'
import { useAppForm, useIsFormReadyForSubmit } from 'hooks'

import { RadioInputBgImages } from './RadioInputBgImages'
import { RadioInputIcons } from './RadioInputIcons'

export const EditBoardModal = () => {
const {
data: { background, title, icon }
} = useModalInstance<{
background: string
title: string
icon: Icon
}>()

const { register, reset, handleSubmit, control, formState } = useAppForm(
BoardSchema,
{ defaultValues: { icon, background } }
)
const { data: board } = useModalInstance<EditBoardModalProps>()

const { register, reset, handleSubmit, control, formState, watch } =
useAppForm(BoardSchema, { shouldUnregister: false })

const { mutate: editBoard, isPending } = useEditBoard(reset)

useEffect(() => {
reset({ icon, title, background })
}, [background, icon, title, reset])
const { isFormReadyForSubmit } = useIsFormReadyForSubmit(board, watch)

const isFormReadyForSubmit = keyof(BoardSchema).options.some(
f => formState.dirtyFields[f]
)
useEffect(() => {
reset(board)
}, [reset, board])

return (
<Modal
modalTitle='Edit board'
onAnimationEnd={() => reset({}, { keepDefaultValues: true })}>
<Modal modalTitle='Edit board'>
<form onSubmit={handleSubmit(data => editBoard(data))}>
<Field
{...register('title', { setValueAs: value => value.trim() })}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { useAppSelector } from 'hooks/redux'

import { selectUserTheme } from 'redux/user.slice'

import { cn } from 'lib'

type RadioInputBgImagesProps = {
control: Control<BoardSchema>
}
Expand All @@ -33,9 +31,7 @@ export const RadioInputBgImages = ({ control }: RadioInputBgImagesProps) => {
<Item
checked={field.value === id}
value={id}
className={cn(
'focus-visible:styled-outline group outline-offset-4'
)}
className='focus-visible:styled-outline group outline-offset-4'
key={id}>
<img
className='group-aria-checked:scale-125'
Expand Down
2 changes: 2 additions & 0 deletions src/features/kanban/card/card.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export type Card = {
priority: Priority
deadline: Date
}

export type EditCardModalProps = Omit<Card, 'columnId' | 'order'>
14 changes: 12 additions & 2 deletions src/features/kanban/card/components/BoardCardActions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Card } from '../card.types'
import type { Card, EditCardModalProps } from '../card.types'

import { isToday } from 'date-fns'
import { useModal } from 'react-modal-state'
Expand All @@ -14,6 +14,16 @@ export const BoardCardActions = ({ card }: { card: Card }) => {

const { mutate: deleteCard } = useDeleteCard()

const handleEditCardModal = () => {
openEditCardModal<EditCardModalProps>({
id: card.id,
title: card.title,
description: card.description,
priority: card.priority,
deadline: new Date(card.deadline)
})
}

return (
<div className='ml-auto flex gap-2'>
{isToday(card.deadline) && (
Expand All @@ -22,7 +32,7 @@ export const BoardCardActions = ({ card }: { card: Card }) => {
</svg>
)}
<Button
onClick={() => openEditCardModal(card)}
onClick={handleEditCardModal}
iconName='pencil'
/>
<Button
Expand Down
31 changes: 11 additions & 20 deletions src/features/kanban/card/components/modals/EditCardModal.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,37 @@
import type { Card } from 'features/kanban/card/card.types'
import type { EditCardModalProps } from 'features/kanban/card/card.types'

import { useEffect } from 'react'
import { useModalInstance } from 'react-modal-state'
import { keyof } from 'valibot'

import { CardSchema } from 'features/kanban/card/card.schema'
import { useEditCard } from 'features/kanban/card/hooks'

import { Button, Field, Modal } from 'components/ui'
import { useAppForm } from 'hooks'
import { useAppForm, useIsFormReadyForSubmit } from 'hooks'

import { DatePicker } from '../ui'
import { ModalDescription } from './ModalDescription'
import { ModalPriorities } from './ModalPriorities'

export const EditCardModal = () => {
const {
data: { title, description, id, priority, deadline }
} = useModalInstance<Card>()
const { data: card } = useModalInstance<EditCardModalProps>()

const { register, handleSubmit, formState, control, reset } = useAppForm(
CardSchema,
{ defaultValues: { priority, deadline: new Date(deadline) } }
)
const { register, handleSubmit, formState, control, reset, watch } =
useAppForm(CardSchema, { shouldUnregister: false })

const { mutate: editCard, isPending } = useEditCard(reset)

useEffect(() => {
reset({ title, priority, deadline: new Date(deadline), description })
}, [deadline, description, priority, title, reset])
const { isFormReadyForSubmit } = useIsFormReadyForSubmit(card, watch)

const isFormReadyForSubmit = keyof(CardSchema).options.some(
f => formState.dirtyFields[f]
)
useEffect(() => {
reset(card)
}, [reset, card])

return (
<Modal
modalTitle='Edit card'
onAnimationEnd={() => reset({}, { keepDefaultValues: true })}>
<Modal modalTitle='Edit card'>
<form
onSubmit={handleSubmit(data =>
editCard({ cardId: id, cardData: data })
editCard({ cardId: card.id, cardData: data })
)}>
<Field
errors={formState.errors}
Expand Down
31 changes: 12 additions & 19 deletions src/features/kanban/card/components/modals/ModalPriorities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,18 @@ export const ModalPriorities = ({ control }: ModalPrioritiesProps) => (
<Controller
name='priority'
control={control}
render={({ field, formState }) => (
<>
<Root
onValueChange={field.onChange}
className='mb-3.5 flex gap-2'>
{PRIORITIES.map(priority => (
<RadioInput
checked={priority === field.value}
value={priority}
key={priority}
/>
))}
</Root>
{formState.errors.priority && (
<p className='mb-3.5 text-red-600'>
{formState.errors.priority.message}
</p>
)}
</>
render={({ field }) => (
<Root
onValueChange={field.onChange}
className='mb-3.5 flex gap-2'>
{PRIORITIES.map(priority => (
<RadioInput
checked={priority === field.value}
value={priority}
key={priority}
/>
))}
</Root>
)}
/>
</>
Expand Down
3 changes: 2 additions & 1 deletion src/features/kanban/card/components/ui/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { Content, Popover, Portal, Trigger } from '@radix-ui/react-popover'
import { Controller } from 'react-hook-form'
import { MdKeyboardArrowDown } from 'react-icons/md'

import { formatTodayDate } from '../../utils'
import { formatTodayDate } from 'features/kanban/card/utils'

import { Calendar } from './Calendar'

type DatePickerProps = {
Expand Down
27 changes: 13 additions & 14 deletions src/features/user/components/modals/EditProfileModal.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
import { useEffect } from 'react'
import { keyof } from 'valibot'

import { useEditProfile } from 'features/user/hooks'
import { EditUserSchema } from 'features/user/user.schema'

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

import { selectUser } from 'redux/user.slice'

import { EditAvatar } from './EditAvatar'

export const EditProfileModal = () => {
const { name: initialName, email: initialEmail } = useAppSelector(selectUser)
const { name, email } = useAppSelector(selectUser)

const { mutate: editProfile, isPending } = useEditProfile()

const { handleSubmit, register, formState, reset } =
const { handleSubmit, register, formState, reset, watch } =
useAppForm(EditUserSchema)

useEffect(() => {
reset({ name: initialName, email: initialEmail })
}, [initialEmail, initialName, reset])

const isFormReadyForSubmit = keyof(EditUserSchema).options.some(
f => formState.dirtyFields[f] && formState.isValid
const { isFormReadyForSubmit } = useIsFormReadyForSubmit(
{ name, email, password: undefined },
watch,
({ password }) => (password ? formState.isValid : true)
)

useEffect(() => {
reset({ name, email })
}, [email, name, reset])

return (
<Modal
modalTitle='Edit profile'
onAnimationEnd={() => reset({}, { keepDefaultValues: true })}>
<Modal modalTitle='Edit profile'>
<form onSubmit={handleSubmit(data => editProfile(data))}>
<EditAvatar changeUserAvatar={editProfile} />
<Field
Expand All @@ -58,7 +57,7 @@ export const EditProfileModal = () => {
/>
<Button
type='submit'
disabled={isPending || !isFormReadyForSubmit}>
disabled={isPending || !formState.isValid || !isFormReadyForSubmit}>
{isPending ? <Loader /> : 'Send'}
</Button>
</form>
Expand Down
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './useAppForm'
export * from './useDocumentTitle'
export * from './useIsFormReadyForSubmit'
export * from './useTabletAndBelowMediaQuery'
1 change: 1 addition & 0 deletions src/hooks/useAppForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const useAppForm = <S extends BaseSchema>(
) =>
useForm<Input<S>>({
resolver: valibotResolver(schema),
shouldUnregister: true,
mode: 'onChange',
...options
})
Loading

0 comments on commit 212caf8

Please sign in to comment.