feature/IO-3066-1-scaffolding: Minor cleanup

This commit is contained in:
Dave Richer
2025-04-21 10:29:25 -04:00
parent b683d054ed
commit d4bde2db40
23 changed files with 110 additions and 50 deletions

6
.idea/prettier.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>

View File

@@ -16,7 +16,6 @@ import { NotificationProvider } from "./util/notificationContext";
const App: FC = () => { const App: FC = () => {
const [user, setUser] = useState<User | boolean | null>(false); const [user, setUser] = useState<User | boolean | null>(false);
useEffect(() => { useEffect(() => {
// Only set up the listener once when component mounts // Only set up the listener once when component mounts
if (auth.currentUser) { if (auth.currentUser) {
@@ -66,7 +65,7 @@ const App: FC = () => {
) : ( ) : (
<Badge.Ribbon <Badge.Ribbon
text={isTest && "Connected to Test"} text={isTest && "Connected to Test"}
color={isTest && "red"} color={isTest ? "red" : undefined}
> >
<Layout.Content style={{ padding: "0 24px" }}> <Layout.Content style={{ padding: "0 24px" }}>
<UpdateAvailable /> <UpdateAvailable />

View File

@@ -1,10 +1,9 @@
/* eslint-disable react/prop-types */ import { FC } from "react";
//TODO: remove eslint-disable
import { Button, Result } from "antd"; import { Button, Result } from "antd";
import { FallbackProps } from "react-error-boundary"; import { FallbackProps } from "react-error-boundary";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const ErrorBoundaryFallback: React.FC<FallbackProps> = ({ const ErrorBoundaryFallback: FC<FallbackProps> = ({
error, error,
resetErrorBoundary, resetErrorBoundary,
}) => { }) => {

View File

@@ -1,4 +1,6 @@
const Home: React.FC = () => { import { FC } from "react";
const Home: FC = () => {
return ( return (
<div> <div>
<h1>Home</h1> <h1>Home</h1>

View File

@@ -1,10 +1,10 @@
import { FolderOpenFilled } from "@ant-design/icons"; import { FolderOpenFilled } from "@ant-design/icons";
import { Button, Card, Input, Space } from "antd"; import { Button, Card, Input, Space } from "antd";
import { useEffect, useState } from "react"; import { useEffect, useState, FC } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json"; import ipcTypes from "../../../../util/ipcTypes.json";
const SettingsEmsOutFilePath: React.FC = () => { const SettingsEmsOutFilePath: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [emsFilePath, setEmsFilePath] = useState<string | null>(null); const [emsFilePath, setEmsFilePath] = useState<string | null>(null);

View File

@@ -1,10 +1,10 @@
import { FolderOpenFilled } from "@ant-design/icons"; import { FolderOpenFilled } from "@ant-design/icons";
import { Button, Card, Input, Space } from "antd"; import { Button, Card, Input, Space } from "antd";
import { useEffect, useState } from "react"; import { useEffect, useState, FC } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json"; import ipcTypes from "../../../../util/ipcTypes.json";
const SettingsPpcFilepath: React.FC = () => { const SettingsPpcFilepath: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [ppcFilePath, setPpcFilePath] = useState<string | null>(null); const [ppcFilePath, setPpcFilePath] = useState<string | null>(null);

View File

@@ -1,10 +1,10 @@
import { DeleteFilled, FileAddFilled } from "@ant-design/icons"; import { DeleteFilled, FileAddFilled } from "@ant-design/icons";
import { Button, Card, Space, Timeline } from "antd"; import { Button, Card, Space, Timeline } from "antd";
import { useEffect, useState } from "react"; import { useEffect, useState, FC } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json"; import ipcTypes from "../../../../util/ipcTypes.json";
const SettingsWatchedPaths: React.FC = () => { const SettingsWatchedPaths: FC = () => {
const [watchedPaths, setWatchedPaths] = useState<string[]>([]); const [watchedPaths, setWatchedPaths] = useState<string[]>([]);
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -1,14 +1,14 @@
import { useAppSelector } from "@renderer/redux/reduxHooks";
import { import {
CheckCircleFilled, CheckCircleFilled,
ExclamationCircleFilled, ExclamationCircleFilled,
PlayCircleOutlined,
PauseCircleOutlined, PauseCircleOutlined,
PlayCircleOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { import {
selectWatcherError, selectWatcherError,
selectWatcherStatus, selectWatcherStatus,
} from "@renderer/redux/app.slice"; } from "@renderer/redux/app.slice";
import { useAppSelector } from "@renderer/redux/reduxHooks";
import { import {
Alert, Alert,
Badge, Badge,
@@ -20,7 +20,7 @@ import {
Space, Space,
Switch, Switch,
} from "antd"; } from "antd";
import { useEffect, useState } from "react"; import { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json"; import ipcTypes from "../../../../util/ipcTypes.json";
@@ -29,7 +29,7 @@ const colSpans = {
sm: 24, sm: 24,
}; };
const SettingsWatcher: React.FC = () => { const SettingsWatcher: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const isWatcherStarted = useAppSelector(selectWatcherStatus); const isWatcherStarted = useAppSelector(selectWatcherStatus);
const watcherError = useAppSelector(selectWatcherError); const watcherError = useAppSelector(selectWatcherError);

View File

@@ -1,4 +1,5 @@
import { Col, Row } from "antd"; import { Col, Row } from "antd";
import { FC } from "react";
import SettingsWatchedPaths from "./Settings.WatchedPaths"; import SettingsWatchedPaths from "./Settings.WatchedPaths";
import SettingsWatcher from "./Settings.Watcher"; import SettingsWatcher from "./Settings.Watcher";
import Welcome from "../Welcome/Welcome"; import Welcome from "../Welcome/Welcome";
@@ -9,7 +10,7 @@ const colSpans = {
md: 12, md: 12,
sm: 24, sm: 24,
}; };
const Settings: React.FC = () => { const Settings: FC = () => {
return ( return (
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>

View File

@@ -1,6 +1,6 @@
import { auth } from "@renderer/util/firebase"; import { auth } from "@renderer/util/firebase";
import type { FormProps } from "antd"; 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 log from "electron-log/renderer";
import { signInWithEmailAndPassword } from "firebase/auth"; import { signInWithEmailAndPassword } from "firebase/auth";
import { useState } from "react"; import { useState } from "react";

View File

@@ -8,9 +8,9 @@ import { useAppSelector } from "@renderer/redux/reduxHooks";
import { Affix, Button, Card, Progress, Space, Statistic } from "antd"; import { Affix, Button, Card, Progress, Space, Statistic } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json"; 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 { t } = useTranslation();
const isUpdateAvailable = useAppSelector(selectUpdateAvailable); const isUpdateAvailable = useAppSelector(selectUpdateAvailable);

View File

@@ -1,4 +1,4 @@
import { useState } from "react"; import { JSX, useState } from "react";
function Versions(): JSX.Element { function Versions(): JSX.Element {
const [versions] = useState(window.electron.process.versions); const [versions] = useState(window.electron.process.versions);

View File

@@ -1,10 +1,11 @@
import { LogoutOutlined } from "@ant-design/icons"; import { LogoutOutlined } from "@ant-design/icons";
import { auth } from "@renderer/util/firebase"; import { auth } from "@renderer/util/firebase";
import { Button, Space, Typography } from "antd"; import { Button, Space, Typography } from "antd";
import _ from "lodash"; import { isEmpty } from "lodash";
import { JSX, useEffect, useState } from "react"; import { JSX, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json"; import ipcTypes from "../../../../util/ipcTypes.json";
const Welcome = (): JSX.Element => { const Welcome = (): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const [shopName, setShopName] = useState<string | null>(null); const [shopName, setShopName] = useState<string | null>(null);
@@ -21,7 +22,7 @@ const Welcome = (): JSX.Element => {
<> <>
<Typography.Title level={4}> <Typography.Title level={4}>
{t("auth.labels.welcome", { {t("auth.labels.welcome", {
name: _.isEmpty(auth.currentUser?.displayName) name: isEmpty(auth.currentUser?.displayName)
? auth.currentUser?.email ? auth.currentUser?.email
: `${auth.currentUser?.displayName} (${auth.currentUser?.email})`.trim(), : `${auth.currentUser?.displayName} (${auth.currentUser?.email})`.trim(),
})} })}
@@ -33,7 +34,9 @@ const Welcome = (): JSX.Element => {
danger danger
icon={<LogoutOutlined />} icon={<LogoutOutlined />}
onClick={(): void => { onClick={(): void => {
auth.signOut(); auth.signOut().catch((error) => {
console.error("Sign out error:", error);
});
}} }}
> >
{t("navigation.signout")} {t("navigation.signout")}

View File

@@ -5,6 +5,16 @@ import "./util/i18n";
import "./util/ipcRendererHandler"; import "./util/ipcRendererHandler";
import * as Sentry from "@sentry/electron/renderer"; 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({ Sentry.init({
dsn: "https://ba41d22656999a8c1fd63bcb7df98650@o492140.ingest.us.sentry.io/4509074139447296", dsn: "https://ba41d22656999a8c1fd63bcb7df98650@o492140.ingest.us.sentry.io/4509074139447296",
}); });

View File

@@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import log from "electron-log/renderer"; import log from "electron-log/renderer";
import type { RootState } from "./redux-store"; import type { RootState } from "./redux-store";
interface AppState { interface AppState {
value: number; value: number;
watcher: { watcher: {

View File

@@ -9,6 +9,7 @@ const store = configureStore({
// Infer the `RootState` and `AppDispatch` types from the store itself // Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>; export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch; export type AppDispatch = typeof store.dispatch;
export type AppStore = typeof store; export type AppStore = typeof store;

View File

@@ -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 = ( const useCountDown = (
timeToCount = 60 * 1000, timeToCount = 60 * 1000,
@@ -12,10 +20,16 @@ const useCountDown = (
reset: () => void; reset: () => void;
}, },
] => { ] => {
const [timeLeft, setTimeLeft] = React.useState(0); const [timeLeft, setTimeLeft] = useState(0);
const timer = React.useRef({}); const timer = useRef<Timer>({
started: null,
lastInterval: null,
timeLeft: 0,
timeToCount: 0,
requestId: 0,
});
const run = (ts) => { const run = (ts: number) => {
if (!timer.current.started) { if (!timer.current.started) {
timer.current.started = ts; timer.current.started = ts;
timer.current.lastInterval = ts; timer.current.lastInterval = ts;
@@ -25,7 +39,7 @@ const useCountDown = (
interval, interval,
timer.current.timeLeft || Infinity, timer.current.timeLeft || Infinity,
); );
if (ts - timer.current.lastInterval >= localInterval) { if (timer.current.lastInterval && ts - timer.current.lastInterval >= localInterval) {
timer.current.lastInterval += localInterval; timer.current.lastInterval += localInterval;
setTimeLeft((timeLeft) => { setTimeLeft((timeLeft) => {
timer.current.timeLeft = timeLeft - localInterval; timer.current.timeLeft = timeLeft - localInterval;
@@ -36,12 +50,18 @@ const useCountDown = (
if (ts - timer.current.started < timer.current.timeToCount) { if (ts - timer.current.started < timer.current.timeToCount) {
timer.current.requestId = window.requestAnimationFrame(run); timer.current.requestId = window.requestAnimationFrame(run);
} else { } else {
timer.current = {}; timer.current = {
started: null,
lastInterval: null,
timeLeft: 0,
timeToCount: 0,
requestId: 0,
};
setTimeLeft(0); setTimeLeft(0);
} }
}; };
const start = React.useCallback( const start = useCallback(
(ttc) => { (ttc) => {
window.cancelAnimationFrame(timer.current.requestId); window.cancelAnimationFrame(timer.current.requestId);
@@ -57,14 +77,14 @@ const useCountDown = (
[], [],
); );
const pause = React.useCallback(() => { const pause = useCallback(() => {
window.cancelAnimationFrame(timer.current.requestId); window.cancelAnimationFrame(timer.current.requestId);
timer.current.started = null; timer.current.started = null;
timer.current.lastInterval = null; timer.current.lastInterval = null;
timer.current.timeToCount = timer.current.timeLeft; timer.current.timeToCount = timer.current.timeLeft;
}, []); }, []);
const resume = React.useCallback( const resume = useCallback(
() => { () => {
if (!timer.current.started && timer.current.timeLeft > 0) { if (!timer.current.started && timer.current.timeLeft > 0) {
window.cancelAnimationFrame(timer.current.requestId); window.cancelAnimationFrame(timer.current.requestId);
@@ -75,20 +95,26 @@ const useCountDown = (
[], [],
); );
const reset = React.useCallback(() => { const reset = useCallback(() => {
if (timer.current.timeLeft) { if (timer.current.timeLeft) {
window.cancelAnimationFrame(timer.current.requestId); window.cancelAnimationFrame(timer.current.requestId);
timer.current = {}; timer.current = {
started: null,
lastInterval: null,
timeLeft: 0,
timeToCount: 0,
requestId: 0,
};
setTimeLeft(0); setTimeLeft(0);
} }
}, []); }, []);
const actions = React.useMemo( const actions = useMemo(
() => ({ start, pause, resume, reset }), // eslint-disable-next-line react-hooks/exhaustive-deps () => ({ start, pause, resume, reset }), // eslint-disable-next-line react-hooks/exhaustive-deps
[], [],
); );
React.useEffect(() => { useEffect(() => {
return () => window.cancelAnimationFrame(timer.current.requestId); return () => window.cancelAnimationFrame(timer.current.requestId);
}, []); }, []);

View File

@@ -10,6 +10,7 @@ const httpLink: HttpLink = new HttpLink({
}); });
const middlewares = []; const middlewares = [];
const client: ApolloClient<any> = new ApolloClient({ const client: ApolloClient<any> = new ApolloClient({
link: ApolloLink.from(middlewares), link: ApolloLink.from(middlewares),
cache: new InMemoryCache(), cache: new InMemoryCache(),

View File

@@ -6,13 +6,19 @@ const resources = {
en: enTranslations, en: enTranslations,
}; };
i18n.use(initReactI18next).init({ i18n
.use(initReactI18next)
.init({
resources, resources,
debug: import.meta.env.DEV, debug: import.meta.env.DEV,
lng: "en", lng: "en",
interpolation: { interpolation: {
escapeValue: false, escapeValue: false,
}, },
}); })
.catch((err) => {
console.error("i18n initialization error:", err);
throw err;
});
export default i18n; export default i18n;

View File

@@ -1,4 +1,4 @@
//Set up all of the IPC handlers. //Set up all the IPC handlers.
import { import {
setWatcherPolling, setWatcherPolling,
updateAvailable, updateAvailable,
@@ -40,6 +40,7 @@ ipcRenderer.on(ipcTypes.toRenderer.watcher.stopped, () => {
console.log("Watcher has stopped"); console.log("Watcher has stopped");
dispatch(watcherStopped()); dispatch(watcherStopped());
}); });
ipcRenderer.on( ipcRenderer.on(
ipcTypes.toRenderer.watcher.error, ipcTypes.toRenderer.watcher.error,
(_event: Electron.IpcRendererEvent, error: string) => { (_event: Electron.IpcRendererEvent, error: string) => {
@@ -58,6 +59,7 @@ ipcRenderer.on(ipcTypes.toRenderer.updates.checking, () => {
ipcRenderer.on(ipcTypes.toRenderer.updates.available, () => { ipcRenderer.on(ipcTypes.toRenderer.updates.available, () => {
dispatch(updateAvailable()); dispatch(updateAvailable());
}); });
ipcRenderer.on( ipcRenderer.on(
ipcTypes.toRenderer.updates.downloading, ipcTypes.toRenderer.updates.downloading,
(_event: Electron.IpcRendererEvent, arg) => { (_event: Electron.IpcRendererEvent, arg) => {
@@ -70,6 +72,7 @@ ipcRenderer.on(
); );
}, },
); );
ipcRenderer.on(ipcTypes.toRenderer.updates.downloaded, () => { ipcRenderer.on(ipcTypes.toRenderer.updates.downloaded, () => {
dispatch(updateDownloaded()); dispatch(updateDownloaded());
}); });

View File

@@ -1,5 +1,4 @@
// eslint-disable-all import {createContext, FC, ReactNode, useContext} from "react";
import { createContext, useContext } from "react";
import { notification } from "antd"; import { notification } from "antd";
/** /**
@@ -23,10 +22,10 @@ export const useNotification = () => {
* - Provide `api` via the NotificationContext. * - Provide `api` via the NotificationContext.
*/ */
interface NotificationProviderProps { interface NotificationProviderProps {
children?: React.ReactNode | React.ReactNode[]; children?: ReactNode | ReactNode[];
} }
export const NotificationProvider: React.FC<NotificationProviderProps> = ({ export const NotificationProvider: FC<NotificationProviderProps> = ({
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
children, //TODO: Unable to resolve this. Adding an eslint disable. children, //TODO: Unable to resolve this. Adding an eslint disable.
}) => { }) => {
@@ -37,6 +36,7 @@ export const NotificationProvider: React.FC<NotificationProviderProps> = ({
}); });
return ( return (
// @ts-ignore
<NotificationContext.Provider value={api}> <NotificationContext.Provider value={api}>
{/* contextHolder must be rendered in the DOM so notifications can appear */} {/* contextHolder must be rendered in the DOM so notifications can appear */}
{contextHolder} {contextHolder}

View File

@@ -33,4 +33,5 @@ function deepLowerCaseKeys<T = any>(obj: any): T {
{} as Record<string, any>, {} as Record<string, any>,
) as T; ) as T;
} }
export default deepLowerCaseKeys; export default deepLowerCaseKeys;

View File

@@ -12,6 +12,7 @@ function errorTypeCheck(passedError: Error | unknown): ParsedError {
stack: errorStack, stack: errorStack,
}; };
} }
export default errorTypeCheck; export default errorTypeCheck;
export interface ParsedError { export interface ParsedError {