Add modal for network errors

This commit is contained in:
Justin Edmund 2023-01-27 21:59:56 -08:00
parent 6da5a4f320
commit ab928f4429
6 changed files with 104 additions and 17 deletions

View file

@ -23,7 +23,11 @@ const Alert = (props: Props) => {
<AlertDialog.Overlay className="Overlay" onClick={props.cancelAction} /> <AlertDialog.Overlay className="Overlay" onClick={props.cancelAction} />
<div className="AlertWrapper"> <div className="AlertWrapper">
<AlertDialog.Content className="Alert"> <AlertDialog.Content className="Alert">
{props.title ? <AlertDialog.Title>Error</AlertDialog.Title> : ''} {props.title ? (
<AlertDialog.Title>{props.title}</AlertDialog.Title>
) : (
''
)}
<AlertDialog.Description className="description"> <AlertDialog.Description className="description">
{props.message} {props.message}
</AlertDialog.Description> </AlertDialog.Description>

View file

@ -2,8 +2,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { getCookie } from 'cookies-next' import { getCookie } from 'cookies-next'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next'
import { AxiosResponse } from 'axios' import { AxiosError, AxiosResponse } from 'axios'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import Alert from '~components/Alert' import Alert from '~components/Alert'
@ -31,12 +32,19 @@ const CharacterGrid = (props: Props) => {
// Constants // Constants
const numCharacters: number = 5 const numCharacters: number = 5
// Localization
const { t } = useTranslation('common')
// Cookies // Cookies
const cookie = getCookie('account') const cookie = getCookie('account')
const accountData: AccountCookie = cookie const accountData: AccountCookie = cookie
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: null : null
// Set up state for error handling
const [axiosError, setAxiosError] = useState<AxiosResponse>()
const [errorAlertOpen, setErrorAlertOpen] = useState(false)
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(appState) const { party, grid } = useSnapshot(appState)
const [slug, setSlug] = useState() const [slug, setSlug] = useState()
@ -111,7 +119,15 @@ const CharacterGrid = (props: Props) => {
if (party.editable) if (party.editable)
saveCharacter(party.id, character, position) saveCharacter(party.id, character, position)
.then((response) => handleCharacterResponse(response.data)) .then((response) => handleCharacterResponse(response.data))
.catch((error) => console.error(error)) .catch((error) => {
const axiosError = error as AxiosError
const response = axiosError.response
if (response) {
setErrorAlertOpen(true)
setAxiosError(response)
}
})
} }
} }
@ -482,6 +498,19 @@ const CharacterGrid = (props: Props) => {
} }
// Render: JSX components // Render: JSX components
const errorAlert = () => {
console.log(axiosError?.status)
return (
<Alert
open={errorAlertOpen}
title={axiosError ? `${axiosError.status}` : 'Error'}
message={t(`errors.${axiosError?.statusText.toLowerCase()}`)}
cancelAction={() => setErrorAlertOpen(false)}
cancelActionText={t('buttons.confirm')}
/>
)
}
return ( return (
<div> <div>
<Alert <Alert
@ -526,6 +555,7 @@ const CharacterGrid = (props: Props) => {
})} })}
</ul> </ul>
</div> </div>
{errorAlert()}
</div> </div>
) )
} }

View file

@ -4,9 +4,10 @@ import { getCookie } from 'cookies-next'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { AxiosResponse } from 'axios' import { AxiosError, AxiosResponse } from 'axios'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import Alert from '~components/Alert'
import SummonUnit from '~components/SummonUnit' import SummonUnit from '~components/SummonUnit'
import ExtraSummons from '~components/ExtraSummons' import ExtraSummons from '~components/ExtraSummons'
@ -38,6 +39,10 @@ const SummonGrid = (props: Props) => {
// Localization // Localization
const { t } = useTranslation('common') const { t } = useTranslation('common')
// Set up state for error handling
const [axiosError, setAxiosError] = useState<AxiosResponse>()
const [errorAlertOpen, setErrorAlertOpen] = useState(false)
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(appState) const { party, grid } = useSnapshot(appState)
const [slug, setSlug] = useState() const [slug, setSlug] = useState()
@ -100,12 +105,12 @@ const SummonGrid = (props: Props) => {
saveSummon(party.id, summon, position) saveSummon(party.id, summon, position)
.then((response) => handleSummonResponse(response.data)) .then((response) => handleSummonResponse(response.data))
.catch((error) => { .catch((error) => {
const code = error.response.status const axiosError = error as AxiosError
const data = error.response.data const response = axiosError.response
if (code === 422) {
if (data.code === 'incompatible_summon_for_position') { if (response) {
// setShowIncompatibleAlert(true) setErrorAlertOpen(true)
} setAxiosError(response)
} }
}) })
} }
@ -380,6 +385,19 @@ const SummonGrid = (props: Props) => {
} }
// Render: JSX components // Render: JSX components
const errorAlert = () => {
console.log(axiosError?.status)
return (
<Alert
open={errorAlertOpen}
title={axiosError ? `${axiosError.status}` : 'Error'}
message={t(`errors.${axiosError?.statusText.toLowerCase()}`)}
cancelAction={() => setErrorAlertOpen(false)}
cancelActionText={t('buttons.confirm')}
/>
)
}
const mainSummonElement = ( const mainSummonElement = (
<div className="LabeledUnit"> <div className="LabeledUnit">
<div className="Label">{t('summons.main')}</div> <div className="Label">{t('summons.main')}</div>
@ -460,6 +478,7 @@ const SummonGrid = (props: Props) => {
</div> </div>
{subAuraSummonElement} {subAuraSummonElement}
{errorAlert()}
</div> </div>
) )
} }

View file

@ -4,7 +4,7 @@ import { getCookie } from 'cookies-next'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { AxiosResponse } from 'axios' import { AxiosError, AxiosResponse } from 'axios'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import Alert from '~components/Alert' import Alert from '~components/Alert'
@ -41,11 +41,15 @@ const WeaponGrid = (props: Props) => {
? JSON.parse(cookie as string) ? JSON.parse(cookie as string)
: null : null
// Set up state for error handling
const [axiosError, setAxiosError] = useState<AxiosResponse>()
const [errorAlertOpen, setErrorAlertOpen] = useState(false)
const [showIncompatibleAlert, setShowIncompatibleAlert] = useState(false)
// Set up state for view management // Set up state for view management
const { party, grid } = useSnapshot(appState) const { party, grid } = useSnapshot(appState)
const [slug, setSlug] = useState() const [slug, setSlug] = useState()
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
const [showIncompatibleAlert, setShowIncompatibleAlert] = useState(false)
// Set up state for conflict management // Set up state for conflict management
const [incoming, setIncoming] = useState<Weapon>() const [incoming, setIncoming] = useState<Weapon>()
@ -100,11 +104,21 @@ const WeaponGrid = (props: Props) => {
saveWeapon(party.id, weapon, position) saveWeapon(party.id, weapon, position)
.then((response) => handleWeaponResponse(response.data)) .then((response) => handleWeaponResponse(response.data))
.catch((error) => { .catch((error) => {
const code = error.response.status const axiosError = error as AxiosError
const data = error.response.data const response = axiosError.response
if (code === 422) {
if (data.code === 'incompatible_weapon_for_position') { if (response) {
const code = response.status
const data = response.data
if (
code === 422 &&
data.code === 'incompatible_weapon_for_position'
) {
setShowIncompatibleAlert(true) setShowIncompatibleAlert(true)
} else {
setErrorAlertOpen(true)
setAxiosError(response)
} }
} }
}) })
@ -362,16 +376,30 @@ const WeaponGrid = (props: Props) => {
cancelAction={() => setShowIncompatibleAlert(!showIncompatibleAlert)} cancelAction={() => setShowIncompatibleAlert(!showIncompatibleAlert)}
cancelActionText={t('buttons.confirm')} cancelActionText={t('buttons.confirm')}
message={t('alert.incompatible_weapon')} message={t('alert.incompatible_weapon')}
></Alert> />
) : ( ) : (
'' ''
) )
} }
const errorAlert = () => {
console.log(axiosError?.status)
return (
<Alert
open={errorAlertOpen}
title={axiosError ? `${axiosError.status}` : 'Error'}
message={t(`errors.${axiosError?.statusText.toLowerCase()}`)}
cancelAction={() => setErrorAlertOpen(false)}
cancelActionText={t('buttons.confirm')}
/>
)
}
return ( return (
<div id="WeaponGrid"> <div id="WeaponGrid">
{conflicts ? conflictModal() : ''} {conflicts ? conflictModal() : ''}
{incompatibleAlert()} {incompatibleAlert()}
{errorAlert()}
<div id="MainGrid"> <div id="MainGrid">
{mainhandElement} {mainhandElement}
<ul id="Weapons">{weaponGridElement}</ul> <ul id="Weapons">{weaponGridElement}</ul>

View file

@ -51,6 +51,9 @@
}, },
"remove": "Remove from grid" "remove": "Remove from grid"
}, },
"errors": {
"unauthorized": "You don't have permission to perform that action"
},
"filters": { "filters": {
"labels": { "labels": {
"element": "Element", "element": "Element",

View file

@ -59,6 +59,9 @@
"rarity": "レアリティ" "rarity": "レアリティ"
} }
}, },
"errors": {
"unauthorized": "行ったアクションを実行する権限がありません"
},
"header": { "header": {
"anonymous": "無名", "anonymous": "無名",
"untitled_team": "{{username}}さんからの無題編成", "untitled_team": "{{username}}さんからの無題編成",