From f9316510e18416b4293739d5511da1117cacaba3 Mon Sep 17 00:00:00 2001
From: TurtIeSocks <58572875+TurtIeSocks@users.noreply.github.com>
Date: Tue, 22 Mar 2022 23:36:02 -0400
Subject: [PATCH 1/4] Hashes and Consistent Esc Key
- Upgrade React Router to v6
- Update syntax for v6
- Add dialog hashes to use the back button, vaguely works
- Consistently exit dialogs with the esc key
- Shuffles things around a little bit for better rerendering when navigating hashes
---
example.env | 1 +
package.json | 2 +-
public/base-locales/en.json | 1 +
server/src/models/Pokemon.js | 16 +--
src/components/App.jsx | 9 +-
src/components/ClearStorage.jsx | 4 +-
src/components/Config.jsx | 72 +++++++++++
src/components/Container.jsx | 27 +++++
src/components/ReactRouter.jsx | 109 ++++-------------
src/components/RouteChangeTracker.jsx | 14 ---
src/components/WebhookQuery.jsx | 14 +--
src/components/layout/FloatingBtn.jsx | 20 +++-
src/components/layout/Nav.jsx | 26 +++-
src/components/layout/auth/Auth.jsx | 24 ++--
src/components/layout/auth/Local.jsx | 4 +-
src/components/layout/auth/Login.jsx | 9 +-
.../layout/dialogs/ResetFilters.jsx | 4 +-
src/components/layout/dialogs/UserProfile.jsx | 2 +-
.../layout/dialogs/webhooks/Manage.jsx | 5 +-
.../layout/dialogs/webhooks/Webhook.jsx | 9 ++
.../dialogs/webhooks/tiles/TrackedTile.jsx | 1 +
src/components/layout/general/Menu.jsx | 24 ++++
src/components/popups/Gym.jsx | 2 +-
.../ConfigSettings.jsx => hooks/useConfig.js} | 40 +------
src/hooks/useHash.js | 27 +++++
src/index.jsx | 6 +-
yarn.lock | 112 +++++-------------
27 files changed, 316 insertions(+), 268 deletions(-)
create mode 100644 src/components/Config.jsx
create mode 100644 src/components/Container.jsx
delete mode 100644 src/components/RouteChangeTracker.jsx
rename src/{components/ConfigSettings.jsx => hooks/useConfig.js} (86%)
create mode 100644 src/hooks/useHash.js
diff --git a/example.env b/example.env
index 6637bc24e..9e75dc1df 100644
--- a/example.env
+++ b/example.env
@@ -6,3 +6,4 @@ SENTRY_AUTH_TOKEN=""
SENTRY_ORG=""
SENTRY_PROJECT=""
SENTRY_TRACES_SAMPLE_RATE=0.1
+SENTRY_DEBUG=
diff --git a/package.json b/package.json
index ed0c823da..2080befae 100644
--- a/package.json
+++ b/package.json
@@ -96,7 +96,7 @@
"react-i18next": "^11.9.0",
"react-leaflet": "3.2.2",
"react-leaflet-markercluster": "3.0.0-rc1",
- "react-router-dom": "^5.2.0",
+ "react-router-dom": "^6.2.0",
"react-telegram-login": "^1.1.2",
"react-virtualized-auto-sizer": "^1.0.5",
"react-window": "^1.8.6",
diff --git a/public/base-locales/en.json b/public/base-locales/en.json
index 85216d914..5816e0a7e 100644
--- a/public/base-locales/en.json
+++ b/public/base-locales/en.json
@@ -493,5 +493,6 @@
"loading": "Loading {{category}}",
"loading_icons": "Fetching Icons",
"loading_invasions": "Fetching Invasions",
+ "loading_settings": "Fetching Settings",
"pvp_ranking_cap": "Level"
}
diff --git a/server/src/models/Pokemon.js b/server/src/models/Pokemon.js
index d479e47f0..d017adf47 100644
--- a/server/src/models/Pokemon.js
+++ b/server/src/models/Pokemon.js
@@ -169,6 +169,14 @@ module.exports = class Pokemon extends Model {
const generateSql = (queryBase, filter, relevant) => {
relevant.forEach(key => {
switch (key) {
+ case 'level':
+ case 'atk_iv':
+ case 'def_iv':
+ case 'sta_iv':
+ case 'iv':
+ if (ivs) {
+ queryBase.andWhereBetween(isMad ? madKeys[key] : key, filter[key])
+ } break
default:
if (pvp) {
queryPvp = true
@@ -181,14 +189,6 @@ module.exports = class Pokemon extends Model {
queryBase.whereNull('pokemon_id')
}
} break
- case 'level':
- case 'atk_iv':
- case 'def_iv':
- case 'sta_iv':
- case 'iv':
- if (ivs) {
- queryBase.andWhereBetween(isMad ? madKeys[key] : key, filter[key])
- } break
}
})
}
diff --git a/src/components/App.jsx b/src/components/App.jsx
index d29fdff4b..d63000590 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -1,10 +1,12 @@
import '@assets/css/main.css'
import React, { Suspense } from 'react'
+import { BrowserRouter } from 'react-router-dom'
+
import { ApolloProvider } from '@apollo/client'
import client from '@services/apollo'
-import ReactRouter from './ReactRouter'
+import Config from './Config'
const SetText = () => {
const locales = {
@@ -31,11 +33,12 @@ const SetText = () => {
export default function App() {
document.body.classList.add('dark')
-
return (
}>
-
+
+
+
)
diff --git a/src/components/ClearStorage.jsx b/src/components/ClearStorage.jsx
index 27f0d9789..61b592361 100644
--- a/src/components/ClearStorage.jsx
+++ b/src/components/ClearStorage.jsx
@@ -1,8 +1,8 @@
import React from 'react'
-import { Redirect } from 'react-router-dom'
+import { Navigate } from 'react-router-dom'
export default function ClearStorage() {
localStorage.clear()
sessionStorage.clear()
- return
+ return
}
diff --git a/src/components/Config.jsx b/src/components/Config.jsx
new file mode 100644
index 000000000..e50b0db22
--- /dev/null
+++ b/src/components/Config.jsx
@@ -0,0 +1,72 @@
+import React, { useEffect, useState, useCallback } from 'react'
+import { ThemeProvider } from '@material-ui/styles'
+import { useTranslation } from 'react-i18next'
+
+import setTheme from '@assets/mui/theme'
+import UIcons from '@services/Icons'
+import Fetch from '@services/Fetch'
+
+import ReactRouter from './ReactRouter'
+import HolidayEffects from './HolidayEffects'
+
+const rootLoading = document.getElementById('loader')
+const loadingText = document.getElementById('loading-text')
+
+export default function Config() {
+ const { t } = useTranslation()
+ const [serverSettings, setServerSettings] = useState(null)
+
+ if (rootLoading) {
+ if (serverSettings) {
+ rootLoading.style.display = 'none'
+ }
+ }
+
+ const getServerSettings = useCallback(async () => {
+ if (loadingText) {
+ loadingText.innerHTML = t('loading_settings')
+ }
+ const data = await Fetch.getSettings()
+ const Icons = data.masterfile ? new UIcons(data.config.icons, data.masterfile.questRewardTypes) : null
+ if (Icons) {
+ if (loadingText) {
+ loadingText.innerText = t('loading_icons')
+ }
+ await Icons.fetchIcons(data.config.icons.styles)
+ if (data.config.icons.defaultIcons) {
+ Icons.setSelection(data.config.icons.defaultIcons)
+ }
+ }
+ if (data.ui?.pokestops?.invasions && data.config?.map.fetchLatestInvasions) {
+ const invasionCache = JSON.parse(localStorage.getItem('invasions_cache'))
+ const cacheTime = data.config.map.invasionCacheHrs * 60 * 60 * 1000
+ if (invasionCache && invasionCache.lastFetched + cacheTime > Date.now()) {
+ data.masterfile.invasions = invasionCache
+ } else {
+ if (loadingText) {
+ loadingText.innerText = t('loading_invasions')
+ }
+ data.masterfile.invasions = await Fetch.getInvasions(data.masterfile.invasions)
+ }
+ }
+ setServerSettings({ ...data, Icons })
+ }, [])
+
+ useEffect(() => {
+ if (!serverSettings) {
+ getServerSettings()
+ }
+ }, [])
+
+ if (!serverSettings) {
+ return
+ }
+
+ return (
+
+
+
+
+
+ )
+}
diff --git a/src/components/Container.jsx b/src/components/Container.jsx
new file mode 100644
index 000000000..bd3d3d77f
--- /dev/null
+++ b/src/components/Container.jsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import { MapContainer } from 'react-leaflet'
+import { useStore } from '@hooks/useStore'
+
+import Map from './Map'
+
+export default function Container({ serverSettings, params }) {
+ const location = useStore(state => state.location)
+ const zoom = useStore(state => state.zoom)
+
+ return (
+
+ {(serverSettings.user && serverSettings.user.perms.map) && (
+
+ )}
+
+ )
+}
diff --git a/src/components/ReactRouter.jsx b/src/components/ReactRouter.jsx
index dc4d19f20..ae0a617b9 100644
--- a/src/components/ReactRouter.jsx
+++ b/src/components/ReactRouter.jsx
@@ -1,95 +1,36 @@
-import React, { useEffect, useState, useCallback } from 'react'
-import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
-import { useTranslation } from 'react-i18next'
-import { ThemeProvider } from '@material-ui/styles'
-
-import setTheme from '@assets/mui/theme'
-import UIcons from '@services/Icons'
-import Fetch from '@services/Fetch'
+import React from 'react'
+import { Route, Routes } from 'react-router-dom'
+import useConfig from '@hooks/useConfig'
import Auth from './layout/auth/Auth'
import Login from './layout/auth/Login'
-import RouteChangeTracker from './RouteChangeTracker'
import Errors from './Errors'
import ClearStorage from './ClearStorage'
-import HolidayEffects from './HolidayEffects'
-
-const rootLoading = document.getElementById('loader')
-const loadingText = document.getElementById('loading-text')
-
-export default function ReactRouter() {
- const { t } = useTranslation()
- const [serverSettings, setServerSettings] = useState(null)
-
- if (rootLoading) {
- if (serverSettings) {
- rootLoading.style.display = 'none'
- }
- }
- const getServerSettings = useCallback(async () => {
- const data = await Fetch.getSettings()
- const Icons = data.masterfile ? new UIcons(data.config.icons, data.masterfile.questRewardTypes) : null
- if (Icons) {
- if (loadingText) {
- loadingText.innerText = t('loading_icons')
- }
- await Icons.fetchIcons(data.config.icons.styles)
- if (data.config.icons.defaultIcons) {
- Icons.setSelection(data.config.icons.defaultIcons)
- }
- }
- if (data.ui?.pokestops?.invasions && data.config?.map.fetchLatestInvasions) {
- const invasionCache = JSON.parse(localStorage.getItem('invasions_cache'))
- const cacheTime = data.config.map.invasionCacheHrs * 60 * 60 * 1000
- if (invasionCache && invasionCache.lastFetched + cacheTime > Date.now()) {
- data.masterfile.invasions = invasionCache
- } else {
- if (loadingText) {
- loadingText.innerText = t('loading_invasions')
- }
- data.masterfile.invasions = await Fetch.getInvasions(data.masterfile.invasions)
- }
- }
- setServerSettings({ ...data, Icons })
- }, [])
+export default function ReactRouter({ serverSettings, getServerSettings }) {
+ useConfig(serverSettings)
- useEffect(() => {
- if (!serverSettings) {
- getServerSettings()
- }
- }, [])
+ const authRoute =
return (
-
-
- {(inject.GOOGLE_ANALYTICS_ID) && }
-
-
-
-
-
- {serverSettings && }
-
-
- {serverSettings && (
-
- )}
-
-
- {serverSettings && }
-
-
- {serverSettings && }
-
-
-
-
-
-
+
+
+ } />
+
+ )}
+ />
+
+
+
+
+ } />
+
)
}
diff --git a/src/components/RouteChangeTracker.jsx b/src/components/RouteChangeTracker.jsx
deleted file mode 100644
index 8bd40378c..000000000
--- a/src/components/RouteChangeTracker.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react'
-import { withRouter } from 'react-router-dom'
-import ReactGA from 'react-ga'
-
-const RouteChangeTracker = ({ history }) => {
- history.listen((location) => {
- ReactGA.set({ page: location.pathname })
- ReactGA.pageview(location.pathname)
- })
-
- return
-}
-
-export default withRouter(RouteChangeTracker)
diff --git a/src/components/WebhookQuery.jsx b/src/components/WebhookQuery.jsx
index 9bbef6b8b..52b4aa1dc 100644
--- a/src/components/WebhookQuery.jsx
+++ b/src/components/WebhookQuery.jsx
@@ -2,10 +2,10 @@ import React from 'react'
import { useQuery } from '@apollo/client'
import Query from '@services/Query'
-import ConfigSettings from './ConfigSettings'
+import ConfigSettings from './Container'
-export default function WebhookQuery({ match, serverSettings }) {
- let lowercase = match.params.category.toLowerCase()
+export default function WebhookQuery({ params, serverSettings }) {
+ let lowercase = params.category.toLowerCase()
if (lowercase === 'invasions'
|| lowercase === 'lures'
|| lowercase === 'quests') {
@@ -16,8 +16,8 @@ export default function WebhookQuery({ match, serverSettings }) {
}
const { data } = useQuery(Query[lowercase]('id'), {
variables: {
- id: match.params.id,
- perm: match.params.category.toLowerCase(),
+ id: params.id,
+ perm: params.category.toLowerCase(),
},
})
return data ? (
@@ -25,8 +25,8 @@ export default function WebhookQuery({ match, serverSettings }) {
paramLocation={data[`${lowercase}Single`]
? [data[`${lowercase}Single`].lat, data[`${lowercase}Single`].lon]
: null}
- paramZoom={match.params.zoom}
- match={match}
+ paramZoom={params.zoom}
+ params={params}
serverSettings={serverSettings}
/>
) : null
diff --git a/src/components/layout/FloatingBtn.jsx b/src/components/layout/FloatingBtn.jsx
index 364229924..7a7e952d0 100644
--- a/src/components/layout/FloatingBtn.jsx
+++ b/src/components/layout/FloatingBtn.jsx
@@ -10,6 +10,7 @@ import L from 'leaflet'
import useStyles from '@hooks/useStyles'
import useLocation from '@hooks/useLocation'
import { useStore, useStatic } from '@hooks/useStore'
+import useHash from '@hooks/useHash'
const DonationIcons = {
dollar: AttachMoney,
@@ -24,6 +25,8 @@ export default function FloatingButtons({
setUserProfile,
}) {
const { t } = useTranslation()
+ const [isOpen, toggleHash, hash] = useHash()
+
const { map: { enableFloatingProfileButton } } = useStatic(state => state.config)
const { loggedIn } = useStatic(state => state.auth)
const map = useMap()
@@ -42,6 +45,12 @@ export default function FloatingButtons({
const DonorIcon = showDonorPage ? DonationIcons[donationPage.fabIcon || 'card'] : null
+ useEffect(() => {
+ if (hash.includes(`#${selectedWebhook}`)) {
+ setWebhookMode(isOpen ? 'open' : false)
+ }
+ }, [isOpen])
+
return (
- setWebhookMode('open')} title={selectedWebhook} disabled={Boolean(webhookMode)}>
+ {
+ toggleHash(true, [selectedWebhook])
+ setWebhookMode('open')
+ }}
+ title={selectedWebhook}
+ disabled={Boolean(webhookMode)}
+ >
diff --git a/src/components/layout/Nav.jsx b/src/components/layout/Nav.jsx
index 9ee29352f..faf7d8061 100644
--- a/src/components/layout/Nav.jsx
+++ b/src/components/layout/Nav.jsx
@@ -1,10 +1,11 @@
-import React, { useState } from 'react'
+import React, { useState, useEffect } from 'react'
import { Dialog, Snackbar } from '@material-ui/core'
import { Alert } from '@material-ui/lab'
import Utility from '@services/Utility'
import useStyles from '@hooks/useStyles'
import { useStore, useStatic } from '@hooks/useStore'
+import useHash from '@hooks/useHash'
import SlideTransition from '@assets/mui/SlideTransition'
import FloatingBtn from './FloatingBtn'
@@ -27,6 +28,8 @@ export default function Nav({
isMobile, isTablet,
}) {
const classes = useStyles()
+ const [isOpen, toggleHash] = useHash()
+ const [isDrawerOpen, toggleDrawerHash, drawerHash] = useHash()
const { perms } = useStatic(state => state.auth)
const webhookAlert = useStatic(state => state.webhookAlert)
@@ -37,6 +40,7 @@ export default function Nav({
const feedback = useStatic(state => state.feedback)
const setFeedback = useStatic(state => state.setFeedback)
const resetFilters = useStatic(state => state.resetFilters)
+ const setResetFilters = useStatic(state => state.setResetFilters)
const filters = useStore(state => state.filters)
const setFilters = useStore(state => state.setFilters)
@@ -67,6 +71,7 @@ export default function Nav({
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return
}
+ toggleDrawerHash(open, ['drawer'])
setDrawer(open)
}
@@ -82,8 +87,8 @@ export default function Nav({
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return
}
+ toggleHash(open, [type, category], true)
setDialog({ open, category, type })
-
if (filter && type === 'search') {
setManualParams({ id: filter.id })
map.flyTo([filter.lat, filter.lon], 16)
@@ -96,6 +101,14 @@ export default function Nav({
}
}
+ useEffect(() => {
+ setDialog({ ...dialog, open: isOpen })
+ }, [isOpen])
+
+ useEffect(() => {
+ setDrawer(isDrawerOpen && drawerHash.includes('#drawer'))
+ }, [isDrawerOpen])
+
return (
<>
{drawer ? (
@@ -127,6 +140,7 @@ export default function Nav({
open={userProfile}
fullScreen={isMobile}
fullWidth={!isMobile}
+ onClose={() => setUserProfile(false)}
>
setTutorial(false)}
>
diff --git a/src/components/layout/auth/Auth.jsx b/src/components/layout/auth/Auth.jsx
index b1db329a6..b5317388c 100644
--- a/src/components/layout/auth/Auth.jsx
+++ b/src/components/layout/auth/Auth.jsx
@@ -1,21 +1,23 @@
import React from 'react'
-import { Redirect, withRouter } from 'react-router-dom'
+import { Navigate, useParams } from 'react-router-dom'
-import ConfigSettings from '../../ConfigSettings'
+import ConfigSettings from '../../Container'
import Login from './Login'
import WebhookQuery from '../../WebhookQuery'
-const Auth = ({ serverSettings, getServerSettings, match }) => {
+export default function Auth({ serverSettings, getServerSettings }) {
+ const params = useParams()
+
if (serverSettings.error) {
return (
-
+
)
}
if ((serverSettings.authMethods.length && !serverSettings.user)
|| (serverSettings.user && !serverSettings.user?.perms?.map)) {
- if (match.params.category || match.params.lat) {
- localStorage.setItem('params', JSON.stringify(match.params))
+ if (params.category || params.lat) {
+ localStorage.setItem('params', JSON.stringify(params))
}
return
}
@@ -25,19 +27,17 @@ const Auth = ({ serverSettings, getServerSettings, match }) => {
const url = cachedParams.category
? `/id/${cachedParams.category}/${cachedParams.id}/${cachedParams.zoom || 18}`
: `/@/${cachedParams.lat}/${cachedParams.lon}/${cachedParams.zoom || 18}`
- return
+ return
}
- if (match.params.category) {
+ if (params.category) {
return (
)
}
return (
-
+
)
}
-
-export default withRouter(Auth)
diff --git a/src/components/layout/auth/Local.jsx b/src/components/layout/auth/Local.jsx
index 9f3ff49a6..999f1b665 100644
--- a/src/components/layout/auth/Local.jsx
+++ b/src/components/layout/auth/Local.jsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-import { Redirect } from 'react-router-dom'
+import { Navigate } from 'react-router-dom'
import {
Grid, Typography, Button, OutlinedInput, InputLabel, FormControl, InputAdornment, IconButton,
} from '@material-ui/core'
@@ -47,7 +47,7 @@ export default function LocalLogin({ href, serverSettings, getServerSettings })
}
if (redirect) {
- return
+ return
}
return (
diff --git a/src/components/layout/auth/Login.jsx b/src/components/layout/auth/Login.jsx
index 0eb94dc40..331d6532c 100644
--- a/src/components/layout/auth/Login.jsx
+++ b/src/components/layout/auth/Login.jsx
@@ -1,6 +1,6 @@
/* eslint-disable react/no-array-index-key */
import React from 'react'
-import { withRouter } from 'react-router-dom'
+import { useLocation } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { Grid, Typography, useMediaQuery } from '@material-ui/core'
import { useTheme } from '@material-ui/styles'
@@ -11,12 +11,13 @@ import DiscordLogin from './Discord'
import TelegramLogin from './Telegram'
import CustomTile from '../custom/CustomTile'
-const Login = ({ clickedTwice, location, serverSettings, getServerSettings }) => {
+export default function Login({ clickedTwice, serverSettings, getServerSettings }) {
+ const location = useLocation()
const { t } = useTranslation()
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.only('xs'))
-
const { settings, components } = serverSettings.config.map.loginPage
+
return components.length ? (
)
}
-
-export default withRouter(Login)
diff --git a/src/components/layout/dialogs/ResetFilters.jsx b/src/components/layout/dialogs/ResetFilters.jsx
index 3535236f1..e51207bd0 100644
--- a/src/components/layout/dialogs/ResetFilters.jsx
+++ b/src/components/layout/dialogs/ResetFilters.jsx
@@ -2,7 +2,7 @@ import React, { useState } from 'react'
import {
Button, Typography, DialogContent,
} from '@material-ui/core'
-import { Redirect } from 'react-router-dom'
+import { Navigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useStatic } from '@hooks/useStore'
@@ -16,7 +16,7 @@ export default function ResetFilters() {
const setResetFilters = useStatic(state => state.setResetFilters)
if (redirect) {
- return
+ return
}
return (
<>
diff --git a/src/components/layout/dialogs/UserProfile.jsx b/src/components/layout/dialogs/UserProfile.jsx
index 809ba41c5..ed2473021 100644
--- a/src/components/layout/dialogs/UserProfile.jsx
+++ b/src/components/layout/dialogs/UserProfile.jsx
@@ -337,7 +337,7 @@ const BadgeTile = ({ data, rowIndex, columnIndex, style }) => {
{item.name || t('unknown_gym')}
-