Strip out partner related functionality.

This commit is contained in:
Patrick Fic
2025-12-08 13:53:15 -08:00
parent 267ef714a7
commit 39d81bbc6a
54 changed files with 79 additions and 3889 deletions

View File

@@ -2,7 +2,7 @@
<html>
<head>
<meta charset="UTF-8" />
<title>Shop Partner</title>
<title>EMS Uploader</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<!-- <meta
http-equiv="Content-Security-Policy"

View File

@@ -1,97 +0,0 @@
import { test, expect } from "@playwright/test";
import { Page } from "@playwright/test";
// src/renderer/src/App.test.tsx
// Mock data
const mockUser = {
uid: "test123",
email: "test@example.com",
displayName: "Test User",
toJSON: () => ({
uid: "test123",
email: "test@example.com",
displayName: "Test User",
}),
};
test.describe("App Component", () => {
let page: Page;
test.beforeEach(async ({ browser }) => {
page = await browser.newPage();
// Mock Firebase Auth
await page.addInitScript(() => {
window.mockAuthState = null;
// Mock the firebase auth module
jest.mock("./util/firebase", () => ({
auth: {
onAuthStateChanged: (callback) => {
callback(window.mockAuthState);
// Return mock unsubscribe function
return () => {};
},
},
}));
// Mock electron IPC
window.electron = {
ipcRenderer: {
send: jest.fn(),
},
};
});
await page.goto("/");
});
test("should show SignInForm when user is not authenticated", async () => {
await page.evaluate(() => {
window.mockAuthState = null;
});
await page.reload();
// Check if SignInForm is visible
const signInForm = await page
.locator("form")
.filter({ hasText: "Sign In" });
await expect(signInForm).toBeVisible();
});
test("should show routes when user is authenticated", async () => {
await page.evaluate((user) => {
window.mockAuthState = user;
}, mockUser);
await page.reload();
// Check if AuthHome is visible
const authHome = await page.locator('div:text("AuthHome")');
await expect(authHome).toBeVisible();
// Check that electron IPC was called with auth state
await expect(
page.evaluate(() => {
return window.electron.ipcRenderer.send.mock.calls.length > 0;
}),
).resolves.toBe(true);
});
test("should navigate to settings page when authenticated", async () => {
await page.evaluate((user) => {
window.mockAuthState = user;
}, mockUser);
await page.reload();
// Navigate to settings
await page.click('a[href="/settings"]');
// Check if Settings page is visible
const settingsPage = await page.locator('div:text("Settings")');
await expect(settingsPage).toBeVisible();
});
});

View File

@@ -1,44 +1,16 @@
import "@ant-design/v5-patch-for-react-19";
import { Layout, Skeleton, ConfigProvider, Badge } from "antd";
import { User } from "firebase/auth";
import { useEffect, useState, FC } from "react";
import { Badge, ConfigProvider, Layout, Skeleton } from "antd";
import { FC } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Provider } from "react-redux";
import { HashRouter, Route, Routes } from "react-router";
import ipcTypes from "../../util/ipcTypes.json";
import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback/ErrorBoundaryFallback";
import Settings from "./components/Settings/Settings";
import SignInForm from "./components/SignInForm/SignInForm";
import UpdateAvailable from "./components/UpdateAvailable/UpdateAvailable";
import reduxStore from "./redux/redux-store";
import { auth } from "./util/firebase";
import { NotificationProvider } from "./util/notificationContext";
const App: FC = () => {
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.
if (user) {
window.electron.ipcRenderer.send(
ipcTypes.toMain.authStateChanged,
user.toJSON(),
);
window.electron.ipcRenderer.send(ipcTypes.toMain.watcher.start);
}
});
// Clean up the listener when component unmounts
return (): void => unsubscribe();
}, []);
const isTest = window.api.isTest();
return (
@@ -58,23 +30,19 @@ const App: FC = () => {
<HashRouter>
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<NotificationProvider>
<Skeleton loading={user === false} active>
<Skeleton loading={false} active>
<Layout style={{ minHeight: "100vh" }}>
{!user ? (
<SignInForm />
) : (
<Badge.Ribbon
text={isTest && "Connected to Test"}
color={isTest ? "red" : undefined}
>
<Layout.Content style={{ padding: "0 24px" }}>
<UpdateAvailable />
<Routes>
<Route path="/" element={<Settings />} />
</Routes>
</Layout.Content>
</Badge.Ribbon>
)}
<Badge.Ribbon
text={isTest && "Connected to Test"}
color={isTest ? "red" : undefined}
>
<Layout.Content style={{ padding: "0 24px" }}>
<UpdateAvailable />
<Routes>
<Route path="/" element={<Settings />} />
</Routes>
</Layout.Content>
</Badge.Ribbon>
</Layout>
</Skeleton>
</NotificationProvider>

View File

@@ -1,11 +0,0 @@
import { FC } from "react";
const Home: FC = () => {
return (
<div>
<h1>Home</h1>
</div>
);
};
export default Home;

View File

@@ -1,164 +0,0 @@
import { useState, useEffect } from "react";
import ipcTypes from "../../../../../util/ipcTypes.json";
import {
PaintScaleConfig,
PaintScaleType,
} from "../../../../../util/types/paintScale";
import { message } from "antd";
import { useTranslation } from "react-i18next";
type ConfigType = "input" | "output";
export const usePaintScaleConfig = (configType: ConfigType) => {
const [paintScaleConfigs, setPaintScaleConfigs] = useState<
PaintScaleConfig[]
>([]);
const { t } = useTranslation();
// Get the appropriate IPC methods based on config type
const getConfigsMethod =
configType === "input"
? ipcTypes.toMain.settings.paintScale.getInputConfigs
: ipcTypes.toMain.settings.paintScale.getOutputConfigs;
const setConfigsMethod =
configType === "input"
? ipcTypes.toMain.settings.paintScale.setInputConfigs
: ipcTypes.toMain.settings.paintScale.setOutputConfigs;
const setPathMethod =
configType === "input"
? ipcTypes.toMain.settings.paintScale.setInputPath
: ipcTypes.toMain.settings.paintScale.setOutputPath;
// Load paint scale configs on mount
useEffect(() => {
window.electron.ipcRenderer
.invoke(getConfigsMethod)
.then((configs: PaintScaleConfig[]) => {
// Ensure all configs have a pollingInterval and type (for backward compatibility)
const defaultPolling = configType === "input" ? 1440 : 60;
const updatedConfigs = configs.map((config) => ({
...config,
pollingInterval: config.pollingInterval || defaultPolling, // Default to 1440 for input, 60 for output
type: config.type || PaintScaleType.PPG, // Default type if missing
}));
setPaintScaleConfigs(updatedConfigs || []);
})
.catch((error) => {
console.error(
`Failed to load paint scale ${configType} configs:`,
error,
);
});
}, [getConfigsMethod]);
// Save configs to store and notify main process of config changes
const saveConfigs = (configs: PaintScaleConfig[]) => {
window.electron.ipcRenderer
.invoke(setConfigsMethod, configs)
.then(() => {
// Notify main process to update cron job
if (configType === "input") {
window.electron.ipcRenderer.send(
ipcTypes.toMain.settings.paintScale.updateInputCron,
configs,
);
} else if (configType === "output") {
window.electron.ipcRenderer.send(
ipcTypes.toMain.settings.paintScale.updateOutputCron,
configs,
);
}
})
.catch((error) => {
console.error(
`Failed to save paint scale ${configType} configs:`,
error,
);
});
};
// New helper to check if a path is unique across input and output configs
const checkPathUnique = async (newPath: string): Promise<boolean> => {
try {
const inputConfigs: PaintScaleConfig[] =
await window.electron.ipcRenderer.invoke(
ipcTypes.toMain.settings.paintScale.getInputConfigs,
);
const outputConfigs: PaintScaleConfig[] =
await window.electron.ipcRenderer.invoke(
ipcTypes.toMain.settings.paintScale.getOutputConfigs,
);
const allConfigs = [...inputConfigs, ...outputConfigs];
// Allow updating the current config even if its current value equals newPath.
return !allConfigs.some((config) => config.path === newPath);
} catch (error) {
console.error("Failed to check unique path:", error);
return false;
}
};
// Handle adding a new paint scale config
const handleAddConfig = (type: PaintScaleType) => {
const defaultPolling = configType === "input" ? 1440 : 60;
const newConfig: PaintScaleConfig = {
id: Date.now().toString(),
type,
pollingInterval: defaultPolling, // Default to 1440 for input, 60 for output
};
const updatedConfigs = [...paintScaleConfigs, newConfig];
setPaintScaleConfigs(updatedConfigs);
saveConfigs(updatedConfigs);
};
// Handle removing a config
const handleRemoveConfig = (id: string) => {
const updatedConfigs = paintScaleConfigs.filter(
(config) => config.id !== id,
);
setPaintScaleConfigs(updatedConfigs);
saveConfigs(updatedConfigs);
};
// Handle path selection (modified to check directory uniqueness)
const handlePathChange = async (id: string) => {
try {
const path: string | null = await window.electron.ipcRenderer.invoke(
setPathMethod,
id,
);
if (path) {
const isUnique = await checkPathUnique(path);
if (!isUnique) {
message.error(t("settings.errors.duplicatePath"));
return;
}
const updatedConfigs = paintScaleConfigs.map((config) =>
config.id === id ? { ...config, path } : config,
);
setPaintScaleConfigs(updatedConfigs);
saveConfigs(updatedConfigs);
}
} catch (error) {
console.error(`Failed to set paint scale ${configType} path:`, error);
}
};
// Handle polling interval change
const handlePollingIntervalChange = (id: string, pollingInterval: number) => {
const updatedConfigs = paintScaleConfigs.map((config) =>
config.id === id ? { ...config, pollingInterval } : config,
);
setPaintScaleConfigs(updatedConfigs);
saveConfigs(updatedConfigs);
};
return {
paintScaleConfigs,
handleAddConfig,
handleRemoveConfig,
handlePathChange,
handlePollingIntervalChange,
};
};

View File

@@ -1,46 +0,0 @@
import { FolderOpenFilled } from "@ant-design/icons";
import { Button, Card, Input, Space } from "antd";
import { useEffect, useState, FC } from "react";
import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json";
const SettingsEmsOutFilePath: FC = () => {
const { t } = useTranslation();
const [emsFilePath, setEmsFilePath] = useState<string | null>(null);
const getPollingStateFromStore = (): void => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.settings.getEmsOutFilePath)
.then((filePath: string | null) => {
setEmsFilePath(filePath);
});
};
//Get state first time it renders.
useEffect(() => {
getPollingStateFromStore();
}, []);
const handlePathChange = (): void => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.settings.setEmsOutFilePath)
.then((filePath: string | null) => {
setEmsFilePath(filePath);
});
};
return (
<Card title={t("settings.labels.emsOutFilePath")}>
<Space wrap>
<Input
value={emsFilePath || ""}
placeholder={t("settings.labels.emsOutFilePath")}
disabled
/>
<Button onClick={handlePathChange} icon={<FolderOpenFilled />} />
</Space>
</Card>
);
};
export default SettingsEmsOutFilePath;

View File

@@ -1,183 +0,0 @@
import {
CheckCircleFilled,
FileAddFilled,
FolderOpenFilled,
WarningFilled,
} from "@ant-design/icons";
import {
Button,
Card,
Input,
Modal,
Select,
Space,
Table,
Tag,
theme,
Tooltip,
} from "antd";
import { JSX, useState } from "react";
import { useTranslation } from "react-i18next";
import {
PaintScaleConfig,
PaintScaleType,
paintScaleTypeOptions,
} from "../../../../util/types/paintScale";
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
const SettingsPaintScaleInputPaths = (): JSX.Element => {
const { t } = useTranslation();
const { token } = theme.useToken(); // Access theme tokens
const {
paintScaleConfigs,
handleAddConfig,
handleRemoveConfig,
handlePathChange,
handlePollingIntervalChange,
} = usePaintScaleConfig("output");
const [isModalVisible, setIsModalVisible] = useState(false);
const [selectedType, setSelectedType] = useState<PaintScaleType | null>(null);
// Show modal when adding a new path
const showAddPathModal = () => {
setSelectedType(null);
setIsModalVisible(true);
};
// Handle modal confirmation
const handleModalOk = () => {
if (selectedType) {
handleAddConfig(selectedType);
setIsModalVisible(false);
}
};
// Handle modal cancellation
const handleModalCancel = () => {
setIsModalVisible(false);
};
// Table columns for paint scale configs
const columns = [
{
title: t("settings.labels.paintScaleType"),
dataIndex: "type",
key: "type",
render: (type: PaintScaleType) => {
const typeOption = paintScaleTypeOptions.find(
(option) => option.value === type,
);
const label = typeOption ? typeOption.label : type;
const colorMap: Partial<Record<PaintScaleType, string>> = {
[PaintScaleType.PPG]: "blue",
// Add other types and colors as needed
};
return <Tag color={colorMap[type] || "default"}>{label}</Tag>;
},
},
{
title: t("settings.labels.paintScalePath"),
dataIndex: "path",
key: "path",
render: (path: string | null, record: PaintScaleConfig) => {
const isValid = path && path.trim() !== "";
return (
<Space>
<Input
value={path || ""}
placeholder={t("settings.labels.paintScalePath")}
disabled
style={{
borderColor: isValid ? token.colorSuccess : token.colorError, // Use semantic tokens
}}
suffix={
<Tooltip
title={
isValid
? t("settings.labels.validPath")
: t("settings.labels.invalidPath")
}
>
{isValid ? (
<CheckCircleFilled style={{ color: token.colorSuccess }} />
) : (
<WarningFilled style={{ color: token.colorError }} />
)}
</Tooltip>
}
/>
<Button
onClick={() => handlePathChange(record.id)}
icon={<FolderOpenFilled />}
/>
</Space>
);
},
},
{
title: t("settings.labels.pollingInterval"),
dataIndex: "pollingInterval",
key: "pollingInterval",
render: (pollingInterval: number, record: PaintScaleConfig) => (
<Input
type="number"
value={pollingInterval}
onChange={(e) =>
handlePollingIntervalChange(record.id, Number(e.target.value))
}
style={{ width: 100 }}
placeholder={t("settings.labels.pollingInterval")}
/>
),
},
{
title: t("settings.labels.actions"),
key: "actions",
render: (_: any, record: PaintScaleConfig) => (
<Button danger onClick={() => handleRemoveConfig(record.id)}>
{t("settings.labels.remove")}
</Button>
),
},
];
return (
<>
<Card
title={t("settings.labels.paintScaleSettingsInput")}
extra={
<Button onClick={showAddPathModal} icon={<FileAddFilled />}>
{t("settings.actions.addpath")}
</Button>
}
>
<Table
dataSource={paintScaleConfigs}
columns={columns}
rowKey="id"
pagination={false}
/>
</Card>
<Modal
title={t("settings.labels.selectPaintScaleType")}
open={isModalVisible}
onOk={handleModalOk}
onCancel={handleModalCancel}
okButtonProps={{ disabled: !selectedType }}
>
<Select
value={selectedType}
options={paintScaleTypeOptions}
onChange={(value) => setSelectedType(value)}
style={{ width: "100%" }}
placeholder={t("settings.labels.selectPaintScaleType")}
/>
</Modal>
</>
);
};
export default SettingsPaintScaleInputPaths;

View File

@@ -1,173 +0,0 @@
import {
CheckCircleFilled,
FileAddFilled,
FolderOpenFilled,
WarningFilled,
} from "@ant-design/icons";
import {
Button,
Card,
Input,
Modal,
Select,
Space,
Table,
Tag,
theme,
} from "antd";
import { JSX, useState } from "react";
import { useTranslation } from "react-i18next";
import {
PaintScaleConfig,
PaintScaleType,
paintScaleTypeOptions,
} from "../../../../util/types/paintScale";
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
const SettingsPaintScaleOutputPaths = (): JSX.Element => {
const { token } = theme.useToken();
const { t } = useTranslation();
const {
paintScaleConfigs,
handleAddConfig,
handleRemoveConfig,
handlePathChange,
handlePollingIntervalChange,
} = usePaintScaleConfig("input");
const [isModalVisible, setIsModalVisible] = useState(false);
const [selectedType, setSelectedType] = useState<PaintScaleType | null>(null);
// Show modal when adding a new path
const showAddPathModal = () => {
setSelectedType(null);
setIsModalVisible(true);
};
// Handle modal confirmation
const handleModalOk = () => {
if (selectedType) {
handleAddConfig(selectedType);
setIsModalVisible(false);
}
};
// Handle modal cancellation
const handleModalCancel = () => {
setIsModalVisible(false);
};
// Table columns for paint scale configs
const columns = [
{
title: t("settings.labels.paintScaleType"),
dataIndex: "type",
key: "type",
render: (type: PaintScaleType) => {
const typeOption = paintScaleTypeOptions.find(
(option) => option.value === type,
);
const label = typeOption ? typeOption.label : type;
const colorMap: Partial<Record<PaintScaleType, string>> = {
[PaintScaleType.PPG]: "blue",
// Add other types and colors as needed
};
return <Tag color={colorMap[type] || "default"}>{label}</Tag>;
},
},
{
title: t("settings.labels.paintScalePath"),
dataIndex: "path",
key: "path",
render: (path: string | null, record: PaintScaleConfig) => {
const isValid = path && path.trim() !== "";
return (
<Space>
<Input
value={path || ""}
placeholder={t("settings.labels.paintScalePath")}
disabled
style={{
borderColor: isValid ? token.colorSuccess : token.colorError,
}}
suffix={
isValid ? (
<CheckCircleFilled style={{ color: token.colorSuccess }} />
) : (
<WarningFilled style={{ color: token.colorError }} />
)
}
/>
<Button
onClick={() => handlePathChange(record.id)}
icon={<FolderOpenFilled />}
/>
</Space>
);
},
},
{
title: t("settings.labels.pollingInterval"),
dataIndex: "pollingInterval",
key: "pollingInterval",
render: (pollingInterval: number, record: PaintScaleConfig) => (
<Input
type="number"
value={pollingInterval}
onChange={(e) =>
handlePollingIntervalChange(record.id, Number(e.target.value))
}
style={{ width: 100 }}
placeholder={t("settings.labels.pollingInterval")}
/>
),
},
{
title: t("settings.labels.actions"),
key: "actions",
render: (_: any, record: PaintScaleConfig) => (
<Button danger onClick={() => handleRemoveConfig(record.id)}>
{t("settings.labels.remove")}
</Button>
),
},
];
return (
<>
<Card
title={t("settings.labels.paintScaleSettingsOutput")}
extra={
<Button onClick={showAddPathModal} icon={<FileAddFilled />}>
{t("settings.actions.addpath")}
</Button>
}
>
<Table
dataSource={paintScaleConfigs}
columns={columns}
rowKey="id"
pagination={false}
/>
</Card>
<Modal
title={t("settings.labels.selectPaintScaleType")}
open={isModalVisible}
onOk={handleModalOk}
onCancel={handleModalCancel}
okButtonProps={{ disabled: !selectedType }}
>
<Select
value={selectedType}
options={paintScaleTypeOptions}
onChange={(value) => setSelectedType(value)}
style={{ width: "100%" }}
placeholder={t("settings.labels.selectPaintScaleType")}
/>
</Modal>
</>
);
};
export default SettingsPaintScaleOutputPaths;

View File

@@ -1,46 +0,0 @@
import { FolderOpenFilled } from "@ant-design/icons";
import { Button, Card, Input, Space } from "antd";
import { useEffect, useState, FC } from "react";
import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json";
const SettingsPpcFilepath: FC = () => {
const { t } = useTranslation();
const [ppcFilePath, setPpcFilePath] = useState<string | null>(null);
const getPollingStateFromStore = (): void => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.settings.getPpcFilePath)
.then((filePath: string | null) => {
setPpcFilePath(filePath);
});
};
//Get state first time it renders.
useEffect(() => {
getPollingStateFromStore();
}, []);
const handlePathChange = (): void => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.settings.setPpcFilePath)
.then((filePath: string | null) => {
setPpcFilePath(filePath);
});
};
return (
<Card title={t("settings.labels.ppcfilepath")}>
<Space wrap>
<Input
value={ppcFilePath || ""}
placeholder={t("settings.labels.ppcfilepath")}
disabled
/>
<Button onClick={handlePathChange} icon={<FolderOpenFilled />} />
</Space>
</Card>
);
};
export default SettingsPpcFilepath;

View File

@@ -3,43 +3,23 @@ import { Col, Row } from "antd";
import { FC } from "react";
import SettingsWatchedPaths from "./Settings.WatchedPaths";
import SettingsWatcher from "./Settings.Watcher";
import Welcome from "../Welcome/Welcome";
import SettingsPpcFilepath from "./Settings.PpcFilePath";
import SettingsEmsOutFilePath from "./Settings.EmsOutFilePath";
import SettingsPaintScaleInputPaths from "./Settings.PaintScaleInputPaths";
import SettingsPaintScaleOutputPaths from "./Settings.PaintScaleOutputPaths";
const colSpans = {
md: 12, // Two columns on medium screens and above
sm: 24, // One column on small screens
md: 12, // Two columns on medium screens and above
sm: 24, // One column on small screens
};
const Settings: FC = () => {
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<Welcome />
</Col>
<Col {...colSpans}>
<SettingsWatchedPaths />
</Col>
<Col {...colSpans}>
<SettingsWatcher />
</Col>
<Col {...colSpans}>
<SettingsPpcFilepath />
</Col>
<Col {...colSpans}>
<SettingsEmsOutFilePath />
</Col>
<Col {...colSpans}>
<SettingsPaintScaleInputPaths />
</Col>
<Col {...colSpans}>
<SettingsPaintScaleOutputPaths />
</Col>
</Row>
);
return (
<Row gutter={[16, 16]}>
<Col {...colSpans}>
<SettingsWatchedPaths />
</Col>
<Col {...colSpans}>
<SettingsWatcher />
</Col>
</Row>
);
};
export default Settings;
export default Settings;

View File

@@ -1,142 +0,0 @@
import { auth } from "@renderer/util/firebase";
import type { FormProps } 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";
import { useTranslation } from "react-i18next";
import errorTypeCheck from "../../../../util/errorTypeCheck";
import ipcTypes from "../../../../util/ipcTypes.json";
const { Title } = Typography;
type FieldType = {
username: string;
password: string;
remember?: string;
};
const SignInForm: React.FC = () => {
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const { t } = useTranslation();
const onFinish: FormProps<FieldType>["onFinish"] = async (values) => {
const { username, password } = values;
setLoading(true);
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"));
} finally {
setLoading(false);
}
};
const onFinishFailed: FormProps<FieldType>["onFinishFailed"] = (
errorInfo,
) => {
log.log("Failed:", errorInfo);
};
return (
<Card
style={{
maxWidth: 600,
margin: "auto auto",
borderRadius: 8,
paddingLeft: 48,
paddingRight: 48,
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
}}
>
<div style={{ textAlign: "center", marginBottom: 24 }}>
<Title level={2}>
{import.meta.env.VITE_COMPANY === "IMEX"
? t("title.imex")
: t("title.rome")}
</Title>
</div>
<Form
name="desktop-sign-in"
layout="vertical"
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
requiredMark={false}
>
{error && (
<Form.Item>
<Alert
message={error}
type="error"
showIcon
style={{ marginBottom: 16 }}
/>
</Form.Item>
)}
<Form.Item<FieldType>
label="Username"
name="username"
rules={[
{
required: true,
message: t(
"auth.login.usernameRequired",
"Please enter your username",
),
},
]}
>
<Input size="large" />
</Form.Item>
<Form.Item<FieldType>
label="Password"
name="password"
rules={[
{
required: true,
message: t(
"auth.login.passwordRequired",
"Please enter your password",
),
},
]}
>
<Input.Password size="large" />
</Form.Item>
<Form.Item>
<Button
type="primary"
loading={loading}
htmlType="submit"
size="large"
block
>
{t("auth.login.login")}
</Button>
</Form.Item>
<Form.Item style={{ marginBottom: 0, textAlign: "center" }}>
<Button
type="link"
onClick={(): void => {
window.electron.ipcRenderer.send(
ipcTypes.toMain.user.resetPassword,
);
}}
>
{t("auth.login.resetpassword")}
</Button>
</Form.Item>
</Form>
</Card>
);
};
export default SignInForm;

View File

@@ -1,15 +0,0 @@
import { JSX, useState } from "react";
function Versions(): JSX.Element {
const [versions] = useState(window.electron.process.versions);
return (
<ul className="versions">
<li className="electron-version">Electron v{versions.electron}</li>
<li className="chrome-version">Chromium v{versions.chrome}</li>
<li className="node-version">Node v{versions.node}</li>
</ul>
);
}
export default Versions;

View File

@@ -1,49 +0,0 @@
import { LogoutOutlined } from "@ant-design/icons";
import { auth } from "@renderer/util/firebase";
import { Button, Space, Typography } from "antd";
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<string | null>(null);
useEffect(() => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.user.getActiveShop)
.then((shopName: string) => {
console.log("Active shop name:", shopName);
setShopName(shopName);
});
}, []);
return (
<>
<Typography.Title level={4}>
{t("auth.labels.welcome", {
name: isEmpty(auth.currentUser?.displayName)
? auth.currentUser?.email
: `${auth.currentUser?.displayName} (${auth.currentUser?.email})`.trim(),
})}
</Typography.Title>
<Space align="baseline">
<Typography.Paragraph>{shopName || ""}</Typography.Paragraph>
<Button
size="small"
danger
icon={<LogoutOutlined />}
onClick={(): void => {
auth.signOut().catch((error) => {
console.error("Sign out error:", error);
});
}}
>
{t("navigation.signout")}
</Button>
</Space>
</>
);
};
export default Welcome;