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,6 +152,188 @@ select {
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Teams,
|
||||||
|
#Profile {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#NotFound {
|
||||||
|
height: 200px;
|
||||||
|
width: 400px;
|
||||||
|
margin: auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: $grey-60;
|
||||||
|
font-size: $font-regular;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img.profile {
|
||||||
|
background: $grey-90;
|
||||||
|
|
||||||
|
&.fire {
|
||||||
|
background: $fire-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.water {
|
||||||
|
background: $water-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.wind {
|
||||||
|
background: $wind-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.earth {
|
||||||
|
background: $earth-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
background: $dark-bg-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
background: $light-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.anonymous {
|
||||||
|
background: var(--anonymous-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i.tag {
|
||||||
|
background: var(--tag-bg);
|
||||||
|
color: var(--tag-text);
|
||||||
|
border-radius: calc($unit / 2);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: $bold;
|
||||||
|
padding: 4px 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infinite-scroll-component {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchFilterBar {
|
||||||
|
display: flex;
|
||||||
|
gap: $unit;
|
||||||
|
padding: 0 ($unit * 3);
|
||||||
|
|
||||||
|
@include breakpoint(phone) {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Joined {
|
||||||
|
$offset: 2px;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
background: var(--input-bg);
|
||||||
|
border-radius: $input-corner;
|
||||||
|
border: $offset solid transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit;
|
||||||
|
padding-top: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
padding-right: calc($unit-2x - $offset);
|
||||||
|
|
||||||
|
&.Bound {
|
||||||
|
background-color: var(--input-bound-bg);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--input-bound-bg-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border: $offset solid $blue;
|
||||||
|
// box-shadow: 0 2px rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Counter {
|
||||||
|
color: $grey-55;
|
||||||
|
font-weight: $bold;
|
||||||
|
line-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Input {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: $unit * 1.5 $unit-2x;
|
||||||
|
padding-left: calc($unit-2x - $offset);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
.LinkItem {
|
||||||
$diameter: $unit-6x;
|
$diameter: $unit-6x;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -225,131 +407,3 @@ select {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Teams,
|
|
||||||
#Profile {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#NotFound {
|
|
||||||
height: 200px;
|
|
||||||
width: 400px;
|
|
||||||
margin: auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
color: $grey-60;
|
|
||||||
font-size: $font-regular;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img.profile {
|
|
||||||
background: $grey-90;
|
|
||||||
|
|
||||||
&.fire {
|
|
||||||
background: $fire-bg-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.water {
|
|
||||||
background: $water-bg-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.wind {
|
|
||||||
background: $wind-bg-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.earth {
|
|
||||||
background: $earth-bg-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dark {
|
|
||||||
background: $dark-bg-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.light {
|
|
||||||
background: $light-bg-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.anonymous {
|
|
||||||
background: var(--anonymous-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i.tag {
|
|
||||||
background: var(--tag-bg);
|
|
||||||
color: var(--tag-text);
|
|
||||||
border-radius: calc($unit / 2);
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: $bold;
|
|
||||||
padding: 4px 6px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infinite-scroll-component {
|
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SearchFilterBar {
|
|
||||||
display: flex;
|
|
||||||
gap: $unit;
|
|
||||||
padding: 0 ($unit * 3);
|
|
||||||
|
|
||||||
@include breakpoint(phone) {
|
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.Joined {
|
|
||||||
$offset: 2px;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
background: var(--input-bg);
|
|
||||||
border-radius: $input-corner;
|
|
||||||
border: $offset solid transparent;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
gap: $unit;
|
|
||||||
padding-top: 2px;
|
|
||||||
padding-bottom: 2px;
|
|
||||||
padding-right: calc($unit-2x - $offset);
|
|
||||||
|
|
||||||
&.Bound {
|
|
||||||
background-color: var(--input-bound-bg);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--input-bound-bg-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus-within {
|
|
||||||
border: $offset solid $blue;
|
|
||||||
// box-shadow: 0 2px rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.Counter {
|
|
||||||
color: $grey-55;
|
|
||||||
font-weight: $bold;
|
|
||||||
line-height: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Input {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
padding: $unit * 1.5 $unit-2x;
|
|
||||||
padding-left: calc($unit-2x - $offset);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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