Add UI basics.
This commit is contained in:
@@ -17,11 +17,9 @@ export async function handleQuickBookRequest(
|
|||||||
res: Response,
|
res: Response,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (process.platform !== "win32") {
|
if (process.platform !== "win32") {
|
||||||
res
|
res.status(500).json({
|
||||||
.status(500)
|
error: "QuickBooks Desktop integration is only available on Windows",
|
||||||
.json({
|
});
|
||||||
error: "QuickBooks Desktop integration is only available on Windows",
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import "@ant-design/v5-patch-for-react-19";
|
import "@ant-design/v5-patch-for-react-19";
|
||||||
import { Layout } from "antd";
|
import { Layout, Skeleton } from "antd";
|
||||||
import { User } from "firebase/auth";
|
import { User } from "firebase/auth";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
@@ -16,10 +16,15 @@ import { auth } from "./util/firebase";
|
|||||||
import { NotificationProvider } from "./util/notificationContext";
|
import { NotificationProvider } from "./util/notificationContext";
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [user, setUser] = useState<User | null>(null);
|
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) {
|
||||||
|
setUser(auth.currentUser);
|
||||||
|
} else {
|
||||||
|
setUser(false);
|
||||||
|
}
|
||||||
const unsubscribe = auth.onAuthStateChanged((user: User | null) => {
|
const unsubscribe = auth.onAuthStateChanged((user: User | null) => {
|
||||||
setUser(user);
|
setUser(user);
|
||||||
//Send back to the main process so that it knows we are authenticated.
|
//Send back to the main process so that it knows we are authenticated.
|
||||||
@@ -41,19 +46,25 @@ const App: React.FC = () => {
|
|||||||
<HashRouter>
|
<HashRouter>
|
||||||
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
|
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<Layout>
|
<Skeleton loading={user === null} active>
|
||||||
{!user ? (
|
<Layout style={{ minHeight: "100vh" }}>
|
||||||
<SignInForm />
|
{!user ? (
|
||||||
) : (
|
<SignInForm />
|
||||||
<>
|
) : (
|
||||||
<NavigationHeader />
|
<>
|
||||||
<UpdateAvailable />
|
<Layout.Header>
|
||||||
<Routes>
|
<NavigationHeader />
|
||||||
<Route path="/" element={<Settings />} />
|
</Layout.Header>
|
||||||
</Routes>
|
<Layout.Content style={{ padding: "0 24px" }}>
|
||||||
</>
|
<UpdateAvailable />
|
||||||
)}
|
<Routes>
|
||||||
</Layout>
|
<Route path="/" element={<Settings />} />
|
||||||
|
</Routes>
|
||||||
|
</Layout.Content>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Layout>
|
||||||
|
</Skeleton>
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
|||||||
@@ -1,28 +1,7 @@
|
|||||||
import { Button } from "antd";
|
|
||||||
import ipcTypes from "../../../../util/ipcTypes.json";
|
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Home</h1>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { selectWatcherStatus } from "@renderer/redux/app.slice";
|
import { selectWatcherStatus } from "@renderer/redux/app.slice";
|
||||||
import { useAppSelector } from "@renderer/redux/reduxHooks";
|
import { useAppSelector } from "@renderer/redux/reduxHooks";
|
||||||
|
import { auth } from "@renderer/util/firebase";
|
||||||
import { Badge, Layout, Menu } from "antd";
|
import { Badge, Layout, Menu } from "antd";
|
||||||
import { MenuItemType } from "antd/es/menu/interface";
|
import { MenuItemType } from "antd/es/menu/interface";
|
||||||
|
import { signOut } from "firebase/auth";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { NavLink } from "react-router";
|
import { NavLink } from "react-router";
|
||||||
|
|
||||||
@@ -10,6 +12,13 @@ const NavigationHeader: React.FC = () => {
|
|||||||
const isWatcherStarted = useAppSelector(selectWatcherStatus);
|
const isWatcherStarted = useAppSelector(selectWatcherStatus);
|
||||||
const menuItems: MenuItemType[] = [
|
const menuItems: MenuItemType[] = [
|
||||||
{ label: <NavLink to="/">{t("navigation.home")}</NavLink>, key: "home" },
|
{ 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>,
|
// label: <NavLink to="/settings">{t("navigation.settings")}</NavLink>,
|
||||||
// key: "settings",
|
// key: "settings",
|
||||||
|
|||||||
@@ -41,9 +41,10 @@ const SettingsWatchedPaths: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Timeline>
|
<Timeline
|
||||||
{watchedPaths?.map((path, index) => (
|
items={watchedPaths.map((path, index) => ({
|
||||||
<Timeline.Item key={index}>
|
key: index,
|
||||||
|
children: (
|
||||||
<Space align="baseline">
|
<Space align="baseline">
|
||||||
{path}
|
{path}
|
||||||
<Button
|
<Button
|
||||||
@@ -54,9 +55,9 @@ const SettingsWatchedPaths: React.FC = () => {
|
|||||||
onClick={() => handleRemovePath(path)}
|
onClick={() => handleRemovePath(path)}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Timeline.Item>
|
),
|
||||||
))}
|
}))}
|
||||||
</Timeline>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,12 +25,10 @@ const SettingsWatcher: React.FC = () => {
|
|||||||
interval: 0,
|
interval: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("*** ~ pollingState:", pollingState);
|
|
||||||
const getPollingStateFromStore = (): void => {
|
const getPollingStateFromStore = (): void => {
|
||||||
window.electron.ipcRenderer
|
window.electron.ipcRenderer
|
||||||
.invoke(ipcTypes.toMain.settings.watcher.getpolling)
|
.invoke(ipcTypes.toMain.settings.watcher.getpolling)
|
||||||
.then((storePollingState: { enabled: boolean; interval: number }) => {
|
.then((storePollingState: { enabled: boolean; interval: number }) => {
|
||||||
console.log("*** ~ .then ~ storePollingState:", storePollingState);
|
|
||||||
setPollingState(storePollingState);
|
setPollingState(storePollingState);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { Divider } from "antd";
|
import { Col, Row } from "antd";
|
||||||
import SettingsWatchedPaths from "./Settings.WatchedPaths";
|
import SettingsWatchedPaths from "./Settings.WatchedPaths";
|
||||||
import SettingsWatcher from "./Settings.Watcher";
|
import SettingsWatcher from "./Settings.Watcher";
|
||||||
import Home from "../Home/Home";
|
|
||||||
|
|
||||||
const Settings: React.FC = () => {
|
const Settings: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Row gutter={[32, 32]}>
|
||||||
<Home />
|
<Col span={24}>
|
||||||
<Divider />
|
<SettingsWatchedPaths />
|
||||||
<SettingsWatchedPaths />
|
</Col>
|
||||||
<SettingsWatcher />
|
<Col span={24}>
|
||||||
</div>
|
<SettingsWatcher />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Settings;
|
export default Settings;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { auth } from "@renderer/util/firebase";
|
import { auth } from "@renderer/util/firebase";
|
||||||
import type { FormProps } from "antd";
|
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 log from "electron-log/renderer";
|
||||||
import { signInWithEmailAndPassword } from "firebase/auth";
|
import { signInWithEmailAndPassword } from "firebase/auth";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import errorTypeCheck from "../../../../util/errorTypeCheck";
|
import errorTypeCheck from "../../../../util/errorTypeCheck";
|
||||||
|
|
||||||
type FieldType = {
|
type FieldType = {
|
||||||
@@ -12,14 +14,16 @@ type FieldType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SignInForm: React.FC = () => {
|
const SignInForm: React.FC = () => {
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
const onFinish: FormProps<FieldType>["onFinish"] = async (values) => {
|
const onFinish: FormProps<FieldType>["onFinish"] = async (values) => {
|
||||||
log.info("Form submitted successfully:", values);
|
|
||||||
const { username, password } = values;
|
const { username, password } = values;
|
||||||
try {
|
try {
|
||||||
const result = await signInWithEmailAndPassword(auth, username, password);
|
const result = await signInWithEmailAndPassword(auth, username, password);
|
||||||
log.debug("Login result", result);
|
log.debug("Login result", result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Login error", errorTypeCheck(error));
|
log.error("Login error", errorTypeCheck(error));
|
||||||
|
setError(t("auth.login.error"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,40 +47,30 @@ const SignInForm: React.FC = () => {
|
|||||||
<Form.Item<FieldType>
|
<Form.Item<FieldType>
|
||||||
label="Username"
|
label="Username"
|
||||||
name="username"
|
name="username"
|
||||||
rules={[{ required: true, message: "Please input your username!" }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item<FieldType>
|
<Form.Item<FieldType>
|
||||||
label="Password"
|
label="Password"
|
||||||
name="password"
|
name="password"
|
||||||
rules={[{ required: true, message: "Please input your password!" }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input.Password />
|
<Input.Password />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item<FieldType>
|
|
||||||
name="remember"
|
|
||||||
valuePropName="checked"
|
|
||||||
label={null}
|
|
||||||
>
|
|
||||||
<Checkbox>Remember me</Checkbox>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={null}>
|
<Form.Item label={null}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
Submit
|
{t("auth.login.login")}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button
|
{error && (
|
||||||
onClick={async () => {
|
<Form.Item label={null}>
|
||||||
if (auth.currentUser) {
|
<Alert message={error} type="error" />
|
||||||
log.info(auth.currentUser);
|
</Form.Item>
|
||||||
} else {
|
)}
|
||||||
log.warn("No user is currently signed in.");
|
<Form.Item label={null}>
|
||||||
}
|
<Button>{t("auth.login.resetpassword")}</Button>
|
||||||
}}
|
</Form.Item>
|
||||||
>
|
|
||||||
Log user info
|
|
||||||
</Button>
|
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
{
|
{
|
||||||
"translation": {
|
"translation": {
|
||||||
|
"auth": {
|
||||||
|
"login": {
|
||||||
|
"error": "The username and password combination provided is not valid.",
|
||||||
|
"login": "Log In",
|
||||||
|
"resetpassword": "Reset Password"
|
||||||
|
}
|
||||||
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"settings": "Settings"
|
"settings": "Settings",
|
||||||
|
"signout": "Sign Out"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"actions": {
|
"actions": {
|
||||||
|
|||||||
@@ -45,6 +45,55 @@
|
|||||||
<folder_node>
|
<folder_node>
|
||||||
<name>translation</name>
|
<name>translation</name>
|
||||||
<children>
|
<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>
|
<folder_node>
|
||||||
<name>navigation</name>
|
<name>navigation</name>
|
||||||
<children>
|
<children>
|
||||||
@@ -74,6 +123,19 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
|
|||||||
Reference in New Issue
Block a user