Skip to content

Commit

Permalink
Add snackbar after creating an offer in CompleteProfileBlock (#2635)
Browse files Browse the repository at this point in the history
* test conditional message & button

* added snackbar after creating an offer in CompleteProfileBlock

* added test for snackbar

* test dispatch

* added tests for CreateOrEditOffer

* tests

* fixed test

* Fixed prettier

* Removed unused onResponseError

* Removed unused imports and variable

* Fixed tests

* Removed useless test

* prettified AppSnackbar.tsx for more readability

* extracted styles in AppSnackbar, optimized dispatch and fixed default values

* prettified openAlert

* fixed ts error & isHash

---------

Co-authored-by: Vadym Pavlyk <[email protected]>
  • Loading branch information
dudchakk and Made1ra authored Dec 4, 2024
1 parent dea0776 commit 0999032
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 25 deletions.
7 changes: 6 additions & 1 deletion src/constants/translations/en/offer-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
"buttonTitles": {
"tutor": "Create offer",
"student": "Create request"
}
},
"extendedSuccessMessage": {
"tutor": "You successfuly created an offer!;You can review all your offers on My Offers page.",
"student": "You successfuly created a request!;You can review all your requests on My Requests page."
},
"seeAll": "See all"
},
"editOffer": {
"title": {
Expand Down
7 changes: 6 additions & 1 deletion src/constants/translations/uk/offer-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
"buttonTitles": {
"tutor": "Створити пропозицію",
"student": "Створити запит"
}
},
"extendedSuccessMessage": {
"tutor": "Ви успішно створили пропозицію!;Ви можете переглянути всі свої пропозиції на сторінці \"Мої пропозиції\".",
"student": "Ви успішно створили запит!;Ви можете переглянути всі свої запити на сторінці \"Мої запити\"."
},
"seeAll": "Переглянути всі"
},
"editOffer": {
"title": {
Expand Down
13 changes: 13 additions & 0 deletions src/containers/layout/app-snackbar/AppSnackbar.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const styles = {
alert: {
color: 'basic.white'
},
action: {
p: '4px 8px 0 30px',
cursor: 'pointer'
},
secondMessage: {
fontSize: '12px',
fontWeight: '300'
}
}
42 changes: 36 additions & 6 deletions src/containers/layout/app-snackbar/AppSnackbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,63 @@ import { useAppDispatch, useAppSelector } from '~/hooks/use-redux'
import Snackbar from '@mui/material/Snackbar'
import Alert from '@mui/material/Alert'
import Box from '@mui/material/Box'

import { closeAlert, snackbarSelector } from '~/redux/features/snackbarSlice'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { styles } from '~/containers/layout/app-snackbar/AppSnackbar.styles'

const AppSnackbar = () => {
const { isOpened, message, duration, severity } =
const { isOpened, message, duration, severity, isExtended, route } =
useAppSelector(snackbarSelector)

const { t } = useTranslation()

const dispatch = useAppDispatch()
const navigate = useNavigate()

const handleClose = () => dispatch(closeAlert())

const translatedMessage =
typeof message === 'string' ? t(message) : t(message.text, message.options)

const actionBody = translatedMessage
.split(', ')
.map((line) => <Box key={line}>{line}</Box>)

const handleButtonClick = () => {
navigate(route!)
handleClose()
}

const actionButton = (
<Box onClick={handleButtonClick} sx={styles.action}>
{t('offerPage.createOffer.seeAll')}
</Box>
)

const [firstMessage, secondMessage] = translatedMessage.split(';')

const extendedBody = (
<>
<Box>{firstMessage}</Box>
<Box sx={styles.secondMessage}>{secondMessage}</Box>
</>
)

return (
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
autoHideDuration={duration}
onClose={handleClose}
open={isOpened}
>
<Alert severity={severity} sx={{ color: 'basic.white' }} variant='filled'>
{translatedMessage.split(', ').map((line) => (
<Box key={line}>{line}</Box>
))}
<Alert
action={isExtended && actionButton}
severity={severity}
sx={styles.alert}
variant='filled'
>
{isExtended ? extendedBody : actionBody}
</Alert>
</Snackbar>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const getInitialValues = (offer: Offer | null) => ({
subject: offer?.subject._id ?? '',
proficiencyLevel: offer?.proficiencyLevel ?? [],
languages: offer?.languages ?? [],
enrolledUsers: [],
title: offer?.title ?? '',
description: offer?.description ?? '',
price: offer?.price.toString() ?? '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
ButtonVariantEnum,
ComponentEnum,
CreateOrUpdateOfferData,
ErrorResponse,
Offer,
OfferActionsEnum,
ServiceFunction,
Expand All @@ -35,7 +34,6 @@ import {
} from '~/types'
import { styles } from '~/containers/offer-page/OfferPage.styles'
import { openAlert } from '~/redux/features/snackbarSlice'
import { getErrorKey } from '~/utils/get-error-key'

interface CreateOrUpdateOfferProps {
existingOffer?: Offer | null
Expand All @@ -61,22 +59,28 @@ const CreateOrEditOffer: FC<CreateOrUpdateOfferProps> = ({
? OfferActionsEnum.Edit
: OfferActionsEnum.Create

const onResponseError = (error?: ErrorResponse) => {
dispatch(
openAlert({
severity: snackbarVariants.error,
message: getErrorKey(error)
})
)
}
const onResponse = (response: Offer | null) => {
const isHash = hash === '#offer'

dispatch(
openAlert({
severity: snackbarVariants.success,
message: `offerPage.${offerAction}.successMessage`
})
openAlert(
isHash
? {
severity: snackbarVariants.success,
message: `offerPage.createOffer.extendedSuccessMessage.${userRole}`,
duration: 10000,
isExtended: true,
route: authRoutes.myOffers.path
}
: {
severity: snackbarVariants.success,
message: `offerPage.${offerAction}.successMessage`
}
)
)

closeDrawer()

if (hash == '#offer') {
navigate(`${authRoutes.myProfile.path}#complete`)
updateOffer!(true)
Expand All @@ -97,8 +101,7 @@ const CreateOrEditOffer: FC<CreateOrUpdateOfferProps> = ({
service,
fetchOnMount: false,
defaultResponse: null,
onResponse,
onResponseError
onResponse
})

const {
Expand Down
10 changes: 9 additions & 1 deletion src/redux/features/snackbarSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ interface SnackbarState {
severity: AlertColor
message: SnackbarMessage
duration: number
isExtended?: boolean
route?: string
}

interface SnackbarOpenParams {
severity: AlertColor
message: SnackbarMessage
duration?: number
isExtended?: boolean
route?: string
}

type OpenSnackbarAction = PayloadAction<SnackbarOpenParams>
Expand All @@ -30,7 +34,9 @@ const initialState: SnackbarState = {
isOpened: false,
severity: 'info',
message: '',
duration: 0
duration: 0,
isExtended: false,
route: ''
}

const snackbarSlice = createSlice({
Expand All @@ -42,6 +48,8 @@ const snackbarSlice = createSlice({
state.severity = action.payload.severity
state.message = action.payload.message
state.duration = action.payload.duration || 4000
state.isExtended = action.payload.isExtended ?? false
state.route = action.payload.route ?? ''
},
closeAlert: (state) => {
state.isOpened = false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { screen, fireEvent, waitFor } from '@testing-library/react'
import { renderWithProviders } from '~tests/test-utils'
import LoginDialog from '~/containers/guest-home-page/login-dialog/LoginDialog'
import { TestSnackbar } from '~tests/test-utils'
import { vi } from 'vitest'
import { useAppSelector } from '~/hooks/use-redux'

const preloadedState = {
appMain: { loading: false, authLoading: false, userRole: '', error: '' }
}
const unwrap = vi.fn().mockRejectedValue({ data: { code: 'error' } })
const loginUser = vi.fn().mockReturnValue({ unwrap })

vi.mock('~/containers/guest-home-page/google-button/GoogleButton', () => ({
__esModule: true,
default: function () {
return <button>Google</button>
}
}))

vi.mock('~/services/auth-service', async () => {
return {
__esModule: true,
authService: {
endpoint: { matchFulfilled: vi.fn(), matchPending: vi.fn() }
},
useLoginMutation: () => [loginUser]
}
})

vi.mock('~/hooks/use-redux', async () => {
const actual = await vi.importActual('~/hooks/use-redux')
return {
...actual,
useAppSelector: vi.fn()
}
})

describe('snackbar with extended', () => {
beforeEach(async () => {
vi.mocked(useAppSelector).mockReturnValue({
isOpened: true,
message: 'Test message; Additional info',
duration: 3000,
severity: 'info',
isExtended: true,
route: '/test-route'
})

renderWithProviders(
<TestSnackbar>
<LoginDialog />
</TestSnackbar>,
preloadedState
)
})

it('should render extended content when isExtended is true', async () => {
const mainMessage = await screen.findByText('Test message')
const additionalInfo = await screen.findByText('Additional info')
const actionButton = await screen.findByText('offerPage.createOffer.seeAll')

expect(mainMessage).toBeInTheDocument()
expect(additionalInfo).toBeInTheDocument()
expect(actionButton).toBeInTheDocument()
})
})
Loading

0 comments on commit 0999032

Please sign in to comment.