Skip to content

Commit

Permalink
226 alert box (#473)
Browse files Browse the repository at this point in the history
* Added alert box hook and custom alert box

* Replaced js alert() with custom implementation

* 226 - style also mutation error alerts

---------

Co-authored-by: rtrembecky <[email protected]>
  • Loading branch information
vgeffer and rtrembecky authored Nov 23, 2024
1 parent 22a0fa3 commit c98409f
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 11 deletions.
35 changes: 35 additions & 0 deletions src/components/Alert/AlertBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {Stack} from '@mui/material'
import {FC} from 'react'
import {useContainer} from 'unstated-next'

import {AlertContainer} from '@/utils/AlertContainer'

import {Button} from '../Clickable/Button'
import {Dialog} from '../Dialog/Dialog'

export const AlertBox: FC = () => {
const container = useContainer(AlertContainer)

const closeContainer = () => {
container.setAlertBox({
message: container.alertBox?.message ?? '',
title: container.alertBox?.title ?? '',
isOpen: false,
})
}

return (
<Dialog
open={container.alertBox?.isOpen ?? false}
title={container.alertBox?.title}
contentText={container.alertBox?.message}
close={closeContainer}
>
<Stack direction={'row'} mt={3} justifyContent="center">
<Button variant="button1" onClick={closeContainer}>
Dobre
</Button>
</Stack>
</Dialog>
)
}
2 changes: 2 additions & 0 deletions src/components/PageLayout/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {Link} from '@/components/Clickable/Link'
import {Dialog} from '@/components/Dialog/Dialog'
import {FormInput} from '@/components/FormItems/FormInput/FormInput'
import {AuthContainer} from '@/utils/AuthContainer'
import {useAlert} from '@/utils/useAlert'
import {useSeminarInfo} from '@/utils/useSeminarInfo'

import {PasswordResetRequestForm} from '../PasswordResetRequest/PasswordResetRequest'
Expand All @@ -30,6 +31,7 @@ export const LoginForm: FC<LoginFormProps> = ({closeDialog}) => {
const {login} = AuthContainer.useContainer()
const {handleSubmit, control} = useForm<LoginFormValues>({defaultValues})
const {seminar} = useSeminarInfo()
const {alert} = useAlert()

const router = useRouter()

Expand Down
3 changes: 3 additions & 0 deletions src/components/Problems/UploadProblemForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {useDropzone} from 'react-dropzone'

import {CloseButton} from '@/components/CloseButton/CloseButton'
import {niceBytes} from '@/utils/niceBytes'
import {useAlert} from '@/utils/useAlert'

import {Button} from '../Clickable/Button'
import {Link} from '../Clickable/Link'
Expand All @@ -28,6 +29,8 @@ export const UploadProblemForm: FC<{
isAfterDeadline,
invalidateSeriesQuery,
}) => {
const {alert} = useAlert()

const {mutate: uploadSolution} = useMutation({
mutationFn: (formData: FormData) => axios.post(`/api/competition/problem/${problemId}/upload-solution`, formData),
onSuccess: (response) => {
Expand Down
2 changes: 2 additions & 0 deletions src/components/Profile/PasswordChangeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {FC} from 'react'
import {SubmitHandler, useForm} from 'react-hook-form'

import {IGeneralPostResponse} from '@/types/api/general'
import {useAlert} from '@/utils/useAlert'

import {Button} from '../Clickable/Button'
import {Dialog} from '../Dialog/Dialog'
Expand Down Expand Up @@ -33,6 +34,7 @@ interface ChangePasswordErrorResponseData {

export const PasswordChangeDialog: FC<PasswordChangeDialogProps> = ({open, close}) => {
const {handleSubmit, reset, control, getValues, setError} = useForm<PasswordChangeDialogValues>({defaultValues})
const {alert} = useAlert()

const onSuccess = () => {
alert('Zmena hesla prebehla úspešne.')
Expand Down
2 changes: 2 additions & 0 deletions src/components/RegisterForm/RegisterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {SubmitHandler, useForm, useFormState} from 'react-hook-form'

import {FormInput} from '@/components/FormItems/FormInput/FormInput'
import {IGeneralPostResponse} from '@/types/api/general'
import {useAlert} from '@/utils/useAlert'
import {useSeminarInfo} from '@/utils/useSeminarInfo'

import {useNavigationTrap} from '../../utils/useNavigationTrap'
Expand Down Expand Up @@ -62,6 +63,7 @@ export const RegisterForm: FC = () => {
const router = useRouter()

const {seminar} = useSeminarInfo()
const {alert} = useAlert()

const transformFormData = (data: RegisterFormValues) => ({
email: data.email,
Expand Down
90 changes: 79 additions & 11 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import {isAxiosError} from 'axios'
import {AppProps} from 'next/app'
import dynamic from 'next/dynamic'
import Head from 'next/head'
import {FC} from 'react'
import {FC, PropsWithChildren, useMemo} from 'react'
import {CookiesProvider} from 'react-cookie'

import {AlertBox} from '@/components/Alert/AlertBox'
import {theme} from '@/theme'
import {AlertContainer} from '@/utils/AlertContainer'
import {AuthContainer} from '@/utils/AuthContainer'
import {useAlert} from '@/utils/useAlert'

const ReactQueryDevtools = dynamic(
() => import('@tanstack/react-query-devtools').then(({ReactQueryDevtools}) => ReactQueryDevtools),
Expand Down Expand Up @@ -71,6 +74,68 @@ const queryClient = new QueryClient({
},
})

const ReactQueryProvider: FC<PropsWithChildren> = ({children}) => {
const {alert} = useAlert()

const queryClient = useMemo(
() =>
new QueryClient({
defaultOptions: {
queries: {
retry: (failureCount, error) => {
if (isAxiosError(error)) {
// nechceme retryovat 403 (Forbidden)
const status = error.response?.status
if (status === 403 || status === 404) return false
}
// klasika - retryuj len 3x
if (failureCount >= 3) return false
return true
},
},
mutations: {
// globalny error handler requestov cez useMutation
// notes:
// - useMutation vzdy sam loguje error do konzoly, my nemusime
// - specifikovanim `onError` na nejakej `useMutation` sa tento handler prepise, tak sa tomu vyhybajme
onError: (error) => {
if (isAxiosError(error)) {
const data = error.response?.data as unknown
if (typeof data === 'object' && data) {
// ak mame vlastny message v `.detail`, ukazeme userovi ten
const detail = 'detail' in data && data.detail
if (typeof detail === 'string') {
alert(detail)
return
}

// ak nie, ale mame message v `.non_field_errors`, ukazeme ten
const nonFieldErrors = 'non_field_errors' in data && data.non_field_errors
const nonFieldError = Array.isArray(nonFieldErrors) && (nonFieldErrors as unknown[])[0]
if (typeof nonFieldError === 'string') {
alert(nonFieldError)
return
}

// TODO: handle field errors (napr. na password) - nealertovat usera, ale zobrazit v UI? alebo alert s prvym field errorom

// ak nie, ukazeme kludne original anglicku hlasku z `error`u
alert(error.message)
return
}
} else {
alert(error.message)
}
},
},
},
}),
[alert],
)

return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}

const MyApp: FC<AppProps> = ({Component, pageProps}) => {
return (
<>
Expand All @@ -86,16 +151,19 @@ const MyApp: FC<AppProps> = ({Component, pageProps}) => {
font-family: ${theme.typography.fontFamily};
}
`}</style>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools />
<CookiesProvider>
<AuthContainer.Provider>
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
</AuthContainer.Provider>
</CookiesProvider>
</QueryClientProvider>
<AlertContainer.Provider>
<ReactQueryProvider>
<ReactQueryDevtools />
<CookiesProvider>
<AuthContainer.Provider>
<ThemeProvider theme={theme}>
<AlertBox />
<Component {...pageProps} />
</ThemeProvider>
</AuthContainer.Provider>
</CookiesProvider>
</ReactQueryProvider>
</AlertContainer.Provider>
</>
)
}
Expand Down
15 changes: 15 additions & 0 deletions src/utils/AlertContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {useState} from 'react'
import {createContainer} from 'unstated-next'

export interface AlertProps {
isOpen: boolean
title?: string
message?: string
}

const useAlertBox = () => {
const [alertBox, setAlertBox] = useState<AlertProps>()
return {alertBox, setAlertBox}
}

export const AlertContainer = createContainer(useAlertBox)
13 changes: 13 additions & 0 deletions src/utils/useAlert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {useContainer} from 'unstated-next'

import {AlertContainer} from '@/utils/AlertContainer'

export const useAlert = () => {
const container = useContainer(AlertContainer)

const alert = (message: string, title?: string) => {
container.setAlertBox({message: message, title: title ?? 'Upozornenie', isOpen: true})
}

return {alert}
}

0 comments on commit c98409f

Please sign in to comment.