diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 0000000..b0c1c68 --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 4e9edf9..f1a6fe2 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -16,7 +16,6 @@ import { NotificationProvider } from "./util/notificationContext"; const App: FC = () => { const [user, setUser] = useState(false); - useEffect(() => { // Only set up the listener once when component mounts if (auth.currentUser) { @@ -66,7 +65,7 @@ const App: FC = () => { ) : ( diff --git a/src/renderer/src/components/ErrorBoundaryFallback/ErrorBoundaryFallback.tsx b/src/renderer/src/components/ErrorBoundaryFallback/ErrorBoundaryFallback.tsx index 4cdc314..010e452 100644 --- a/src/renderer/src/components/ErrorBoundaryFallback/ErrorBoundaryFallback.tsx +++ b/src/renderer/src/components/ErrorBoundaryFallback/ErrorBoundaryFallback.tsx @@ -1,10 +1,9 @@ -/* eslint-disable react/prop-types */ -//TODO: remove eslint-disable +import { FC } from "react"; import { Button, Result } from "antd"; import { FallbackProps } from "react-error-boundary"; import { useTranslation } from "react-i18next"; -const ErrorBoundaryFallback: React.FC = ({ +const ErrorBoundaryFallback: FC = ({ error, resetErrorBoundary, }) => { diff --git a/src/renderer/src/components/Home/Home.tsx b/src/renderer/src/components/Home/Home.tsx index f02fa22..cb159a2 100644 --- a/src/renderer/src/components/Home/Home.tsx +++ b/src/renderer/src/components/Home/Home.tsx @@ -1,4 +1,6 @@ -const Home: React.FC = () => { +import { FC } from "react"; + +const Home: FC = () => { return (

Home

diff --git a/src/renderer/src/components/Settings/Settings.EmsOutFilePath.tsx b/src/renderer/src/components/Settings/Settings.EmsOutFilePath.tsx index aff8ee6..a84e0a5 100644 --- a/src/renderer/src/components/Settings/Settings.EmsOutFilePath.tsx +++ b/src/renderer/src/components/Settings/Settings.EmsOutFilePath.tsx @@ -1,10 +1,10 @@ import { FolderOpenFilled } from "@ant-design/icons"; import { Button, Card, Input, Space } from "antd"; -import { useEffect, useState } from "react"; +import { useEffect, useState, FC } from "react"; import { useTranslation } from "react-i18next"; import ipcTypes from "../../../../util/ipcTypes.json"; -const SettingsEmsOutFilePath: React.FC = () => { +const SettingsEmsOutFilePath: FC = () => { const { t } = useTranslation(); const [emsFilePath, setEmsFilePath] = useState(null); diff --git a/src/renderer/src/components/Settings/Settings.PpcFilePath.tsx b/src/renderer/src/components/Settings/Settings.PpcFilePath.tsx index 43318a7..82ff80c 100644 --- a/src/renderer/src/components/Settings/Settings.PpcFilePath.tsx +++ b/src/renderer/src/components/Settings/Settings.PpcFilePath.tsx @@ -1,10 +1,10 @@ import { FolderOpenFilled } from "@ant-design/icons"; import { Button, Card, Input, Space } from "antd"; -import { useEffect, useState } from "react"; +import { useEffect, useState, FC } from "react"; import { useTranslation } from "react-i18next"; import ipcTypes from "../../../../util/ipcTypes.json"; -const SettingsPpcFilepath: React.FC = () => { +const SettingsPpcFilepath: FC = () => { const { t } = useTranslation(); const [ppcFilePath, setPpcFilePath] = useState(null); diff --git a/src/renderer/src/components/Settings/Settings.WatchedPaths.tsx b/src/renderer/src/components/Settings/Settings.WatchedPaths.tsx index dfe0999..81026f4 100644 --- a/src/renderer/src/components/Settings/Settings.WatchedPaths.tsx +++ b/src/renderer/src/components/Settings/Settings.WatchedPaths.tsx @@ -1,10 +1,10 @@ import { DeleteFilled, FileAddFilled } from "@ant-design/icons"; import { Button, Card, Space, Timeline } from "antd"; -import { useEffect, useState } from "react"; +import { useEffect, useState, FC } from "react"; import { useTranslation } from "react-i18next"; import ipcTypes from "../../../../util/ipcTypes.json"; -const SettingsWatchedPaths: React.FC = () => { +const SettingsWatchedPaths: FC = () => { const [watchedPaths, setWatchedPaths] = useState([]); const { t } = useTranslation(); diff --git a/src/renderer/src/components/Settings/Settings.Watcher.tsx b/src/renderer/src/components/Settings/Settings.Watcher.tsx index 136b561..6215ec6 100644 --- a/src/renderer/src/components/Settings/Settings.Watcher.tsx +++ b/src/renderer/src/components/Settings/Settings.Watcher.tsx @@ -1,14 +1,14 @@ +import { useAppSelector } from "@renderer/redux/reduxHooks"; import { CheckCircleFilled, ExclamationCircleFilled, - PlayCircleOutlined, PauseCircleOutlined, + PlayCircleOutlined, } from "@ant-design/icons"; import { selectWatcherError, selectWatcherStatus, } from "@renderer/redux/app.slice"; -import { useAppSelector } from "@renderer/redux/reduxHooks"; import { Alert, Badge, @@ -20,7 +20,7 @@ import { Space, Switch, } from "antd"; -import { useEffect, useState } from "react"; +import { FC, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import ipcTypes from "../../../../util/ipcTypes.json"; @@ -29,7 +29,7 @@ const colSpans = { sm: 24, }; -const SettingsWatcher: React.FC = () => { +const SettingsWatcher: FC = () => { const { t } = useTranslation(); const isWatcherStarted = useAppSelector(selectWatcherStatus); const watcherError = useAppSelector(selectWatcherError); diff --git a/src/renderer/src/components/Settings/Settings.tsx b/src/renderer/src/components/Settings/Settings.tsx index b0eb60a..0e71702 100644 --- a/src/renderer/src/components/Settings/Settings.tsx +++ b/src/renderer/src/components/Settings/Settings.tsx @@ -1,4 +1,5 @@ import { Col, Row } from "antd"; +import { FC } from "react"; import SettingsWatchedPaths from "./Settings.WatchedPaths"; import SettingsWatcher from "./Settings.Watcher"; import Welcome from "../Welcome/Welcome"; @@ -9,7 +10,7 @@ const colSpans = { md: 12, sm: 24, }; -const Settings: React.FC = () => { +const Settings: FC = () => { return ( diff --git a/src/renderer/src/components/SignInForm/SignInForm.tsx b/src/renderer/src/components/SignInForm/SignInForm.tsx index 83dff59..360cc23 100644 --- a/src/renderer/src/components/SignInForm/SignInForm.tsx +++ b/src/renderer/src/components/SignInForm/SignInForm.tsx @@ -1,6 +1,6 @@ import { auth } from "@renderer/util/firebase"; import type { FormProps } from "antd"; -import { Alert, Button, Form, Input, Space, Card, Typography } from "antd"; +import { Alert, Button, Card, Form, Input, Typography } from "antd"; import log from "electron-log/renderer"; import { signInWithEmailAndPassword } from "firebase/auth"; import { useState } from "react"; diff --git a/src/renderer/src/components/UpdateAvailable/UpdateAvailable.tsx b/src/renderer/src/components/UpdateAvailable/UpdateAvailable.tsx index b808854..a43c61b 100644 --- a/src/renderer/src/components/UpdateAvailable/UpdateAvailable.tsx +++ b/src/renderer/src/components/UpdateAvailable/UpdateAvailable.tsx @@ -8,9 +8,9 @@ import { useAppSelector } from "@renderer/redux/reduxHooks"; import { Affix, Button, Card, Progress, Space, Statistic } from "antd"; import { useTranslation } from "react-i18next"; import ipcTypes from "../../../../util/ipcTypes.json"; -import { useState } from "react"; +import { useState, FC } from "react"; -const UpdateAvailable: React.FC = () => { +const UpdateAvailable: FC = () => { const { t } = useTranslation(); const isUpdateAvailable = useAppSelector(selectUpdateAvailable); diff --git a/src/renderer/src/components/Versions.tsx b/src/renderer/src/components/Versions.tsx index dc71bbf..ddd7349 100644 --- a/src/renderer/src/components/Versions.tsx +++ b/src/renderer/src/components/Versions.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { JSX, useState } from "react"; function Versions(): JSX.Element { const [versions] = useState(window.electron.process.versions); diff --git a/src/renderer/src/components/Welcome/Welcome.tsx b/src/renderer/src/components/Welcome/Welcome.tsx index a36ed9d..d3d3c87 100644 --- a/src/renderer/src/components/Welcome/Welcome.tsx +++ b/src/renderer/src/components/Welcome/Welcome.tsx @@ -1,10 +1,11 @@ import { LogoutOutlined } from "@ant-design/icons"; import { auth } from "@renderer/util/firebase"; import { Button, Space, Typography } from "antd"; -import _ from "lodash"; +import { isEmpty } from "lodash"; import { JSX, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import ipcTypes from "../../../../util/ipcTypes.json"; + const Welcome = (): JSX.Element => { const { t } = useTranslation(); const [shopName, setShopName] = useState(null); @@ -21,7 +22,7 @@ const Welcome = (): JSX.Element => { <> {t("auth.labels.welcome", { - name: _.isEmpty(auth.currentUser?.displayName) + name: isEmpty(auth.currentUser?.displayName) ? auth.currentUser?.email : `${auth.currentUser?.displayName} (${auth.currentUser?.email})`.trim(), })} @@ -33,7 +34,9 @@ const Welcome = (): JSX.Element => { danger icon={} onClick={(): void => { - auth.signOut(); + auth.signOut().catch((error) => { + console.error("Sign out error:", error); + }); }} > {t("navigation.signout")} diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index a9c033b..ef1b4a0 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -5,6 +5,16 @@ import "./util/i18n"; import "./util/ipcRendererHandler"; import * as Sentry from "@sentry/electron/renderer"; +// Extend the Window interface to include the api property +declare global { + interface Window { + api: { + isTest: () => boolean; + }; + } +} + + Sentry.init({ dsn: "https://ba41d22656999a8c1fd63bcb7df98650@o492140.ingest.us.sentry.io/4509074139447296", }); diff --git a/src/renderer/src/redux/app.slice.ts b/src/renderer/src/redux/app.slice.ts index 09dc261..806c8e8 100644 --- a/src/renderer/src/redux/app.slice.ts +++ b/src/renderer/src/redux/app.slice.ts @@ -1,6 +1,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import log from "electron-log/renderer"; import type { RootState } from "./redux-store"; + interface AppState { value: number; watcher: { diff --git a/src/renderer/src/redux/redux-store.ts b/src/renderer/src/redux/redux-store.ts index 84a9707..a798de3 100644 --- a/src/renderer/src/redux/redux-store.ts +++ b/src/renderer/src/redux/redux-store.ts @@ -9,6 +9,7 @@ const store = configureStore({ // Infer the `RootState` and `AppDispatch` types from the store itself export type RootState = ReturnType; + // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} export type AppDispatch = typeof store.dispatch; export type AppStore = typeof store; diff --git a/src/renderer/src/util/countdownHook.ts b/src/renderer/src/util/countdownHook.ts index 5b2acd9..61b2d28 100644 --- a/src/renderer/src/util/countdownHook.ts +++ b/src/renderer/src/util/countdownHook.ts @@ -1,4 +1,12 @@ -import React from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; + +type Timer = { + started: number | null; + lastInterval: number | null; + timeLeft: number; + timeToCount: number; + requestId: number; +}; const useCountDown = ( timeToCount = 60 * 1000, @@ -12,10 +20,16 @@ const useCountDown = ( reset: () => void; }, ] => { - const [timeLeft, setTimeLeft] = React.useState(0); - const timer = React.useRef({}); + const [timeLeft, setTimeLeft] = useState(0); + const timer = useRef({ + started: null, + lastInterval: null, + timeLeft: 0, + timeToCount: 0, + requestId: 0, + }); - const run = (ts) => { + const run = (ts: number) => { if (!timer.current.started) { timer.current.started = ts; timer.current.lastInterval = ts; @@ -25,7 +39,7 @@ const useCountDown = ( interval, timer.current.timeLeft || Infinity, ); - if (ts - timer.current.lastInterval >= localInterval) { + if (timer.current.lastInterval && ts - timer.current.lastInterval >= localInterval) { timer.current.lastInterval += localInterval; setTimeLeft((timeLeft) => { timer.current.timeLeft = timeLeft - localInterval; @@ -36,12 +50,18 @@ const useCountDown = ( if (ts - timer.current.started < timer.current.timeToCount) { timer.current.requestId = window.requestAnimationFrame(run); } else { - timer.current = {}; + timer.current = { + started: null, + lastInterval: null, + timeLeft: 0, + timeToCount: 0, + requestId: 0, + }; setTimeLeft(0); } }; - const start = React.useCallback( + const start = useCallback( (ttc) => { window.cancelAnimationFrame(timer.current.requestId); @@ -57,14 +77,14 @@ const useCountDown = ( [], ); - const pause = React.useCallback(() => { + const pause = useCallback(() => { window.cancelAnimationFrame(timer.current.requestId); timer.current.started = null; timer.current.lastInterval = null; timer.current.timeToCount = timer.current.timeLeft; }, []); - const resume = React.useCallback( + const resume = useCallback( () => { if (!timer.current.started && timer.current.timeLeft > 0) { window.cancelAnimationFrame(timer.current.requestId); @@ -75,20 +95,26 @@ const useCountDown = ( [], ); - const reset = React.useCallback(() => { + const reset = useCallback(() => { if (timer.current.timeLeft) { window.cancelAnimationFrame(timer.current.requestId); - timer.current = {}; + timer.current = { + started: null, + lastInterval: null, + timeLeft: 0, + timeToCount: 0, + requestId: 0, + }; setTimeLeft(0); } }, []); - const actions = React.useMemo( + const actions = useMemo( () => ({ start, pause, resume, reset }), // eslint-disable-next-line react-hooks/exhaustive-deps [], ); - React.useEffect(() => { + useEffect(() => { return () => window.cancelAnimationFrame(timer.current.requestId); }, []); diff --git a/src/renderer/src/util/graphql.client.ts b/src/renderer/src/util/graphql.client.ts index 5183a9f..5ddac51 100644 --- a/src/renderer/src/util/graphql.client.ts +++ b/src/renderer/src/util/graphql.client.ts @@ -10,6 +10,7 @@ const httpLink: HttpLink = new HttpLink({ }); const middlewares = []; + const client: ApolloClient = new ApolloClient({ link: ApolloLink.from(middlewares), cache: new InMemoryCache(), diff --git a/src/renderer/src/util/i18n.ts b/src/renderer/src/util/i18n.ts index 414aa6c..0df882e 100644 --- a/src/renderer/src/util/i18n.ts +++ b/src/renderer/src/util/i18n.ts @@ -6,13 +6,19 @@ const resources = { en: enTranslations, }; -i18n.use(initReactI18next).init({ - resources, - debug: import.meta.env.DEV, - lng: "en", - interpolation: { - escapeValue: false, - }, -}); +i18n + .use(initReactI18next) + .init({ + resources, + debug: import.meta.env.DEV, + lng: "en", + interpolation: { + escapeValue: false, + }, + }) + .catch((err) => { + console.error("i18n initialization error:", err); + throw err; + }); export default i18n; diff --git a/src/renderer/src/util/ipcRendererHandler.ts b/src/renderer/src/util/ipcRendererHandler.ts index c0bc704..8182169 100644 --- a/src/renderer/src/util/ipcRendererHandler.ts +++ b/src/renderer/src/util/ipcRendererHandler.ts @@ -1,4 +1,4 @@ -//Set up all of the IPC handlers. +//Set up all the IPC handlers. import { setWatcherPolling, updateAvailable, @@ -40,6 +40,7 @@ ipcRenderer.on(ipcTypes.toRenderer.watcher.stopped, () => { console.log("Watcher has stopped"); dispatch(watcherStopped()); }); + ipcRenderer.on( ipcTypes.toRenderer.watcher.error, (_event: Electron.IpcRendererEvent, error: string) => { @@ -58,6 +59,7 @@ ipcRenderer.on(ipcTypes.toRenderer.updates.checking, () => { ipcRenderer.on(ipcTypes.toRenderer.updates.available, () => { dispatch(updateAvailable()); }); + ipcRenderer.on( ipcTypes.toRenderer.updates.downloading, (_event: Electron.IpcRendererEvent, arg) => { @@ -70,6 +72,7 @@ ipcRenderer.on( ); }, ); + ipcRenderer.on(ipcTypes.toRenderer.updates.downloaded, () => { dispatch(updateDownloaded()); }); diff --git a/src/renderer/src/util/notificationContext.tsx b/src/renderer/src/util/notificationContext.tsx index 8f4450f..efec48a 100644 --- a/src/renderer/src/util/notificationContext.tsx +++ b/src/renderer/src/util/notificationContext.tsx @@ -1,5 +1,4 @@ -// eslint-disable-all -import { createContext, useContext } from "react"; +import {createContext, FC, ReactNode, useContext} from "react"; import { notification } from "antd"; /** @@ -23,10 +22,10 @@ export const useNotification = () => { * - Provide `api` via the NotificationContext. */ interface NotificationProviderProps { - children?: React.ReactNode | React.ReactNode[]; + children?: ReactNode | ReactNode[]; } -export const NotificationProvider: React.FC = ({ +export const NotificationProvider: FC = ({ // eslint-disable-next-line react/prop-types children, //TODO: Unable to resolve this. Adding an eslint disable. }) => { @@ -37,6 +36,7 @@ export const NotificationProvider: React.FC = ({ }); return ( + // @ts-ignore {/* contextHolder must be rendered in the DOM so notifications can appear */} {contextHolder} diff --git a/src/util/deepLowercaseKeys.ts b/src/util/deepLowercaseKeys.ts index 9bf9d47..052ee8c 100644 --- a/src/util/deepLowercaseKeys.ts +++ b/src/util/deepLowercaseKeys.ts @@ -33,4 +33,5 @@ function deepLowerCaseKeys(obj: any): T { {} as Record, ) as T; } + export default deepLowerCaseKeys; diff --git a/src/util/errorTypeCheck.ts b/src/util/errorTypeCheck.ts index 8ed8853..149b348 100644 --- a/src/util/errorTypeCheck.ts +++ b/src/util/errorTypeCheck.ts @@ -12,6 +12,7 @@ function errorTypeCheck(passedError: Error | unknown): ParsedError { stack: errorStack, }; } + export default errorTypeCheck; export interface ParsedError {