From e9ede280982dbe0f20c16aa832e0aa99234ebbab Mon Sep 17 00:00:00 2001 From: Altair-sh Date: Sun, 13 Sep 2026 23:50:59 +0200 Subject: [PATCH] fixed warnings from eslint --- src/app/api/proxy/functions.ts | 20 +++--- .../saveParserBackend/[[...path]]/route.ts | 5 +- src/app/browse/page.tsx | 63 +++++++++++-------- src/app/login/page.tsx | 8 ++- src/app/register/page.tsx | 8 ++- src/components/SaveFileUploadingDialog.tsx | 17 ++--- src/instrumentation.ts | 7 ++- src/lib/customConsoleLog.ts | 2 +- src/lib/errors.ts | 7 +++ src/services/SaveParserBackendService.ts | 2 +- src/types/fetch.d.ts | 6 ++ 11 files changed, 91 insertions(+), 54 deletions(-) create mode 100644 src/lib/errors.ts create mode 100644 src/types/fetch.d.ts diff --git a/src/app/api/proxy/functions.ts b/src/app/api/proxy/functions.ts index f5b316a..499fa37 100644 --- a/src/app/api/proxy/functions.ts +++ b/src/app/api/proxy/functions.ts @@ -1,8 +1,10 @@ import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' -/// Resend request to backend. -/// Body is being sent as stream. +/** + * Resend request to backend. + * Body is being sent as stream. + */ export async function redirectRequest( req: NextRequest, proxyUrl: string, @@ -26,8 +28,7 @@ export async function redirectRequest( method: req.method, headers: req.headers, body: req.body, - // @ts-ignore - duplex: 'half', // some property required by nodejs but not defined in @types/node + duplex: 'half', // required by nodejs when the body is a stream, see src/types/fetch.d.ts }) return new NextResponse(backendRes.body, { @@ -41,9 +42,14 @@ export async function redirectRequest( }) } -/// Browsers send OPTIONS request sometimes to check webserver's allowed headers and methods. -/// My C# backend doesn't support OPTIONS, so the proxy answer to OPTIONS request that all headers and methods are allowed. -export async function responseToOPTIONS( +/** + * Browsers send OPTIONS request sometimes to check webserver's allowed + * headers and methods. + * My C# backend doesn't support OPTIONS, so the proxy answer to OPTIONS + * request that all headers and methods are allowed. + */ +export async function respondToOPTIONS( + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- required by the nextjs route handler signature req: NextRequest ): Promise { // console.log('proxy responding to CORS OPTIONS', req.url) diff --git a/src/app/api/proxy/saveParserBackend/[[...path]]/route.ts b/src/app/api/proxy/saveParserBackend/[[...path]]/route.ts index 756cf71..4963a75 100644 --- a/src/app/api/proxy/saveParserBackend/[[...path]]/route.ts +++ b/src/app/api/proxy/saveParserBackend/[[...path]]/route.ts @@ -1,9 +1,6 @@ import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' -import { - redirectRequest, - responseToOPTIONS as respondToOPTIONS, -} from '@/app/api/proxy/functions' +import { redirectRequest, respondToOPTIONS } from '@/app/api/proxy/functions' import { PROXY_PATHS } from '@/app/api/proxy/paths' import { appConfig } from '@/lib/configLoader' diff --git a/src/app/browse/page.tsx b/src/app/browse/page.tsx index 16aeed0..74c9d2d 100644 --- a/src/app/browse/page.tsx +++ b/src/app/browse/page.tsx @@ -6,38 +6,47 @@ import { themeQuartz, colorSchemeDark, } from 'ag-grid-community' -import { useState, useEffect } from 'react' +import { useSyncExternalStore } from 'react' + +const DARK_SCHEME_QUERY = '(prefers-color-scheme: dark)' + +// add the vertical lines between cells +const BASE_THEME = themeQuartz.withParams({ + columnBorder: true, + headerColumnBorder: true, +}) +// Built once, so the grid keeps seeing the same theme object between renders. +const LIGHT_THEME = BASE_THEME +const DARK_THEME = BASE_THEME.withPart(colorSchemeDark) + +// Notifies React whenever the browser's light/dark color scheme changes. +function subscribeToColorScheme(onChange: () => void) { + const mediaQuery = window.matchMedia(DARK_SCHEME_QUERY) + mediaQuery.addEventListener('change', onChange) + return () => mediaQuery.removeEventListener('change', onChange) +} + +function isDarkSchemePreferred() { + return window.matchMedia(DARK_SCHEME_QUERY).matches +} + +// The server has no color scheme, so it always prerenders the light theme. +function isDarkSchemePreferredOnServer() { + return false +} // TODO: create tabs with general info, tables, plots // Page that shows a save file's parsed data in a grid. export default function BrowseSaveDataPage() { - const [agTheme, setAgTheme] = useState(themeQuartz) - - // Keep the grid theme in sync with the browser's light/dark color scheme. - useEffect(() => { - const prefersDark = window.matchMedia( - '(prefers-color-scheme: dark)' - ).matches - setAgTheme( - prefersDark ? themeQuartz.withPart(colorSchemeDark) : themeQuartz - ) - - const listener = (e: MediaQueryListEvent) => { - setAgTheme( - e.matches ? themeQuartz.withPart(colorSchemeDark) : themeQuartz - ) - } - - window - .matchMedia('(prefers-color-scheme: dark)') - .addEventListener('change', listener) - return () => { - window - .matchMedia('(prefers-color-scheme: dark)') - .removeEventListener('change', listener) - } - }, []) + // Read the color scheme straight from the browser instead of mirroring it + // into state, which would need an extra render to catch up. + const prefersDark = useSyncExternalStore( + subscribeToColorScheme, + isDarkSchemePreferred, + isDarkSchemePreferredOnServer + ) + const agTheme = prefersDark ? DARK_THEME : LIGHT_THEME return (
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 49585d6..c2e6788 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { authService } from '@/services/AuthService' import FormError from '@/components/FormError' +import { getMsgFromError } from '@/lib/errors' // Page with the login form. export default function LoginPage() { @@ -35,9 +36,10 @@ export default function LoginPage() { formData.password ) console.log('Logged in:', result) - } catch (err: any) { - console.error(err.message) - setError(err.message) + } catch (err) { + const errMsg = getMsgFromError(err) + console.error(errMsg) + setError(errMsg) } } diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index fec1a16..66123dc 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -4,6 +4,7 @@ import { useState } from 'react' import { useRouter } from 'next/navigation' import { authService } from '@/services/AuthService' import FormError from '@/components/FormError' +import { getMsgFromError } from '@/lib/errors' // Page with the registration form. export default function RegisterPage() { @@ -43,9 +44,10 @@ export default function RegisterPage() { ) console.log('Registered:', result) router.push('/login') // Redirect after success - } catch (err: any) { - console.error(err.message) - setError(err.message) + } catch (err) { + const errMsg = getMsgFromError(err) + console.error(errMsg) + setError(errMsg) } } diff --git a/src/components/SaveFileUploadingDialog.tsx b/src/components/SaveFileUploadingDialog.tsx index fe63d23..1456027 100644 --- a/src/components/SaveFileUploadingDialog.tsx +++ b/src/components/SaveFileUploadingDialog.tsx @@ -7,6 +7,7 @@ import { } from '@/services/SaveParserBackendService' import { useState } from 'react' import FormError from './FormError' +import { getMsgFromError } from '@/lib/errors' export enum GameEnum { EU4 = 'eu4', @@ -71,16 +72,18 @@ export default function SaveFileUploadingDialog() { clearInterval(intervalId) setUploadDisabled(false) } - } catch (err: any) { - console.error('Error while polling status:', err) + } catch (err) { + const errMsg = getMsgFromError(err) + console.error('Error while polling status:', errMsg) clearInterval(intervalId) - setError(err.message) + setError(errMsg) setUploadDisabled(false) } }, pollInterval) - } catch (err: any) { - console.error(err.message) - setError(err.message) + } catch (err) { + const errMsg = getMsgFromError(err) + console.error(errMsg) + setError(errMsg) setUploadDisabled(false) } } @@ -145,7 +148,7 @@ export default function SaveFileUploadingDialog() {
)} - {/* add link to /browse?id=${saveStatus.id} */} + {/* TODO: add link to /browse?id=${saveStatus.id} */} ) diff --git a/src/instrumentation.ts b/src/instrumentation.ts index bdc2266..390e193 100644 --- a/src/instrumentation.ts +++ b/src/instrumentation.ts @@ -4,5 +4,10 @@ export async function register() { console.log('hello from instrumentation.ts!') await import('@/lib/customConsoleLog') // replaces console.log - await import('@/lib/configLoader') // forces loadConfig() to run at server startup + + // Importing configLoader outside of this block will produce warning. + // Next.js compiles this file for some "edge runtime", where no fs and path modules exist. + if (process.env.NEXT_RUNTIME === 'nodejs') { + await import('@/lib/configLoader') // forces loadConfig() to run at server startup + } } diff --git a/src/lib/customConsoleLog.ts b/src/lib/customConsoleLog.ts index 2c98619..d2e9462 100644 --- a/src/lib/customConsoleLog.ts +++ b/src/lib/customConsoleLog.ts @@ -2,7 +2,7 @@ function injectConsoleLog() { const originalLog = console.log - function customLog(message?: any, ...optionalParams: any[]) { + function customLog(message?: unknown, ...optionalParams: unknown[]) { const now = new Date() const timestamp = now.toTimeString().split(' ')[0] // hh:mm:ss originalLog(`[${timestamp}] ${message}`, ...optionalParams) diff --git a/src/lib/errors.ts b/src/lib/errors.ts new file mode 100644 index 0000000..1569736 --- /dev/null +++ b/src/lib/errors.ts @@ -0,0 +1,7 @@ +/** + * Extracts a readable message from an unknown thrown value, since anything + * (not just an Error) can be thrown and caught. + */ +export function getMsgFromError(err: unknown): string { + return err instanceof Error ? err.message : String(err) +} diff --git a/src/services/SaveParserBackendService.ts b/src/services/SaveParserBackendService.ts index 6ded113..2149da0 100644 --- a/src/services/SaveParserBackendService.ts +++ b/src/services/SaveParserBackendService.ts @@ -61,7 +61,7 @@ export class SaveParserBackendService { } // Fetches the fully parsed data for a save once its parsing is done. - async getParsedSave(id: string): Promise> { + async getParsedSave(id: string): Promise> { const url = `${this.API_BASE}/getParsedSave?id=${encodeURIComponent(id)}` const response = await fetch(url) diff --git a/src/types/fetch.d.ts b/src/types/fetch.d.ts new file mode 100644 index 0000000..78c19f5 --- /dev/null +++ b/src/types/fetch.d.ts @@ -0,0 +1,6 @@ +// Node's fetch needs `duplex: 'half'` for stream bodies, +// but lib.dom's RequestInit doesn't declare it. +// This interface is automatically merged with RequestInit from lib.dom.d.ts +interface RequestInit { + duplex?: 'half' +}