Skip to content

Commit 397f46b

Browse files
committed
refactor: optimize states in users/tasks context (#71)
* refactor: optimize states in tasks context * refactor: optimize states in users context
1 parent c47d3df commit 397f46b

10 files changed

+217
-185
lines changed

src/features/tasks/components/data-table-row-actions.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
DropdownMenuSubTrigger,
1616
DropdownMenuTrigger,
1717
} from '@/components/ui/dropdown-menu'
18-
import { useTasksContext } from '../context/tasks-context'
18+
import { useTasks } from '../context/tasks-context'
1919
import { labels } from '../data/data'
2020
import { taskSchema } from '../data/schema'
2121

@@ -28,7 +28,7 @@ export function DataTableRowActions<TData>({
2828
}: DataTableRowActionsProps<TData>) {
2929
const task = taskSchema.parse(row.original)
3030

31-
const { setOpen, setCurrentRow } = useTasksContext()
31+
const { setOpen, setCurrentRow } = useTasks()
3232

3333
return (
3434
<DropdownMenu modal={false}>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { toast } from '@/hooks/use-toast'
2+
import { ConfirmDialog } from '@/components/confirm-dialog'
3+
import { useTasks } from '../context/tasks-context'
4+
import { TasksImportDialog } from './tasks-import-dialog'
5+
import { TasksMutateDrawer } from './tasks-mutate-drawer'
6+
7+
export function TasksDialogs() {
8+
const { open, setOpen, currentRow, setCurrentRow } = useTasks()
9+
return (
10+
<>
11+
<TasksMutateDrawer
12+
key='task-create'
13+
open={open === 'create'}
14+
onOpenChange={() => setOpen('create')}
15+
/>
16+
17+
<TasksImportDialog
18+
key='tasks-import'
19+
open={open === 'import'}
20+
onOpenChange={() => setOpen('import')}
21+
/>
22+
23+
{currentRow && (
24+
<>
25+
<TasksMutateDrawer
26+
key={`task-update-${currentRow.id}`}
27+
open={open === 'update'}
28+
onOpenChange={() => {
29+
setOpen('update')
30+
setTimeout(() => {
31+
setCurrentRow(null)
32+
}, 500)
33+
}}
34+
currentRow={currentRow}
35+
/>
36+
37+
<ConfirmDialog
38+
key='task-delete'
39+
destructive
40+
open={open === 'delete'}
41+
onOpenChange={() => {
42+
setOpen('delete')
43+
setTimeout(() => {
44+
setCurrentRow(null)
45+
}, 500)
46+
}}
47+
handleConfirm={() => {
48+
setOpen(null)
49+
setTimeout(() => {
50+
setCurrentRow(null)
51+
}, 500)
52+
toast({
53+
title: 'The following task has been deleted:',
54+
description: (
55+
<pre className='mt-2 w-[340px] rounded-md bg-slate-950 p-4'>
56+
<code className='text-white'>
57+
{JSON.stringify(currentRow, null, 2)}
58+
</code>
59+
</pre>
60+
),
61+
})
62+
}}
63+
className='max-w-md'
64+
title={`Delete this task: ${currentRow.id} ?`}
65+
desc={
66+
<>
67+
You are about to delete a task with the ID{' '}
68+
<strong>{currentRow.id}</strong>. <br />
69+
This action cannot be undone.
70+
</>
71+
}
72+
confirmText='Delete'
73+
/>
74+
</>
75+
)}
76+
</>
77+
)
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { IconDownload, IconPlus } from '@tabler/icons-react'
2+
import { Button } from '@/components/ui/button'
3+
import { useTasks } from '../context/tasks-context'
4+
5+
export function TasksPrimaryButtons() {
6+
const { setOpen } = useTasks()
7+
return (
8+
<div className='flex gap-2'>
9+
<Button
10+
variant='outline'
11+
className='space-x-1'
12+
onClick={() => setOpen('import')}
13+
>
14+
<span>Import</span> <IconDownload size={18} />
15+
</Button>
16+
<Button className='space-x-1' onClick={() => setOpen('create')}>
17+
<span>Create</span> <IconPlus size={18} />
18+
</Button>
19+
</div>
20+
)
21+
}

src/features/tasks/context/tasks-context.tsx

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import React from 'react'
1+
import React, { useState } from 'react'
2+
import useDialogState from '@/hooks/use-dialog-state'
23
import { Task } from '../data/schema'
34

4-
export type TasksDialogType = 'create' | 'update' | 'delete' | 'import'
5+
type TasksDialogType = 'create' | 'update' | 'delete' | 'import'
56

67
interface TasksContextType {
78
open: TasksDialogType | null
@@ -14,21 +15,24 @@ const TasksContext = React.createContext<TasksContextType | null>(null)
1415

1516
interface Props {
1617
children: React.ReactNode
17-
value: TasksContextType
1818
}
1919

20-
export default function TasksContextProvider({ children, value }: Props) {
21-
return <TasksContext.Provider value={value}>{children}</TasksContext.Provider>
20+
export default function TasksProvider({ children }: Props) {
21+
const [open, setOpen] = useDialogState<TasksDialogType>(null)
22+
const [currentRow, setCurrentRow] = useState<Task | null>(null)
23+
return (
24+
<TasksContext value={{ open, setOpen, currentRow, setCurrentRow }}>
25+
{children}
26+
</TasksContext>
27+
)
2228
}
2329

2430
// eslint-disable-next-line react-refresh/only-export-components
25-
export const useTasksContext = () => {
31+
export const useTasks = () => {
2632
const tasksContext = React.useContext(TasksContext)
2733

2834
if (!tasksContext) {
29-
throw new Error(
30-
'useTasksContext has to be used within <TasksContext.Provider>'
31-
)
35+
throw new Error('useTasks has to be used within <TasksContext>')
3236
}
3337

3438
return tasksContext

src/features/tasks/index.tsx

+7-94
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
1-
import { useState } from 'react'
2-
import { IconDownload, IconPlus } from '@tabler/icons-react'
3-
import useDialogState from '@/hooks/use-dialog-state'
4-
import { toast } from '@/hooks/use-toast'
5-
import { Button } from '@/components/ui/button'
6-
import { ConfirmDialog } from '@/components/confirm-dialog'
71
import { Header } from '@/components/layout/header'
82
import { Main } from '@/components/layout/main'
93
import { ProfileDropdown } from '@/components/profile-dropdown'
104
import { Search } from '@/components/search'
115
import { ThemeSwitch } from '@/components/theme-switch'
126
import { columns } from './components/columns'
137
import { DataTable } from './components/data-table'
14-
import { TasksImportDialog } from './components/tasks-import-dialog'
15-
import { TasksMutateDrawer } from './components/tasks-mutate-drawer'
16-
import TasksContextProvider, { TasksDialogType } from './context/tasks-context'
17-
import { Task } from './data/schema'
8+
import { TasksDialogs } from './components/tasks-dialogs'
9+
import { TasksPrimaryButtons } from './components/tasks-primary-buttons'
10+
import TasksProvider from './context/tasks-context'
1811
import { tasks } from './data/tasks'
1912

2013
export default function Tasks() {
21-
// Local states
22-
const [currentRow, setCurrentRow] = useState<Task | null>(null)
23-
const [open, setOpen] = useDialogState<TasksDialogType>(null)
24-
2514
return (
26-
<TasksContextProvider value={{ open, setOpen, currentRow, setCurrentRow }}>
27-
{/* ===== Top Heading ===== */}
15+
<TasksProvider>
2816
<Header fixed>
2917
<Search />
3018
<div className='ml-auto flex items-center space-x-4'>
@@ -41,89 +29,14 @@ export default function Tasks() {
4129
Here&apos;s a list of your tasks for this month!
4230
</p>
4331
</div>
44-
<div className='flex gap-2'>
45-
<Button
46-
variant='outline'
47-
className='space-x-1'
48-
onClick={() => setOpen('import')}
49-
>
50-
<span>Import</span> <IconDownload size={18} />
51-
</Button>
52-
<Button className='space-x-1' onClick={() => setOpen('create')}>
53-
<span>Create</span> <IconPlus size={18} />
54-
</Button>
55-
</div>
32+
<TasksPrimaryButtons />
5633
</div>
5734
<div className='-mx-4 flex-1 overflow-auto px-4 py-1 lg:flex-row lg:space-x-12 lg:space-y-0'>
5835
<DataTable data={tasks} columns={columns} />
5936
</div>
6037
</Main>
6138

62-
<TasksMutateDrawer
63-
key='task-create'
64-
open={open === 'create'}
65-
onOpenChange={() => setOpen('create')}
66-
/>
67-
68-
<TasksImportDialog
69-
key='tasks-import'
70-
open={open === 'import'}
71-
onOpenChange={() => setOpen('import')}
72-
/>
73-
74-
{currentRow && (
75-
<>
76-
<TasksMutateDrawer
77-
key={`task-update-${currentRow.id}`}
78-
open={open === 'update'}
79-
onOpenChange={() => {
80-
setOpen('update')
81-
setTimeout(() => {
82-
setCurrentRow(null)
83-
}, 500)
84-
}}
85-
currentRow={currentRow}
86-
/>
87-
88-
<ConfirmDialog
89-
key='task-delete'
90-
destructive
91-
open={open === 'delete'}
92-
onOpenChange={() => {
93-
setOpen('delete')
94-
setTimeout(() => {
95-
setCurrentRow(null)
96-
}, 500)
97-
}}
98-
handleConfirm={() => {
99-
setOpen(null)
100-
setTimeout(() => {
101-
setCurrentRow(null)
102-
}, 500)
103-
toast({
104-
title: 'The following task has been deleted:',
105-
description: (
106-
<pre className='mt-2 w-[340px] rounded-md bg-slate-950 p-4'>
107-
<code className='text-white'>
108-
{JSON.stringify(currentRow, null, 2)}
109-
</code>
110-
</pre>
111-
),
112-
})
113-
}}
114-
className='max-w-md'
115-
title={`Delete this task: ${currentRow.id} ?`}
116-
desc={
117-
<>
118-
You are about to delete a task with the ID{' '}
119-
<strong>{currentRow.id}</strong>. <br />
120-
This action cannot be undone.
121-
</>
122-
}
123-
confirmText='Delete'
124-
/>
125-
</>
126-
)}
127-
</TasksContextProvider>
39+
<TasksDialogs />
40+
</TasksProvider>
12841
)
12942
}

src/features/users/components/data-table-row-actions.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import {
1010
DropdownMenuShortcut,
1111
DropdownMenuTrigger,
1212
} from '@/components/ui/dropdown-menu'
13-
import { useUsersContext } from '../context/users-context'
13+
import { useUsers } from '../context/users-context'
1414
import { User } from '../data/schema'
1515

1616
interface DataTableRowActionsProps {
1717
row: Row<User>
1818
}
1919

2020
export function DataTableRowActions({ row }: DataTableRowActionsProps) {
21-
const { setOpen, setCurrentRow } = useUsersContext()
21+
const { setOpen, setCurrentRow } = useUsers()
2222
return (
2323
<>
2424
<DropdownMenu modal={false}>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useUsers } from '../context/users-context'
2+
import { UsersActionDialog } from './users-action-dialog'
3+
import { UsersDeleteDialog } from './users-delete-dialog'
4+
import { UsersInviteDialog } from './users-invite-dialog'
5+
6+
export function UsersDialogs() {
7+
const { open, setOpen, currentRow, setCurrentRow } = useUsers()
8+
return (
9+
<>
10+
<UsersActionDialog
11+
key='user-add'
12+
open={open === 'add'}
13+
onOpenChange={() => setOpen('add')}
14+
/>
15+
16+
<UsersInviteDialog
17+
key='user-invite'
18+
open={open === 'invite'}
19+
onOpenChange={() => setOpen('invite')}
20+
/>
21+
22+
{currentRow && (
23+
<>
24+
<UsersActionDialog
25+
key={`user-edit-${currentRow.id}`}
26+
open={open === 'edit'}
27+
onOpenChange={() => {
28+
setOpen('edit')
29+
setTimeout(() => {
30+
setCurrentRow(null)
31+
}, 500)
32+
}}
33+
currentRow={currentRow}
34+
/>
35+
36+
<UsersDeleteDialog
37+
key={`user-delete-${currentRow.id}`}
38+
open={open === 'delete'}
39+
onOpenChange={() => {
40+
setOpen('delete')
41+
setTimeout(() => {
42+
setCurrentRow(null)
43+
}, 500)
44+
}}
45+
currentRow={currentRow}
46+
/>
47+
</>
48+
)}
49+
</>
50+
)
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { IconMailPlus, IconUserPlus } from '@tabler/icons-react'
2+
import { Button } from '@/components/ui/button'
3+
import { useUsers } from '../context/users-context'
4+
5+
export function UsersPrimaryButtons() {
6+
const { setOpen } = useUsers()
7+
return (
8+
<div className='flex gap-2'>
9+
<Button
10+
variant='outline'
11+
className='space-x-1'
12+
onClick={() => setOpen('invite')}
13+
>
14+
<span>Invite User</span> <IconMailPlus size={18} />
15+
</Button>
16+
<Button className='space-x-1' onClick={() => setOpen('add')}>
17+
<span>Add User</span> <IconUserPlus size={18} />
18+
</Button>
19+
</div>
20+
)
21+
}

0 commit comments

Comments
 (0)