diff --git a/.env.local.template b/.env.local.template index 15bc394e..1c94baab 100644 --- a/.env.local.template +++ b/.env.local.template @@ -1,7 +1,7 @@ # REQUIRED | Port to listen on. PORT=3000 -# REQUIRED | Publicly or locally accessible URL. Leave as localhost if in development. +# REQUIRED | Publicly or locally accessible URL. Leave untouched if in development. APP_URL=http://localhost:$PORT # API URL which the SERVER contacts. Client-side never contacts API directly. @@ -31,5 +31,11 @@ FR_STRIPE_API_SK=pk_XXXX_XXXXXXXXXXXXXXXXXXXXXXXX # Donation API IP bans file. Leave undefined unless testing # FR_STRIPE_BANS_FILE=/path/to/file.json +# API URL for the Queue Management System +QMS_API_URL=https://api.qms.fuelrats.dev + +# API Token for the Queue Management System +QMS_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + # Editor command to use when opening files from dev overlay. This is an optional dev variable. REACT_EDITOR=code diff --git a/.eslintrc.js b/.eslintrc.js index fc1cd224..08fbd934 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -36,4 +36,13 @@ module.exports = { }, }, }, + overrides: [ + { + files: ['src/pages/api/**/*.js'], + env: { + browser: false, + node: true, + }, + }, + ], } diff --git a/.vscode/launch.json b/.vscode/launch.json index 549eddf5..a19d2947 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,31 +5,24 @@ "version": "0.2.0", "configurations": [ { - "name": "Next: Server", + "name": "Next: Attach Server", "type": "node", - "request": "launch", - "envFile": "${workspaceFolder}/.env", - "program": "${workspaceFolder}/server/server.js", - "protocol": "inspector", - "console": "integratedTerminal", + "request": "attach", + "port": 9229, }, { - "name": "Next: Server (Attach)", - "type": "node", - "request": "attach" - }, - { - "name": "Next: Chrome", + "name": "Next: Attach Chrome", "type": "chrome", - "request": "launch", - "url": "${config:debug.developurl}", + "request": "attach", + "port": 9222, + "url": "${config:develop.attachurl}", "webRoot": "${workspaceFolder}" } ], "compounds": [ { - "name": "Next: Full", - "configurations": ["Next: Chrome", "Next: Server"] + "name": "Next: Attach", + "configurations": ["Next: Attach Server", "Next: Attach Chrome"] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 35456f1c..9f11f470 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -61,4 +61,5 @@ "stylelint.packageManager": "yarn", "stylelint.reportNeedlessDisables": true, "cSpell.language": "en,en-US", + "develop.attachurl": "http://localhost:3000/" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d1f9aa6b..faed5c88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ For detailed rules of this file, see [Changelog Rules](#changelog-rules) ### ✨ Added * Added controls for rats to opt-in to Odyssey rescues. - [#320][] * Added `odyssey` field to registration page. - [#320][] +* Added basic QMS queue counter to Dispatch Board. - [#323][] ### ⚡ Changed @@ -52,6 +53,7 @@ For detailed rules of this file, see [Changelog Rules](#changelog-rules) [#316]: https://github.com/fuelRats/fuelrats.com/pull/316 [#317]: https://github.com/fuelRats/fuelrats.com/pull/317 [#320]: https://github.com/fuelRats/fuelrats.com/pull/320 +[#323]: https://github.com/fuelRats/fuelrats.com/pull/323 [2.13.0]: https://github.com/FuelRats/fuelrats.com/compare/v2.12.10...v2.13.0 diff --git a/src/components/DispatchTable/DispatchTable.js b/src/components/DispatchTable/DispatchTable.js index 9ad29747..0dd9f1f7 100644 --- a/src/components/DispatchTable/DispatchTable.js +++ b/src/components/DispatchTable/DispatchTable.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types' import { useSelector } from 'react-redux' +import { useRescueQueueCount } from '~/hooks/rescueHooks' import { selectDispatchBoard } from '~/store/selectors/dispatch' import styles from './DispatchTable.module.scss' @@ -16,6 +17,7 @@ function DispatchTable (props) { } = props const rescueIds = useSelector(selectDispatchBoard) + const queueLength = useRescueQueueCount() return (
@@ -43,6 +45,15 @@ function DispatchTable (props) { } + { + queueLength > 0 && ( +
+ {'+'} + {queueLength} + {' IN QUEUE'} +
+ ) + }
) } diff --git a/src/components/DispatchTable/DispatchTable.module.scss b/src/components/DispatchTable/DispatchTable.module.scss index eab9d78c..ce80cac7 100644 --- a/src/components/DispatchTable/DispatchTable.module.scss +++ b/src/components/DispatchTable/DispatchTable.module.scss @@ -72,3 +72,14 @@ background-color: rgba(240, 230, 140, 0.25); opacity: 0.25; } + + +.queueLength { + float: right; + + padding: 2px 8px; + + border: 1px solid var(--table-border-color); + + background: $black; +} diff --git a/src/components/Switch/Switch.js b/src/components/Switch/Switch.js index f9fef188..d97827ed 100644 --- a/src/components/Switch/Switch.js +++ b/src/components/Switch/Switch.js @@ -16,22 +16,22 @@ function Switch (props) { disabled, id, label, - onClick, + onChange, ...inputProps } = props const [loading, setLoading] = useState(false) - const handleClick = useCallback(async (event) => { + const handleChange = useCallback(async (event) => { if (!isAsync) { - onClick?.(event) + onChange?.(event) return } setLoading(true) - await onClick?.(event) + await onChange?.(event) setLoading(false) - }, [isAsync, onClick]) + }, [isAsync, onChange]) let icon = 'times' if (loading) { @@ -52,7 +52,7 @@ function Switch (props) { disabled={loading || disabled} id={id} type="checkbox" - onChange={handleClick} /> + onChange={handleChange} /> { + const methodHandler = handlers[req.method] + + if (typeof methodHandler === 'function') { + await methodHandler(req, res) + return + } + + res.setHeader('Allow', Object.keys(handlers)) + res.status(HttpStatus.METHOD_NOT_ALLOWED).end(`Method ${req.method} Not Allowed`) + } +} diff --git a/src/hooks/rescueHooks.js b/src/hooks/rescueHooks.js index 624e58bc..1cbd069e 100644 --- a/src/hooks/rescueHooks.js +++ b/src/hooks/rescueHooks.js @@ -1,4 +1,5 @@ -import { useMemo } from 'react' +import axios from 'axios' +import { useMemo, useState, useEffect } from 'react' import { getLanguage } from '~/data/LanguageList' import { getPlatform } from '~/data/PlatformList' @@ -27,3 +28,34 @@ export const usePlatformData = (rescue) => { return getPlatform(rescue.attributes.platform) }, [rescue.attributes.platform]) } + + +export const useRescueQueueCount = () => { + const [rescueCount, setCount] = useState(0) + + useEffect( + () => { + let timeout = null + + const fetchData = async () => { + const { data } = await axios.get('/api/qms/queue') + + setCount(data.data.queueLength) + + timeout = setTimeout(fetchData, data.meta.maxAge) + } + + fetchData() + + return () => { + if (timeout) { + clearTimeout(timeout) + } + } + }, + [], + ) + + + return rescueCount +} diff --git a/src/pages/api/qms/queue.js b/src/pages/api/qms/queue.js new file mode 100644 index 00000000..1fbdfb6b --- /dev/null +++ b/src/pages/api/qms/queue.js @@ -0,0 +1,54 @@ +import { HttpStatus } from '@fuelrats/web-util/http' +import axios from 'axios' + +import { methodRouter } from '~/helpers/apiMiddlewares' + + +// 10 second maxAge +const cacheMaxAge = 10000 + +const cache = { + lastCheck: 0, + count: 0, +} + + +async function Queue (req, res) { + const nowTime = Date.now() + + if (nowTime - cache.lastCheck >= cacheMaxAge) { + cache.lastCheck = nowTime + + const result = await axios.get( + `${process.env.QMS_API_URL}/api/v1/queue`, + { + headers: { + Authorization: `Bearer ${process.env.QMS_API_TOKEN}`, + }, + }, + ) + + const rescueQueue = result.data.filter((rescue) => { + return !rescue.pending && !rescue.in_progress + }) + + cache.count = rescueQueue.length + } + + + return res.status(HttpStatus.OK).json({ + data: { + queueLength: cache.count, + }, + meta: { + age: nowTime - cache.lastCheck, + maxAge: cacheMaxAge, + }, + }) +} + + + +export default methodRouter({ + GET: Queue, +})