Skip to content

Commit

Permalink
implemented clear history functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
TSlashDreamy committed Nov 15, 2023
1 parent f1604b2 commit 5191cca
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 49 deletions.
3 changes: 2 additions & 1 deletion src/constants/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ export const URLs = {
messages: {
get: '/messages',
post: '/messages',
delete: '/messages'
delete: '/messages',
patch: '/messages'
},
quizzes: {
get: '/quizzes',
Expand Down
21 changes: 13 additions & 8 deletions src/constants/translations/en/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,25 @@
"sendingMessage": "Sending...",
"interlocutor": "Your interlocutor"
},
"chatMenu": {
"deleteChat": "Delete chat",
"markingAsDeletedTitle": "Mark chat as deleted?",
"markingAsDeletedWarning": "This operation will delete chat only for you. Your interlocutor will still have access to this chat. Are you sure?",
"fullDeleteTitle": "Confirm chat deletion",
"fullDeleteWarning": "This operation is irreversible and your chat will be deleted completely and permanently!",
"deleteSuccess": "Your chat was deleted successfully!",
"clearHistory": "Clear history",
"clearHistoryTitle": "Do you want to clear your chat history?",
"clearHistoryWarning": "Your interlocutor will still have access to all old messages. Are you sure?",
"historyClearSuccess": "Chat history cleared successfully!"
},
"youCanAsk": "You can ask:",
"firstQuestion": {
"teachMethod": "What’s your teaching method?",
"trialLesson": "Do you have a trial lesson?",
"tutoringSession": "How long is each tutoring session?"
},
"creating": "Creating new chat for you, please wait...",
"deleteSuccess": "Your chat was deleted successfully!",
"fullDeleteTitle": "Confirm chat deletion",
"markingAsDeletedTitle": "Mark chat as deleted?",
"fullDeleteWarning": "This operation is irreversible and your chat will be deleted completely and permanently!",
"markingAsDeletedWarning": "This operation will delete chat only for you. Your interlocutor will still have access to this chat. Are you sure?",
"deleted": "Chat was deleted",
"deletedChip": "{{userName}} deleted this chat, and now it is read-only",
"deleteChat": "Delete chat",
"clearHistory": "Clear history"
"deletedChip": "{{userName}} deleted this chat, and now it is read-only"
}
3 changes: 2 additions & 1 deletion src/constants/translations/en/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"UNAUTHORIZED": "User is not authorized",
"EMAIL_NOT_FOUND": "There is no user registered with that email",
"BAD_RESET_TOKEN": "Your time is up please try again",
"DOCUMENT_NOT_FOUND": "Not found"
"DOCUMENT_NOT_FOUND": "Not found",
"UNKNOWN_ERROR": "Something went wrong"
}
21 changes: 13 additions & 8 deletions src/constants/translations/ua/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,25 @@
"sendingMessage": "Надсилання...",
"interlocutor": "Ваш співрозмовник"
},
"chatMenu": {
"deleteChat": "Видалити чат",
"markingAsDeletedTitle": "Позначити чат як видалений?",
"markingAsDeletedWarning": "Ця операція видалить чат лише для вас. Ваш співрозмовник як і раніше матиме доступ до цього чату. Ви впевнені?",
"fullDeleteTitle": "Підтвердіть видалення чату",
"fullDeleteWarning": "Ця операція є незворотною, і ваш чат буде видалено повністю та назавжди!",
"deleteSuccess": "Ваш чат успішно видалено!",
"clearHistory": "Очистити історію",
"clearHistoryTitle": "Бажаєте очистити історію чату?",
"clearHistoryWarning": "Ваш співрозмовник як і раніше матиме доступ до всіх старих повідомлень. Ви впевнені?",
"historyClearSuccess": "Історія чату успішно очищена!"
},
"youCanAsk": "Ви можете запитати:",
"firstQuestion": {
"teachMethod": "Який ваш метод навчання?",
"trialLesson": "У вас є пробне заняття?",
"tutoringSession": "Яка тривалість кожного уроку?"
},
"creating": "Створюємо новий чат, зачекайте...",
"deleteSuccess": "Ваш чат успішно видалено!",
"fullDeleteTitle": "Підтвердіть видалення чату",
"markingAsDeletedTitle": "Позначити чат як видалений?",
"fullDeleteWarning": "Ця операція є незворотною, і ваш чат буде видалено повністю та назавжди!",
"markingAsDeletedWarning": "Ця операція видалить чат лише для вас. Ваш співрозмовник як і раніше матиме доступ до цього чату. Ви впевнені?",
"deleted": "Чат було видалено",
"deletedChip": "Користувач {{userName}} видалив цей чат, і тепер він доступний лише для читання",
"deleteChat": "Видалити чат",
"clearHistory": "Очистити історію"
"deletedChip": "Користувач {{userName}} видалив цей чат, і тепер він доступний лише для читання"
}
3 changes: 2 additions & 1 deletion src/constants/translations/ua/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"UNAUTHORIZED": "Користувач не авторизований",
"EMAIL_NOT_FOUND": "Користувача з таким email не знайдено",
"BAD_RESET_TOKEN": "Час вийшов, спробуйте знову",
"DOCUMENT_NOT_FOUND": "Не знайдено"
"DOCUMENT_NOT_FOUND": "Не знайдено",
"UNKNOWN_ERROR": "Щось пішло не так"
}
4 changes: 4 additions & 0 deletions src/containers/chat/chat-header/ChatHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface ChatHeaderProps {
onClick: (e?: MouseEvent<HTMLButtonElement>) => void
onMenuClick: (e: MouseEvent<HTMLButtonElement>) => void
updateChats: () => Promise<void>
updateMessages: () => Promise<void>
currentChat: ChatResponse
user: Pick<UserResponse, '_id' | 'firstName' | 'lastName' | 'photo'>
messages: { text: string }[]
Expand All @@ -31,6 +32,7 @@ const ChatHeader: FC<ChatHeaderProps> = ({
onClick,
user,
updateChats,
updateMessages,
onMenuClick,
currentChat,
messages,
Expand Down Expand Up @@ -83,8 +85,10 @@ const ChatHeader: FC<ChatHeaderProps> = ({
<ChatMenu
anchorEl={menuAnchorEl}
currentChat={currentChat}
messagesLength={messages.length}
onClose={closeMenu}
updateChats={updateChats}
updateMessages={updateMessages}
/>
{isMobile && (
<IconButton onClick={onMenuClick} sx={styles.menuIconBtn}>
Expand Down
73 changes: 57 additions & 16 deletions src/containers/layout/chat-menu/ChatMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,41 @@ import { defaultResponses, snackbarVariants } from '~/constants'
interface ChatMenuProps {
anchorEl: Element | null
currentChat: ChatResponse
messagesLength: number
onClose: () => void
updateChats: () => Promise<void>
updateMessages: () => Promise<void>
}

const ChatMenu: FC<ChatMenuProps> = ({
anchorEl,
currentChat,
messagesLength,
onClose,
updateChats
updateChats,
updateMessages
}) => {
const { t } = useTranslation()
const { openDialog } = useConfirm()
const { setAlert } = useSnackBarContext()

const onResponse = useCallback(() => {
setAlert({
severity: snackbarVariants.success,
message: 'chatPage.deleteSuccess'
})
}, [setAlert])
const onResponse = useCallback(
(isDeleting = true) => {
setAlert({
severity: snackbarVariants.success,
message: isDeleting
? 'chatPage.chatMenu.deleteSuccess'
: 'chatPage.chatMenu.historyClearSuccess'
})
},
[setAlert]
)

const onResponseError = useCallback(
(error: ErrorResponse) => {
setAlert({
severity: snackbarVariants.error,
message: error ? `errors.${error.code}` : ''
message: error ? `errors.${error.code}` : 'errors.UNKNOWN_ERROR'
})
},
[setAlert]
Expand All @@ -67,6 +76,12 @@ const ChatMenu: FC<ChatMenuProps> = ({
[]
)

const clearHistoryService = useCallback(
(id?: string): Promise<AxiosResponse> =>
messageService.clearChatHistory(id ?? ''),
[]
)

const { fetchData: markAsDeleted } = useAxios({
service: markAsDeletedService,
defaultResponse: defaultResponses.object,
Expand All @@ -91,6 +106,14 @@ const ChatMenu: FC<ChatMenuProps> = ({
fetchOnMount: false
})

const { fetchData: clearHistory } = useAxios({
service: clearHistoryService,
defaultResponse: defaultResponses.object,
onResponse: () => onResponse(false),
onResponseError,
fetchOnMount: false
})

const handleDeletion = async (
id: string,
isConfirmed: boolean,
Expand All @@ -106,9 +129,23 @@ const ChatMenu: FC<ChatMenuProps> = ({
}
}

const onClearHistory = (e: MouseEvent<HTMLButtonElement>) => {
const handleClearChat = async (id: string, isConfirmed: boolean) => {
if (isConfirmed) {
await clearHistory(id)
await updateMessages()
}
}

const onClearHistory = (e: MouseEvent<HTMLButtonElement>, id: string) => {
e.stopPropagation()
onClose()

openDialog({
message: 'chatPage.chatMenu.clearHistoryWarning',
sendConfirm: (isConfirmed: boolean) =>
void handleClearChat(id, isConfirmed),
title: 'chatPage.chatMenu.clearHistoryTitle'
})
}

const onDelete = (e: MouseEvent<HTMLButtonElement>, id: string) => {
Expand All @@ -118,27 +155,30 @@ const ChatMenu: FC<ChatMenuProps> = ({

openDialog({
message: deletingFully
? 'chatPage.fullDeleteWarning'
: 'chatPage.markingAsDeletedWarning',
? 'chatPage.chatMenu.fullDeleteWarning'
: 'chatPage.chatMenu.markingAsDeletedWarning',
sendConfirm: (isConfirmed: boolean) =>
void handleDeletion(id, isConfirmed, deletingFully),
title: deletingFully
? 'chatPage.fullDeleteTitle'
: 'chatPage.markingAsDeletedTitle'
? 'chatPage.chatMenu.fullDeleteTitle'
: 'chatPage.chatMenu.markingAsDeletedTitle'
})
}

const menuButtons = [
{
_id: 1,
icon: <UpdateDisabledIcon />,
name: t('chatPage.clearHistory'),
handleOnClick: (e: MouseEvent<HTMLButtonElement>) => onClearHistory(e)
isAvailable: !!messagesLength,
name: t('chatPage.chatMenu.clearHistory'),
handleOnClick: (e: MouseEvent<HTMLButtonElement>) =>
onClearHistory(e, currentChat._id)
},
{
_id: 2,
icon: <DeleteOutlineIcon />,
name: t('chatPage.deleteChat'),
isAvailable: !!currentChat,
name: t('chatPage.chatMenu.deleteChat'),
handleOnClick: (e: MouseEvent<HTMLButtonElement>) =>
onDelete(e, currentChat._id),
isDangerous: true
Expand All @@ -148,6 +188,7 @@ const ChatMenu: FC<ChatMenuProps> = ({
const menuItems = menuButtons.map((item) => (
<MenuItem
component={ComponentEnum.Button}
disabled={!item.isAvailable}
key={item._id}
onClick={item.handleOnClick}
sx={styles.menuItem(!!item.isDangerous)}
Expand Down
1 change: 1 addition & 0 deletions src/pages/chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ const Chat = () => {
onFilteredMessagesChange={handleFilteredMessage}
onMenuClick={openChatsHandler}
updateChats={handleUpdateChats}
updateMessages={fetchData}
user={userToSpeak?.user}
/>
)}
Expand Down
4 changes: 4 additions & 0 deletions src/services/message-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,9 @@ export const messageService = {
): Promise<AxiosResponse<void>> => {
const chat = createUrlPath(URLs.chats.delete, chatId)
return axiosClient.delete(`${chat}${URLs.messages.delete}`)
},
clearChatHistory: async (chatId: string): Promise<AxiosResponse> => {
const chat = createUrlPath(URLs.chats.patch, chatId)
return axiosClient.patch(`${chat}${URLs.messages.patch}`)
}
}
63 changes: 49 additions & 14 deletions tests/unit/containers/layout/chat-menu/ChatMenu.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fireEvent, screen } from '@testing-library/react'
import { fireEvent, screen, cleanup } from '@testing-library/react'
import { renderWithProviders } from '~tests/test-utils'
import { expect, vi } from 'vitest'
import ChatMenu from '~/containers/layout/chat-menu/ChatMenu'
Expand Down Expand Up @@ -52,53 +52,88 @@ describe('ChatMenu Component', () => {
}
const onClose = vi.fn()
const updateChats = vi.fn()
const updateMessages = vi.fn()

beforeEach(() => {
renderWithProviders(
<ChatMenu
anchorEl={anchorEl}
currentChat={currentChat}
messagesLength={0}
onClose={onClose}
updateChats={updateChats}
updateMessages={updateMessages}
/>
)
})

it('renders without errors', () => {
expect(screen.getByText('chatPage.clearHistory')).toBeInTheDocument()
expect(screen.getByText('chatPage.deleteChat')).toBeInTheDocument()
expect(
screen.getByText('chatPage.chatMenu.clearHistory')
).toBeInTheDocument()
expect(screen.getByText('chatPage.chatMenu.deleteChat')).toBeInTheDocument()
})

it('should disable Clear History button if there are no messages', () => {
const clearHistoryButton = screen.getByText(
'chatPage.chatMenu.clearHistory'
)

fireEvent.click(clearHistoryButton)

expect(clearHistoryButton).toHaveAttribute('disabled')
expect(onClose).not.toHaveBeenCalled()
expect(updateMessages).not.toHaveBeenCalled()
})

it('handles Clear History button click', () => {
const clearHistoryButton = screen.getByText('chatPage.clearHistory')
cleanup()
renderWithProviders(
<ChatMenu
anchorEl={anchorEl}
currentChat={currentChat}
messagesLength={5}
onClose={onClose}
updateChats={updateChats}
updateMessages={updateMessages}
/>
)

const clearHistoryButton = screen.getByText(
'chatPage.chatMenu.clearHistory'
)
const modalTitle = 'chatPage.chatMenu.clearHistoryTitle'
const modalDescription = 'chatPage.chatMenu.clearHistoryWarning'

fireEvent.click(clearHistoryButton)

expect(onClose).toHaveBeenCalled()
expect(modalTitle).toBe(mockOpenDialog.mock.calls[0][0].title)
expect(modalDescription).toBe(mockOpenDialog.mock.calls[0][0].message)
})

it('handles Delete button click (mark as deleted)', () => {
const deleteButton = screen.getByText('chatPage.deleteChat')
const modalTitle = 'chatPage.markingAsDeletedTitle'
const modalDescription = 'chatPage.markingAsDeletedWarning'
const deleteButton = screen.getByText('chatPage.chatMenu.deleteChat')
const modalTitle = 'chatPage.chatMenu.markingAsDeletedTitle'
const modalDescription = 'chatPage.chatMenu.markingAsDeletedWarning'

fireEvent.click(deleteButton)

expect(onClose).toHaveBeenCalled()
expect(modalTitle).toBe(mockOpenDialog.mock.calls[0][0].title)
expect(modalDescription).toBe(mockOpenDialog.mock.calls[0][0].message)
expect(modalTitle).toBe(mockOpenDialog.mock.calls[1][0].title)
expect(modalDescription).toBe(mockOpenDialog.mock.calls[1][0].message)
})

it('handles Delete button click (fully deleting)', async () => {
const deleteButton = screen.getByText('chatPage.deleteChat')
const modalTitle = 'chatPage.fullDeleteTitle'
const modalDescription = 'chatPage.fullDeleteWarning'
const deleteButton = screen.getByText('chatPage.chatMenu.deleteChat')
const modalTitle = 'chatPage.chatMenu.fullDeleteTitle'
const modalDescription = 'chatPage.chatMenu.fullDeleteWarning'
currentChat.deletedFor = ['user1']

fireEvent.click(deleteButton)

expect(onClose).toHaveBeenCalled()
expect(modalTitle).toBe(mockOpenDialog.mock.calls[1][0].title)
expect(modalDescription).toBe(mockOpenDialog.mock.calls[1][0].message)
expect(modalTitle).toBe(mockOpenDialog.mock.calls[2][0].title)
expect(modalDescription).toBe(mockOpenDialog.mock.calls[2][0].message)
})
})

0 comments on commit 5191cca

Please sign in to comment.