Skip to content

Commit d407836

Browse files
committed
feat: adding comments integration
1 parent 4d5e3b7 commit d407836

File tree

8 files changed

+356
-13
lines changed

8 files changed

+356
-13
lines changed

DONE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@
149149
- [x] Heart games
150150
- [x] Heart comments and replies
151151
- [x] Heart blog posts
152+
- [x] Integrating comments
153+
- [x] Create comments
154+
- [x] Delete comment
155+
- [x] Integrate the search page
152156

153157
### Post MVP
154158

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { Button } from '@mui/material'
2+
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
3+
import toast from 'react-hot-toast'
4+
5+
import ActionDialog, { ActionDialogProps } from './ActionDialog'
6+
7+
jest.mock('react-hot-toast', () => ({
8+
success: jest.fn(),
9+
error: jest.fn(),
10+
}))
11+
12+
const renderActionDialog = (props: Partial<ActionDialogProps> = {}) => {
13+
const defaultProps: ActionDialogProps = {
14+
title: 'Test Dialog',
15+
confirmAction: jest.fn(),
16+
trigger: <Button>Open</Button>,
17+
...props,
18+
}
19+
20+
return render(<ActionDialog {...defaultProps} />)
21+
}
22+
23+
describe('ActionDialog', () => {
24+
it('should render the trigger button and not show the dialog initially', () => {
25+
renderActionDialog()
26+
27+
expect(screen.getByText('Open')).toBeInTheDocument()
28+
29+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
30+
})
31+
32+
it('should open the dialog when the trigger button is clicked', () => {
33+
renderActionDialog()
34+
35+
fireEvent.click(screen.getByText('Open'))
36+
37+
expect(screen.getByRole('dialog')).toBeInTheDocument()
38+
expect(screen.getByText('Test Dialog')).toBeInTheDocument()
39+
})
40+
41+
it('should close the dialog when the cancel button is clicked', async () => {
42+
renderActionDialog()
43+
44+
fireEvent.click(screen.getByText('Open'))
45+
46+
expect(screen.getByRole('dialog')).toBeInTheDocument()
47+
48+
fireEvent.click(screen.getByText('Cancelar'))
49+
50+
await waitFor(() => {
51+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
52+
})
53+
})
54+
55+
it('should call the confirm action when the confirm button is clicked', async () => {
56+
const mockConfirmAction = jest.fn().mockResolvedValueOnce(null)
57+
renderActionDialog({ confirmAction: mockConfirmAction })
58+
59+
fireEvent.click(screen.getByText('Open'))
60+
61+
fireEvent.click(screen.getByText('Confirm'))
62+
63+
await waitFor(() => {
64+
expect(mockConfirmAction).toHaveBeenCalledTimes(1)
65+
})
66+
})
67+
68+
it('should show an error toast if confirmation fails', async () => {
69+
const mockConfirmAction = jest
70+
.fn()
71+
.mockRejectedValueOnce(new Error('Error'))
72+
renderActionDialog({ confirmAction: mockConfirmAction })
73+
74+
fireEvent.click(screen.getByText('Open'))
75+
76+
fireEvent.click(screen.getByText('Confirm'))
77+
78+
await waitFor(() => {
79+
expect(toast.error).toHaveBeenCalledWith('Ops! Algo deu errado')
80+
})
81+
82+
expect(screen.getByRole('dialog')).toBeInTheDocument()
83+
})
84+
})
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { LoadingButton as Button } from '@mui/lab'
2+
import {
3+
Alert,
4+
AlertProps,
5+
Box,
6+
Dialog,
7+
DialogContent,
8+
DialogContentText,
9+
DialogProps,
10+
DialogTitle,
11+
Divider,
12+
IconButton,
13+
Stack,
14+
Typography,
15+
} from '@mui/material'
16+
import {
17+
cloneElement,
18+
ReactElement,
19+
ReactNode,
20+
useEffect,
21+
useState,
22+
} from 'react'
23+
import toast from 'react-hot-toast'
24+
import { IoClose } from 'react-icons/io5'
25+
26+
export interface ActionDialogProps {
27+
children?: ReactNode
28+
successMessage?: string
29+
confirmAction: () => Promise<unknown> | void
30+
trigger: ReactElement
31+
confirmText?: string
32+
description?: string
33+
dialogProps?: DialogProps
34+
title: string
35+
isSuccess?: boolean
36+
isLoading?: boolean
37+
alert?: AlertProps
38+
}
39+
40+
function ActionDialog(props: ActionDialogProps) {
41+
const {
42+
alert,
43+
children,
44+
confirmAction,
45+
confirmText = 'Confirm',
46+
description,
47+
dialogProps,
48+
title,
49+
trigger = <Button>Open</Button>,
50+
successMessage,
51+
isSuccess = false,
52+
isLoading = false,
53+
} = props
54+
55+
const [open, setOpen] = useState(false)
56+
57+
const handleOpen = () => {
58+
setOpen(true)
59+
}
60+
61+
const handleClose = () => {
62+
setOpen(false)
63+
}
64+
65+
useEffect(() => {
66+
if (isSuccess) {
67+
if (successMessage) toast.success(successMessage)
68+
setOpen(false)
69+
}
70+
}, [isSuccess, successMessage])
71+
72+
const handleConfirm = async () => {
73+
try {
74+
await confirmAction()
75+
} catch (e) {
76+
console.log(e)
77+
toast.error('Ops! Algo deu errado')
78+
}
79+
}
80+
81+
return (
82+
<Box>
83+
{cloneElement(trigger, { onClick: handleOpen })}
84+
85+
<Dialog
86+
onClose={handleClose}
87+
aria-labelledby="alert-dialog-title"
88+
aria-describedby="alert-dialog-description"
89+
maxWidth="md"
90+
fullWidth
91+
TransitionProps={{
92+
onExited: () => setOpen(false),
93+
}}
94+
{...dialogProps}
95+
open={open}
96+
PaperProps={{
97+
sx: {
98+
borderRadius: 3,
99+
p: 2,
100+
transition: 'all 0.3s ease-in-out',
101+
transform: 'scale(0.95)',
102+
animation: 'scale-in 0.3s ease-in-out forwards',
103+
'@keyframes scale-in': {
104+
'0%': { transform: 'scale(0.95)', opacity: 0 },
105+
'100%': { transform: 'scale(1)', opacity: 1 },
106+
},
107+
},
108+
}}>
109+
<DialogTitle
110+
component="div"
111+
id="alert-dialog-title"
112+
sx={{
113+
display: 'flex',
114+
alignItems: 'center',
115+
justifyContent: 'space-between',
116+
mb: 1,
117+
}}>
118+
<Typography variant="h6" fontWeight="bold">
119+
{title}
120+
</Typography>
121+
<IconButton
122+
aria-label="close"
123+
onClick={handleClose}
124+
sx={{
125+
color: (theme) => theme.palette.grey[500],
126+
transition: 'color 0.2s',
127+
'&:hover': {
128+
color: (theme) => theme.palette.grey[700],
129+
},
130+
}}>
131+
<IoClose />
132+
</IconButton>
133+
</DialogTitle>
134+
135+
<DialogContent>
136+
{description && (
137+
<DialogContentText sx={{ mb: 3 }}>
138+
{description}
139+
</DialogContentText>
140+
)}
141+
{alert && <Alert {...alert} sx={{ mb: 2, borderRadius: 2 }} />}
142+
<Box mb={3}>{children}</Box>
143+
<Divider />
144+
<Stack
145+
direction={{ xs: 'column', sm: 'row' }}
146+
justifyContent="space-between"
147+
spacing={2}
148+
mt={3}>
149+
<Button
150+
onClick={handleClose}
151+
variant="outlined"
152+
color="error"
153+
sx={{
154+
flexGrow: 1,
155+
textTransform: 'uppercase',
156+
}}>
157+
Cancelar
158+
</Button>
159+
160+
<Button
161+
loading={isLoading}
162+
onClick={handleConfirm}
163+
variant="outlined"
164+
color="success"
165+
sx={{
166+
flexGrow: 1,
167+
textTransform: 'uppercase',
168+
minWidth: '150px',
169+
position: 'relative',
170+
}}>
171+
{confirmText}
172+
</Button>
173+
</Stack>
174+
</DialogContent>
175+
</Dialog>
176+
</Box>
177+
)
178+
}
179+
180+
export default ActionDialog
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Button } from '@mui/material'
2+
import { Meta, StoryObj } from '@storybook/react'
3+
import { Provider } from 'react-redux'
4+
5+
import store from '@/store'
6+
7+
import ActionDialog, { ActionDialogProps } from './ActionDialog'
8+
9+
const meta: Meta<typeof ActionDialog> = {
10+
title: 'Components/ActionDialog',
11+
component: ActionDialog,
12+
args: {
13+
confirmAction: () => console.log('confirmed action'),
14+
children: <>This is just a dialog story.</>,
15+
title: 'This is a storybook for ActionDialog',
16+
trigger: <Button variant="contained">Open dialog</Button>,
17+
},
18+
argTypes: {},
19+
}
20+
21+
export const Default: Story = {
22+
render: (args) => (
23+
<Provider store={store}>
24+
<ActionDialog {...args} />
25+
</Provider>
26+
),
27+
}
28+
29+
type Story = StoryObj<ActionDialogProps>
30+
31+
export default meta

src/components/ActionDialog/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './ActionDialog'

0 commit comments

Comments
 (0)