Add a message if the server goes down
Right now the app fails silently if the server becomes unreachable. Now, we use the version check to determine if we have a connection to the server, and if not we display an error message.
This commit is contained in:
parent
9de87abd1e
commit
ab671b509d
6 changed files with 264 additions and 148 deletions
|
|
@ -17,16 +17,18 @@ const Layout = ({ children }: PropsWithChildren<Props>) => {
|
||||||
const [updateToastOpen, setUpdateToastOpen] = useState(false)
|
const [updateToastOpen, setUpdateToastOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (appState.version) {
|
||||||
const cookie = getToastCookie()
|
const cookie = getToastCookie()
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const updatedAt = new Date(appState.version.updated_at)
|
const updatedAt = new Date(appState.version.updated_at)
|
||||||
const validUntil = add(updatedAt, { days: 7 })
|
const validUntil = add(updatedAt, { days: 7 })
|
||||||
|
|
||||||
if (now < validUntil && !cookie.seen) setUpdateToastOpen(true)
|
if (now < validUntil && !cookie.seen) setUpdateToastOpen(true)
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
function getToastCookie() {
|
function getToastCookie() {
|
||||||
if (appState.version.updated_at !== '') {
|
if (appState.version && appState.version.updated_at !== '') {
|
||||||
const updatedAt = new Date(appState.version.updated_at)
|
const updatedAt = new Date(appState.version.updated_at)
|
||||||
const cookieValues = getCookie(
|
const cookieValues = getCookie(
|
||||||
`update-${format(updatedAt, 'yyyy-MM-dd')}`
|
`update-${format(updatedAt, 'yyyy-MM-dd')}`
|
||||||
|
|
@ -50,7 +52,9 @@ const Layout = ({ children }: PropsWithChildren<Props>) => {
|
||||||
const updateToast = () => {
|
const updateToast = () => {
|
||||||
const path = router.asPath.replaceAll('/', '')
|
const path = router.asPath.replaceAll('/', '')
|
||||||
|
|
||||||
return !['about', 'updates', 'roadmap'].includes(path) ? (
|
return (
|
||||||
|
!['about', 'updates', 'roadmap'].includes(path) &&
|
||||||
|
appState.version && (
|
||||||
<UpdateToast
|
<UpdateToast
|
||||||
open={updateToastOpen}
|
open={updateToastOpen}
|
||||||
updateType={appState.version.update_type}
|
updateType={appState.version.update_type}
|
||||||
|
|
@ -58,15 +62,22 @@ const Layout = ({ children }: PropsWithChildren<Props>) => {
|
||||||
onCloseClicked={handleToastClosed}
|
onCloseClicked={handleToastClosed}
|
||||||
lastUpdated={appState.version.updated_at}
|
lastUpdated={appState.version.updated_at}
|
||||||
/>
|
/>
|
||||||
) : (
|
)
|
||||||
''
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServerAvailable = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TopHeader />
|
||||||
|
{updateToast()}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopHeader />
|
{appState.version && ServerAvailable}
|
||||||
{updateToast()}
|
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import { appWithTranslation } from 'next-i18next'
|
import { appWithTranslation } from 'next-i18next'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
import { get } from 'local-storage'
|
import { get } from 'local-storage'
|
||||||
import { getCookie, setCookie } from 'cookies-next'
|
import { getCookie, setCookie } from 'cookies-next'
|
||||||
import { subscribe } from 'valtio'
|
import { subscribe } from 'valtio'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { ThemeProvider } from 'next-themes'
|
import { ThemeProvider } from 'next-themes'
|
||||||
|
|
||||||
|
import { appState } from '~utils/appState'
|
||||||
import { accountState } from '~utils/accountState'
|
import { accountState } from '~utils/accountState'
|
||||||
import { retrieveCookies } from '~utils/retrieveCookies'
|
import { retrieveCookies } from '~utils/retrieveCookies'
|
||||||
import { setHeaders } from '~utils/userToken'
|
import { setHeaders } from '~utils/userToken'
|
||||||
|
|
@ -16,9 +19,12 @@ import Layout from '~components/Layout'
|
||||||
|
|
||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
|
|
||||||
|
import DiscordIcon from '~public/icons/discord.svg'
|
||||||
|
import ShareIcon from '~public/icons/Share.svg'
|
||||||
import '../styles/globals.scss'
|
import '../styles/globals.scss'
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
const { t } = useTranslation('common')
|
||||||
const [refresh, setRefresh] = useState(false)
|
const [refresh, setRefresh] = useState(false)
|
||||||
|
|
||||||
// Subscribe to app state to listen for account changes and
|
// Subscribe to app state to listen for account changes and
|
||||||
|
|
@ -88,12 +94,47 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const serverUnavailable = () => {
|
||||||
|
return (
|
||||||
|
<div className="ServerUnavailableWrapper">
|
||||||
|
<div className="ServerUnavailable">
|
||||||
|
<div className="Message">
|
||||||
|
<h1>{t('errors.server_unavailable.title')}</h1>
|
||||||
|
<p>{t('errors.server_unavailable.message')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="Connect">
|
||||||
|
<p>{t('errors.server_unavailable.discord')}</p>
|
||||||
|
<div className="Discord LinkItem">
|
||||||
|
<Link href="https://discord.gg/qyZ5hGdPC8">
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/qyZ5hGdPC8"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<div className="Left">
|
||||||
|
<DiscordIcon />
|
||||||
|
<h3>granblue-tools</h3>
|
||||||
|
</div>
|
||||||
|
<ShareIcon className="ShareIcon" />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<ToastProvider swipeDirection="right">
|
<ToastProvider swipeDirection="right">
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Layout>
|
<Layout>
|
||||||
|
{appState.version ? (
|
||||||
|
serverUnavailable()
|
||||||
|
) : (
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
)}
|
||||||
</Layout>
|
</Layout>
|
||||||
<Viewport className="ToastViewport" />
|
<Viewport className="ToastViewport" />
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,11 @@
|
||||||
"description": "The page you're looking for couldn't be found",
|
"description": "The page you're looking for couldn't be found",
|
||||||
"button": "Create a new party"
|
"button": "Create a new party"
|
||||||
},
|
},
|
||||||
|
"server_unavailable": {
|
||||||
|
"title": "Server unavailable",
|
||||||
|
"message": "We're having trouble connecting to the server right now",
|
||||||
|
"discord": "Join us on Discord for status updates"
|
||||||
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"guidebooks": "You cannot equip more than one of each Sephira Guidebook"
|
"guidebooks": "You cannot equip more than one of each Sephira Guidebook"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,11 @@
|
||||||
"description": "探しているページは見つかりませんでした",
|
"description": "探しているページは見つかりませんでした",
|
||||||
"button": "新しい編成を作成"
|
"button": "新しい編成を作成"
|
||||||
},
|
},
|
||||||
|
"server_unavailable": {
|
||||||
|
"title": "サーバーダウン",
|
||||||
|
"message": "現在サーバーがダウンしています。しばらくしてから再度お試しください",
|
||||||
|
"discord": "Discordでサービスの状態を確認する"
|
||||||
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"guidebooks": "セフィラ導本を複数個装備することはできません"
|
"guidebooks": "セフィラ導本を複数個装備することはできません"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -152,80 +152,6 @@ select {
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.LinkItem {
|
|
||||||
$diameter: $unit-6x;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--dialog-bg);
|
|
||||||
border: 1px solid var(--link-item-bg);
|
|
||||||
border-radius: $card-corner;
|
|
||||||
display: flex;
|
|
||||||
min-height: 82px;
|
|
||||||
transition: background $duration-zoom ease-in,
|
|
||||||
transform $duration-zoom ease-in;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--link-item-bg);
|
|
||||||
color: var(--text-primary);
|
|
||||||
|
|
||||||
.ShareIcon {
|
|
||||||
fill: var(--text-primary);
|
|
||||||
transform: translate($unit-half, calc(($unit * -1) / 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.Github {
|
|
||||||
&:hover {
|
|
||||||
.Left svg {
|
|
||||||
fill: var(--text-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.Discord:hover .Left svg {
|
|
||||||
fill: #5865f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: $unit-2x;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Left {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: $unit-2x;
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: 600;
|
|
||||||
max-width: 70%;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: var(--link-item-image-color);
|
|
||||||
width: $diameter;
|
|
||||||
height: auto;
|
|
||||||
transition: fill $duration-zoom ease-in;
|
|
||||||
|
|
||||||
&.ShareIcon {
|
|
||||||
width: $unit-4x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: $bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Teams,
|
#Teams,
|
||||||
#Profile {
|
#Profile {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -353,3 +279,131 @@ i.tag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ServerUnavailableWrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
.ServerUnavailable {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-6x;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 440px;
|
||||||
|
|
||||||
|
.Message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: $font-xxlarge;
|
||||||
|
font-weight: $bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: $font-regular;
|
||||||
|
font-weight: $bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Connect {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: $unit-2x;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.LinkItem {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.LinkItem {
|
||||||
|
$diameter: $unit-6x;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--dialog-bg);
|
||||||
|
border: 1px solid var(--link-item-bg);
|
||||||
|
border-radius: $card-corner;
|
||||||
|
display: flex;
|
||||||
|
min-height: 82px;
|
||||||
|
transition: background $duration-zoom ease-in,
|
||||||
|
transform $duration-zoom ease-in;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--link-item-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
.ShareIcon {
|
||||||
|
fill: var(--text-primary);
|
||||||
|
transform: translate($unit-half, calc(($unit * -1) / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Github {
|
||||||
|
&:hover {
|
||||||
|
.Left svg {
|
||||||
|
fill: var(--text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.Discord:hover .Left svg {
|
||||||
|
fill: #5865f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: $unit-2x;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Left {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit-2x;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: 600;
|
||||||
|
max-width: 70%;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--link-item-image-color);
|
||||||
|
width: $diameter;
|
||||||
|
height: auto;
|
||||||
|
transition: fill $duration-zoom ease-in;
|
||||||
|
|
||||||
|
&.ShareIcon {
|
||||||
|
width: $unit-4x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: $bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ interface AppState {
|
||||||
jobs: Job[]
|
jobs: Job[]
|
||||||
jobSkills: JobSkill[]
|
jobSkills: JobSkill[]
|
||||||
weaponKeys: GroupedWeaponKeys
|
weaponKeys: GroupedWeaponKeys
|
||||||
version: AppUpdate
|
version?: AppUpdate
|
||||||
status?: ResponseStatus
|
status?: ResponseStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue