Progress Update.

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-01-11 01:29:36 -05:00
parent ab299619dd
commit d3654ec16e
7 changed files with 237 additions and 171 deletions

View File

@@ -1,7 +1,7 @@
import {useSplitClient} from "@splitsoftware/splitio-react";
import {Button, Result} from "antd";
import LogRocket from "logrocket";
import React, {lazy, Suspense, useEffect} from "react";
import React, {lazy, Suspense, useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Route, Routes} from "react-router-dom";
@@ -19,6 +19,7 @@ import {checkUserSession} from "../redux/user/user.actions";
import {selectBodyshop, selectCurrentUser,} from "../redux/user/user.selectors";
import PrivateRoute from "../components/PrivateRoute";
import "./App.styles.scss";
import handleBeta from "../utils/betaHandler";
const ResetPassword = lazy(() =>
import("../pages/reset-password/reset-password.component")
@@ -40,14 +41,16 @@ const mapDispatchToProps = (dispatch) => ({
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
});
export function App({
bodyshop,
checkUserSession,
currentUser,
online,
setOnline,
}) {
export function App({bodyshop, checkUserSession, currentUser, online, setOnline}) {
const client = useSplitClient().client;
const [listenersAdded, setListenersAdded] = useState(false)
const {t} = useTranslation();
// Handle The Beta Switch.
useEffect(() => {
handleBeta();
}, [])
useEffect(() => {
if (!navigator.onLine) {
@@ -60,15 +63,28 @@ export function App({
//const b = Grid.useBreakpoint();
// console.log("Breakpoints:", b);
const {t} = useTranslation();
window.addEventListener("offline", function (e) {
const offlineListener = (e) => {
setOnline(false);
});
}
window.addEventListener("online", function (e) {
const onlineListener = (e) => {
setOnline(true);
});
}
// Associate event listeners, memoize to prevent multiple listeners being added
useEffect(() => {
if (!listenersAdded) {
console.log('Added events for offline and online');
window.addEventListener("offline", offlineListener);
window.addEventListener("online", onlineListener);
setListenersAdded(true);
}
return () => {
window.removeEventListener("offline", offlineListener);
window.removeEventListener("online", onlineListener);
}
}, [listenersAdded]);
useEffect(() => {
if (currentUser.authorized && bodyshop) {

View File

@@ -4,20 +4,11 @@ import { Button, notification, Space } from "antd";
import axios from "axios";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { messaging, requestForToken } from "../../firebase/firebase.utils";
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import FcmHandler from "../../utils/fcm-handler";
import ChatPopupComponent from "../chat-popup/chat-popup.component";
import "./chat-affix.styles.scss";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
chatVisible: selectChatVisible,
});
export function ChatAffixContainer({ bodyshop, chatVisible }) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -36,35 +27,34 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
console.log("FCM Topic Subscription", r.data);
} catch (error) {
console.log(
"Error attempting to subscribe to messaging topic: ",
error
"Error attempting to subscribe to messaging topic: ",
error
);
notification.open({
type: "warning",
message: t("general.errors.fcm"),
btn: (
<Space>
<Button
onClick={async () => {
await requestForToken();
SubscribeToTopic();
}}
>
{t("general.actions.tryagain")}
</Button>
<Button
onClick={() => {
const win = window.open(
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
"_blank"
);
win.focus();
}}
>
{t("general.labels.help")}
</Button>
</Space>
<Space>
<Button
onClick={async () => {
await requestForToken();
SubscribeToTopic();
}}
>
{t("general.actions.tryagain")}
</Button>
<Button
onClick={() => {
const win = window.open(
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
"_blank"
);
win.focus();
}}
>
{t("general.labels.help")}
</Button>
</Space>
),
});
}
@@ -81,16 +71,16 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
payload: (payload && payload.data && payload.data.data) || payload.data,
});
}
let stopMessageListenr, channel;
let stopMessageListener, channel;
try {
stopMessageListenr = onMessage(messaging, handleMessage);
stopMessageListener = onMessage(messaging, handleMessage);
channel = new BroadcastChannel("imex-sw-messages");
channel.addEventListener("message", handleMessage);
} catch (error) {
console.log("Unable to set event listeners.");
}
return () => {
stopMessageListenr && stopMessageListenr();
stopMessageListener && stopMessageListener();
channel && channel.removeEventListener("message", handleMessage);
};
}, [client]);
@@ -98,9 +88,10 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
return (
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
</div>
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
</div>
);
}
export default connect(mapStateToProps, null)(ChatAffixContainer);
export default ChatAffixContainer;

View File

@@ -12,7 +12,7 @@ import Icon, {
FileAddOutlined,
FileFilled,
HomeFilled,
ImportOutlined,
ImportOutlined, InfoCircleOutlined,
LineChartOutlined,
PaperClipOutlined,
PhoneOutlined,
@@ -25,8 +25,8 @@ import Icon, {
UserOutlined,
} from "@ant-design/icons";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
import {Layout, Menu} from "antd";
import React from "react";
import {Layout, Menu, Switch, Tooltip} from "antd";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {BsKanban} from "react-icons/bs";
import {FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar,} from "react-icons/fa";
@@ -41,6 +41,7 @@ import {setModalContext} from "../../redux/modals/modals.actions";
import {signOutStart} from "../../redux/user/user.actions";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import {FiLogOut} from "react-icons/fi";
import handleBeta, {checkBeta, setBeta} from "../../utils/betaHandler";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -70,9 +71,21 @@ function Header({handleMenuClick, currentUser, bodyshop, selectedHeader, signOut
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
splitKey: bodyshop && bodyshop.imexshopid,
});
const [betaSwitch, setBetaSwitch] = useState(false);
const {t} = useTranslation();
useEffect(() => {
const isBeta = checkBeta();
setBetaSwitch(isBeta );
}, []);
const betaSwitchChange = (checked) => {
setBeta(checked);
setBetaSwitch(checked);
handleBeta();
}
const accountingChildren = [
{
key: 'bills',
@@ -451,13 +464,28 @@ function Header({handleMenuClick, currentUser, bodyshop, selectedHeader, signOut
})),
}
]
];
menuItems.push({
key: 'beta-switch',
style: { marginLeft: 'auto' },
label: (
<Tooltip title="A faster more modern ImEX Online is ready for you to try! You can switch back at any time.">
<InfoCircleOutlined />
<span style={{marginRight: 8}}>Try the new ImEX Online</span>
<Switch
checked={betaSwitch}
onChange={betaSwitchChange}
/>
</Tooltip>
)
});
return (
<Layout.Header>
<Menu
mode="horizontal"
//theme="light"
theme={"dark"}
selectedKeys={[selectedHeader]}
onClick={handleMenuClick}

View File

@@ -1,6 +1,6 @@
import {FloatButton, Layout} from "antd";
import preval from "preval.macro";
import React, {lazy, Suspense, useEffect} from "react";
import React, {lazy, Suspense, useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link, Route, Routes} from "react-router-dom";
@@ -177,6 +177,8 @@ const mapStateToProps = createStructuredSelector({
export function Manage({conflict, bodyshop}) {
const {t} = useTranslation();
const [chatVisible, setChatVisible] = useState(false);
useEffect(() => {
const widgetId = "IABVNO4scRKY11XBQkNr";
@@ -360,7 +362,7 @@ export function Manage({conflict, bodyshop}) {
return (
<>
<ChatAffixContainer/>
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
<Layout className="layout-container">
<UpdateAlert/>
<HeaderContainer/>

View File

@@ -42,9 +42,7 @@ if (process.env.NODE_ENV === "development") {
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
serializableCheck: false,
}).concat(middlewares),
// middleware: middlewares,
devTools: process.env.NODE_ENV !== 'production',

View File

@@ -0,0 +1,32 @@
export const BETA_KEY = 'betaSwitchImex';
export const checkBeta = () => document.cookie.split('; ').find(row => row.startsWith(BETA_KEY)).split('=')[1] === 'true';
export const setBeta = (value) => {
const domain = window.location.hostname.split('.').slice(-2).join('.');
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
}
export const handleBeta = () => {
// If the current host name does not start with beta or test, then we don't need to do anything.
if (window.location.hostname.startsWith('localhost')) {
console.log('Not on beta or test, so no need to handle beta.');
return;
}
const isBeta = checkBeta();
const currentHostName = window.location.hostname;
// Beta is enabled, but the current host name does start with beta.
if (isBeta && !currentHostName.startsWith('beta')) {
window.location.href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
}
// Beta is not enabled, but the current host name does start with beta.
else if (!isBeta && currentHostName.startsWith('beta')) {
window.location.href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`;
}
}
export default handleBeta;

View File

@@ -1,129 +1,128 @@
import { useEffect, useCallback, useReducer } from "react";
import {useCallback, useEffect, useReducer, useState} from "react";
//Based on https://www.fullstacklabs.co/blog/keyboard-shortcuts-with-react-hooks
const blacklistedTargets = []; // ["INPUT", "TEXTAREA"];
export const useKeyboardSaveShortcut = (callback) =>
useKeyboardShortcut(["Control", "S"], callback, { overrideSystem: true });
useKeyboardShortcut(["Control", "S"], callback, {overrideSystem: true});
const keysReducer = (state, action) => {
switch (action.type) {
case "set-key-down":
const keydownState = { ...state, [action.key]: true };
return keydownState;
case "set-key-up":
const keyUpState = { ...state, [action.key]: false };
return keyUpState;
case "reset-keys":
const resetState = { ...action.data };
return resetState;
default:
return state;
}
switch (action.type) {
case "set-key-down":
const keydownState = {...state, [action.key]: true};
return keydownState;
case "set-key-up":
const keyUpState = {...state, [action.key]: false};
return keyUpState;
case "reset-keys":
const resetState = {...action.data};
return resetState;
default:
return state;
}
};
const useKeyboardShortcut = (shortcutKeys, callback, options) => {
if (!Array.isArray(shortcutKeys))
throw new Error(
"The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings."
if (!Array.isArray(shortcutKeys))
throw new Error(
"The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings."
);
if (!shortcutKeys.length)
throw new Error(
"The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string."
);
if (!callback || typeof callback !== "function")
throw new Error(
"The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed."
);
const {overrideSystem} = options || {};
const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => {
currentKeys[key.toLowerCase()] = false;
return currentKeys;
}, {});
const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping);
const [listenersAdded, setListenersAdded] = useState(false);
const keydownListener = useCallback(
(assignedKey) => (keydownEvent) => {
const loweredKey = assignedKey.toLowerCase();
if (keydownEvent.repeat) return;
if (blacklistedTargets.includes(keydownEvent.target.tagName)) return;
if (loweredKey !== keydownEvent.key.toLowerCase()) return;
if (keys[loweredKey] === undefined) return;
if (overrideSystem) {
keydownEvent.preventDefault();
disabledEventPropagation(keydownEvent);
}
setKeys({type: "set-key-down", key: loweredKey});
return false;
},
[keys, overrideSystem]
);
if (!shortcutKeys.length)
throw new Error(
"The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string."
const keyupListener = useCallback(
(assignedKey) => (keyupEvent) => {
const raisedKey = assignedKey.toLowerCase();
if (blacklistedTargets.includes(keyupEvent.target.tagName)) return;
if (keyupEvent.key.toLowerCase() !== raisedKey) return;
if (keys[raisedKey] === undefined) return;
if (overrideSystem) {
keyupEvent.preventDefault();
disabledEventPropagation(keyupEvent);
}
setKeys({type: "set-key-up", key: raisedKey});
return false;
},
[keys, overrideSystem]
);
if (!callback || typeof callback !== "function")
throw new Error(
"The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed."
);
useEffect(() => {
if (!Object.values(keys).filter((value) => !value).length) {
callback(keys);
setKeys({type: "reset-keys", data: initalKeyMapping});
} else {
setKeys({type: null});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [callback, keys]);
const { overrideSystem } = options || {};
const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => {
currentKeys[key.toLowerCase()] = false;
return currentKeys;
}, {});
useEffect(() => {
if (!listenersAdded) {
console.log('Added events for keyup and keydown');
shortcutKeys.forEach((k) => {
window.addEventListener("keydown", keydownListener(k));
window.addEventListener("keyup", keyupListener(k))
});
}
const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping);
setListenersAdded(true);
const keydownListener = useCallback(
(assignedKey) => (keydownEvent) => {
const loweredKey = assignedKey.toLowerCase();
return () => {
shortcutKeys.forEach((k) => {
window.removeEventListener("keydown", keydownListener(k));
window.removeEventListener("keyup", keyupListener(k));
});
}
}, [listenersAdded]);
if (keydownEvent.repeat) return;
if (blacklistedTargets.includes(keydownEvent.target.tagName)) return;
if (loweredKey !== keydownEvent.key.toLowerCase()) return;
if (keys[loweredKey] === undefined) return;
if (overrideSystem) {
keydownEvent.preventDefault();
disabledEventPropagation(keydownEvent);
}
setKeys({ type: "set-key-down", key: loweredKey });
return false;
},
[keys, overrideSystem]
);
const keyupListener = useCallback(
(assignedKey) => (keyupEvent) => {
const raisedKey = assignedKey.toLowerCase();
if (blacklistedTargets.includes(keyupEvent.target.tagName)) return;
if (keyupEvent.key.toLowerCase() !== raisedKey) return;
if (keys[raisedKey] === undefined) return;
if (overrideSystem) {
keyupEvent.preventDefault();
disabledEventPropagation(keyupEvent);
}
setKeys({ type: "set-key-up", key: raisedKey });
return false;
},
[keys, overrideSystem]
);
useEffect(() => {
if (!Object.values(keys).filter((value) => !value).length) {
callback(keys);
setKeys({ type: "reset-keys", data: initalKeyMapping });
} else {
setKeys({ type: null });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [callback, keys]);
useEffect(() => {
shortcutKeys.forEach((k) =>
window.addEventListener("keydown", keydownListener(k))
);
return () =>
shortcutKeys.forEach((k) =>
window.removeEventListener("keydown", keydownListener(k))
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
shortcutKeys.forEach((k) =>
window.addEventListener("keyup", keyupListener(k))
);
return () =>
shortcutKeys.forEach((k) =>
window.removeEventListener("keyup", keyupListener(k))
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};
export default useKeyboardShortcut;
function disabledEventPropagation(e) {
if (e) {
if (e.stopPropagation) {
e.stopPropagation();
} else if (window.event) {
window.event.cancelBubble = true;
if (e) {
if (e.stopPropagation) {
e.stopPropagation();
} else if (window.event) {
window.event.cancelBubble = true;
}
}
}
}