Add UI basics.

This commit is contained in:
Patrick Fic
2025-03-28 10:20:31 -07:00
parent ecab1e8f05
commit c33b02f77e
10 changed files with 142 additions and 81 deletions

View File

@@ -17,11 +17,9 @@ export async function handleQuickBookRequest(
res: Response,
): Promise<void> {
if (process.platform !== "win32") {
res
.status(500)
.json({
error: "QuickBooks Desktop integration is only available on Windows",
});
res.status(500).json({
error: "QuickBooks Desktop integration is only available on Windows",
});
return;
}

View File

@@ -1,5 +1,5 @@
import "@ant-design/v5-patch-for-react-19";
import { Layout } from "antd";
import { Layout, Skeleton } from "antd";
import { User } from "firebase/auth";
import { useEffect, useState } from "react";
import { ErrorBoundary } from "react-error-boundary";
@@ -16,10 +16,15 @@ import { auth } from "./util/firebase";
import { NotificationProvider } from "./util/notificationContext";
const App: React.FC = () => {
const [user, setUser] = useState<User | null>(null);
const [user, setUser] = useState<User | boolean | null>(false);
useEffect(() => {
// Only set up the listener once when component mounts
if (auth.currentUser) {
setUser(auth.currentUser);
} else {
setUser(false);
}
const unsubscribe = auth.onAuthStateChanged((user: User | null) => {
setUser(user);
//Send back to the main process so that it knows we are authenticated.
@@ -41,19 +46,25 @@ const App: React.FC = () => {
<HashRouter>
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<NotificationProvider>
<Layout>
{!user ? (
<SignInForm />
) : (
<>
<NavigationHeader />
<UpdateAvailable />
<Routes>
<Route path="/" element={<Settings />} />
</Routes>
</>
)}
</Layout>
<Skeleton loading={user === null} active>
<Layout style={{ minHeight: "100vh" }}>
{!user ? (
<SignInForm />
) : (
<>
<Layout.Header>
<NavigationHeader />
</Layout.Header>
<Layout.Content style={{ padding: "0 24px" }}>
<UpdateAvailable />
<Routes>
<Route path="/" element={<Settings />} />
</Routes>
</Layout.Content>
</>
)}
</Layout>
</Skeleton>
</NotificationProvider>
</ErrorBoundary>
</HashRouter>

View File

@@ -1,28 +1,7 @@
import { Button } from "antd";
import ipcTypes from "../../../../util/ipcTypes.json";
const Home: React.FC = () => {
return (
<div>
<h1>Home</h1>
<Button
onClick={(): void => {
window.electron.ipcRenderer.send(
ipcTypes.toMain.debug.decodeEstimate,
);
}}
>
Test Decode Estimate
</Button>
<Button
onClick={(): void => {
window.electron.ipcRenderer.send(
ipcTypes.toMain.updates.checkForUpdates,
);
}}
>
Check for Update
</Button>
</div>
);
};

View File

@@ -1,7 +1,9 @@
import { selectWatcherStatus } from "@renderer/redux/app.slice";
import { useAppSelector } from "@renderer/redux/reduxHooks";
import { auth } from "@renderer/util/firebase";
import { Badge, Layout, Menu } from "antd";
import { MenuItemType } from "antd/es/menu/interface";
import { signOut } from "firebase/auth";
import { useTranslation } from "react-i18next";
import { NavLink } from "react-router";
@@ -10,6 +12,13 @@ const NavigationHeader: React.FC = () => {
const isWatcherStarted = useAppSelector(selectWatcherStatus);
const menuItems: MenuItemType[] = [
{ label: <NavLink to="/">{t("navigation.home")}</NavLink>, key: "home" },
{
label: t("navigation.signout"),
key: "watchlist",
onClick: (): void => {
signOut(auth);
},
},
// {
// label: <NavLink to="/settings">{t("navigation.settings")}</NavLink>,
// key: "settings",

View File

@@ -41,9 +41,10 @@ const SettingsWatchedPaths: React.FC = () => {
</Button>
}
>
<Timeline>
{watchedPaths?.map((path, index) => (
<Timeline.Item key={index}>
<Timeline
items={watchedPaths.map((path, index) => ({
key: index,
children: (
<Space align="baseline">
{path}
<Button
@@ -54,9 +55,9 @@ const SettingsWatchedPaths: React.FC = () => {
onClick={() => handleRemovePath(path)}
/>
</Space>
</Timeline.Item>
))}
</Timeline>
),
}))}
/>
</Card>
);
};

View File

@@ -25,12 +25,10 @@ const SettingsWatcher: React.FC = () => {
interval: 0,
});
console.log("*** ~ pollingState:", pollingState);
const getPollingStateFromStore = (): void => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.settings.watcher.getpolling)
.then((storePollingState: { enabled: boolean; interval: number }) => {
console.log("*** ~ .then ~ storePollingState:", storePollingState);
setPollingState(storePollingState);
});
};

View File

@@ -1,16 +1,17 @@
import { Divider } from "antd";
import { Col, Row } from "antd";
import SettingsWatchedPaths from "./Settings.WatchedPaths";
import SettingsWatcher from "./Settings.Watcher";
import Home from "../Home/Home";
const Settings: React.FC = () => {
return (
<div>
<Home />
<Divider />
<SettingsWatchedPaths />
<SettingsWatcher />
</div>
<Row gutter={[32, 32]}>
<Col span={24}>
<SettingsWatchedPaths />
</Col>
<Col span={24}>
<SettingsWatcher />
</Col>
</Row>
);
};
export default Settings;

View File

@@ -1,8 +1,10 @@
import { auth } from "@renderer/util/firebase";
import type { FormProps } from "antd";
import { Button, Checkbox, Form, Input } from "antd";
import { Alert, Button, Checkbox, Form, Input } from "antd";
import log from "electron-log/renderer";
import { signInWithEmailAndPassword } from "firebase/auth";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import errorTypeCheck from "../../../../util/errorTypeCheck";
type FieldType = {
@@ -12,14 +14,16 @@ type FieldType = {
};
const SignInForm: React.FC = () => {
const [error, setError] = useState<string | null>(null);
const { t } = useTranslation();
const onFinish: FormProps<FieldType>["onFinish"] = async (values) => {
log.info("Form submitted successfully:", values);
const { username, password } = values;
try {
const result = await signInWithEmailAndPassword(auth, username, password);
log.debug("Login result", result);
} catch (error) {
log.error("Login error", errorTypeCheck(error));
setError(t("auth.login.error"));
}
};
@@ -43,40 +47,30 @@ const SignInForm: React.FC = () => {
<Form.Item<FieldType>
label="Username"
name="username"
rules={[{ required: true, message: "Please input your username!" }]}
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item<FieldType>
label="Password"
name="password"
rules={[{ required: true, message: "Please input your password!" }]}
rules={[{ required: true }]}
>
<Input.Password />
</Form.Item>
<Form.Item<FieldType>
name="remember"
valuePropName="checked"
label={null}
>
<Checkbox>Remember me</Checkbox>
</Form.Item>
<Form.Item label={null}>
<Button type="primary" htmlType="submit">
Submit
{t("auth.login.login")}
</Button>
</Form.Item>
<Button
onClick={async () => {
if (auth.currentUser) {
log.info(auth.currentUser);
} else {
log.warn("No user is currently signed in.");
}
}}
>
Log user info
</Button>
{error && (
<Form.Item label={null}>
<Alert message={error} type="error" />
</Form.Item>
)}
<Form.Item label={null}>
<Button>{t("auth.login.resetpassword")}</Button>
</Form.Item>
</Form>
);
};

View File

@@ -1,8 +1,16 @@
{
"translation": {
"auth": {
"login": {
"error": "The username and password combination provided is not valid.",
"login": "Log In",
"resetpassword": "Reset Password"
}
},
"navigation": {
"home": "Home",
"settings": "Settings"
"settings": "Settings",
"signout": "Sign Out"
},
"settings": {
"actions": {

View File

@@ -45,6 +45,55 @@
<folder_node>
<name>translation</name>
<children>
<folder_node>
<name>auth</name>
<children>
<folder_node>
<name>login</name>
<children>
<concept_node>
<name>error</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>login</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>resetpassword</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>navigation</name>
<children>
@@ -74,6 +123,19 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>signout</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>