diff --git a/components/Layout/index.tsx b/components/Layout/index.tsx index a77014c5..6aafdde1 100644 --- a/components/Layout/index.tsx +++ b/components/Layout/index.tsx @@ -17,16 +17,18 @@ const Layout = ({ children }: PropsWithChildren) => { const [updateToastOpen, setUpdateToastOpen] = useState(false) useEffect(() => { - const cookie = getToastCookie() - const now = new Date() - const updatedAt = new Date(appState.version.updated_at) - const validUntil = add(updatedAt, { days: 7 }) + if (appState.version) { + const cookie = getToastCookie() + const now = new Date() + const updatedAt = new Date(appState.version.updated_at) + const validUntil = add(updatedAt, { days: 7 }) - if (now < validUntil && !cookie.seen) setUpdateToastOpen(true) + if (now < validUntil && !cookie.seen) setUpdateToastOpen(true) + } }, []) function getToastCookie() { - if (appState.version.updated_at !== '') { + if (appState.version && appState.version.updated_at !== '') { const updatedAt = new Date(appState.version.updated_at) const cookieValues = getCookie( `update-${format(updatedAt, 'yyyy-MM-dd')}` @@ -50,23 +52,32 @@ const Layout = ({ children }: PropsWithChildren) => { const updateToast = () => { const path = router.asPath.replaceAll('/', '') - return !['about', 'updates', 'roadmap'].includes(path) ? ( - - ) : ( - '' + return ( + !['about', 'updates', 'roadmap'].includes(path) && + appState.version && ( + + ) + ) + } + + const ServerAvailable = () => { + return ( + <> + + {updateToast()} + ) } return ( <> - - {updateToast()} + {appState.version && ServerAvailable}
{children}
) diff --git a/pages/_app.tsx b/pages/_app.tsx index ab639d9d..1486ab98 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,10 +1,13 @@ import { appWithTranslation } from 'next-i18next' +import Link from 'next/link' +import { useTranslation } from 'next-i18next' import { get } from 'local-storage' import { getCookie, setCookie } from 'cookies-next' import { subscribe } from 'valtio' import { useEffect, useState } from 'react' import { ThemeProvider } from 'next-themes' +import { appState } from '~utils/appState' import { accountState } from '~utils/accountState' import { retrieveCookies } from '~utils/retrieveCookies' import { setHeaders } from '~utils/userToken' @@ -16,9 +19,12 @@ import Layout from '~components/Layout' import type { AppProps } from 'next/app' +import DiscordIcon from '~public/icons/discord.svg' +import ShareIcon from '~public/icons/Share.svg' import '../styles/globals.scss' function MyApp({ Component, pageProps }: AppProps) { + const { t } = useTranslation('common') const [refresh, setRefresh] = useState(false) // Subscribe to app state to listen for account changes and @@ -88,12 +94,47 @@ function MyApp({ Component, pageProps }: AppProps) { } } + const serverUnavailable = () => { + return ( +
+
+
+

{t('errors.server_unavailable.title')}

+

{t('errors.server_unavailable.message')}

+
+
+

{t('errors.server_unavailable.discord')}

+ +
+
+
+ ) + } + return ( - + {appState.version ? ( + serverUnavailable() + ) : ( + + )} diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 2d23f711..e19e664e 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -87,6 +87,11 @@ "description": "The page you're looking for couldn't be found", "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": { "guidebooks": "You cannot equip more than one of each Sephira Guidebook" }, diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json index f4351ccd..01f04098 100644 --- a/public/locales/ja/common.json +++ b/public/locales/ja/common.json @@ -87,6 +87,11 @@ "description": "探しているページは見つかりませんでした", "button": "新しい編成を作成" }, + "server_unavailable": { + "title": "サーバーダウン", + "message": "現在サーバーがダウンしています。しばらくしてから再度お試しください", + "discord": "Discordでサービスの状態を確認する" + }, "validation": { "guidebooks": "セフィラ導本を複数個装備することはできません" }, diff --git a/styles/globals.scss b/styles/globals.scss index ec53412d..b868d270 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -152,6 +152,188 @@ select { 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 { $diameter: $unit-6x; 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; - } - } -} diff --git a/utils/appState.tsx b/utils/appState.tsx index 797f1664..73657e9c 100644 --- a/utils/appState.tsx +++ b/utils/appState.tsx @@ -88,7 +88,7 @@ interface AppState { jobs: Job[] jobSkills: JobSkill[] weaponKeys: GroupedWeaponKeys - version: AppUpdate + version?: AppUpdate status?: ResponseStatus }