feature/IO-3499-React-19: Bug Fixes / Checkpoint

This commit is contained in:
Dave
2026-01-14 00:33:44 -05:00
parent 53d556a621
commit 36fd077bab
3 changed files with 317 additions and 256 deletions

View File

@@ -17,4 +17,5 @@ TEST_PASSWORD="test123"
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
#VITE_ENABLE_COMPILER_IN_DEV=1

View File

@@ -46,6 +46,8 @@ const CardPaymentModalComponent = ({
const [form] = Form.useForm();
const [paymentLink, setPaymentLink] = useState();
const isMountedRef = useRef(true);
const [loading, setLoading] = useState(false);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation();
@@ -69,6 +71,16 @@ const CardPaymentModalComponent = ({
[called, loadRoAndOwnerByJobPks, refetch]
);
useEffect(() => {
return () => {
isMountedRef.current = false;
};
}, []);
const setLoadingSafe = useCallback((value) => {
if (isMountedRef.current) setLoading(value);
}, []);
// Watch form payments so we can query jobs when all jobids are filled (without side effects during render)
const payments = Form.useWatch(["payments"], form);
@@ -106,45 +118,74 @@ const CardPaymentModalComponent = ({
const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions.");
const isLikelyUserCancel = (response) => {
const reason = String(response?.declinereason ?? "").toLowerCase();
// Heuristics: adjust if IntelliPay gives you a known cancel code/message
return (
reason.includes("cancel") ||
reason.includes("canceled") ||
reason.includes("closed") ||
// many gateways won't have a paymentid if user cancels before submitting
!response?.paymentid
);
};
window.intellipay.runOnClose(() => {
//window.intellipay.initialize();
// This is the path for Cancel / X
try {
// If IntelliPay uses this flag, clear it so initialize() won't reopen unexpectedly
window.intellipay.isAutoOpen = false;
} catch {
// ignore
}
// Optional: if IntelliPay needs re-init after close, uncomment:
// try { window.intellipay.initialize?.(); } catch {}
setLoadingSafe(false);
});
window.intellipay.runOnApproval(() => {
// 2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
// Add a slight delay to allow the refetch to properly get the data.
// keep your existing behavior
setTimeout(() => {
if (actions?.refetch) actions.refetch();
setLoading(false);
setLoadingSafe(false);
toggleModalVisible();
}, 750);
});
window.intellipay.runOnNonApproval(async (response) => {
// Mutate unsuccessful payment
try {
// If cancel is reported as "non-approval", don't record it as a failed payment
if (isLikelyUserCancel(response)) return;
const { payments } = form.getFieldsValue();
await insertPaymentResponse({
variables: {
paymentResponse: payments.map((payment) => ({
amount: payment.amount,
bodyshopid: bodyshop.id,
const { payments } = form.getFieldsValue();
await insertPaymentResponse({
variables: {
paymentResponse: payments.map((payment) => ({
amount: payment.amount,
bodyshopid: bodyshop.id,
jobid: payment.jobid,
declinereason: response.declinereason,
ext_paymentid: response.paymentid?.toString?.() ?? null,
successful: false,
response
}))
}
});
payments.forEach((payment) =>
insertAuditTrail({
jobid: payment.jobid,
declinereason: response.declinereason,
ext_paymentid: response.paymentid.toString(),
successful: false,
response
}))
}
});
payments.forEach((payment) =>
insertAuditTrail({
jobid: payment.jobid,
operation: AuditTrailMapping.failedpayment(),
type: "failedpayment"
})
);
operation: AuditTrailMapping.failedpayment(),
type: "failedpayment"
})
);
} finally {
// IMPORTANT: always clear loading, even on errors
setLoadingSafe(false);
}
});
};
@@ -154,7 +195,7 @@ const CardPaymentModalComponent = ({
try {
await form.validateFields();
} catch {
setLoading(false);
setLoadingSafe(false);
return;
}
@@ -185,7 +226,7 @@ const CardPaymentModalComponent = ({
document.documentElement.appendChild(node);
pollForIntelliPay(() => {
SetIntellipayCallbackFunctions();
// eslint-disable-next-line react-compiler/react-compiler
window.intellipay.isAutoOpen = true;
window.intellipay.initialize();
});
@@ -194,7 +235,7 @@ const CardPaymentModalComponent = ({
notification.error({
title: t("job_payments.notifications.error.openingip")
});
setLoading(false);
setLoadingSafe(false);
}
};
@@ -204,7 +245,7 @@ const CardPaymentModalComponent = ({
try {
await form.validateFields();
} catch {
setLoading(false);
setLoadingSafe(false);
return;
}
@@ -227,12 +268,12 @@ const CardPaymentModalComponent = ({
await navigator.clipboard.writeText(response.data.shorUrl);
message.success(t("general.actions.copied"));
}
setLoading(false);
setLoadingSafe(false);
} catch {
notification.error({
title: t("job_payments.notifications.error.openingip")
});
setLoading(false);
setLoadingSafe(false);
}
};

View File

@@ -37,239 +37,258 @@ const getFormattedTimestamp = () =>
export const logger = createLogger("info", { allowClearScreen: false });
export default defineConfig({
base: "/",
plugins: [
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
// Read HTTPS certs once (used by both server + preview)
const httpsCerts = {
key: await fsPromises.readFile("../certs/key.pem"),
cert: await fsPromises.readFile("../certs/cert.pem")
};
// PWA only for production builds (faster dev)
VitePWA({
apply: "build",
injectRegister: "auto",
registerType: "prompt",
workbox: {
navigateFallbackDenylist: [/^\/api\//] // prevent caching API routes
},
manifest: {
short_name: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "ImEX Online",
rome: "Rome Online"
}),
name: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "ImEX Online",
rome: "Rome Online"
}),
description: "The ultimate bodyshop management system.",
icons: [
{
src: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "favicon.png",
rome: "ro-favicon.png"
}),
sizes: "64x64 32x32 24x24 16x16",
type: "image/x-icon"
},
{
src: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "logo192.png",
rome: "logo192.png"
}),
type: "image/png",
sizes: "192x192"
},
{
src: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "logo512.png",
rome: "ro-favicon.png"
}),
type: "image/png",
sizes: "512x512"
}
],
theme_color: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "#1890ff",
rome: "#fff"
}),
background_color: "#fff",
gcm_sender_id: "103953800507"
}
}),
export default defineConfig(({ command, mode }) => {
// Only enable React Compiler on build in production/test (keeps dev as fast as possible)
const isBuild = command === "build";
const isTestBuild =
mode === "test" || process.env.VITE_APP_IS_TEST === "true" || process.env.VITE_APP_IS_TEST === "1";
const enableReactCompiler =
process.env.VITE_ENABLE_COMPILER_IN_DEV || (isBuild && (mode === "production" || isTestBuild));
react({
babel: {
plugins: [
['babel-plugin-react-compiler', {
// Exclude third-party drag-and-drop library from compilation
sources: (filename) => {
return !filename.includes('trello-board/dnd');
console.log(enableReactCompiler ? "React Compiler enabled" : "React Compiler disabled");
return {
base: "/",
plugins: [
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
// PWA only for production builds (faster dev)
VitePWA({
apply: "build",
injectRegister: "auto",
registerType: "prompt",
workbox: {
navigateFallbackDenylist: [/^\/api\//] // prevent caching API routes
},
manifest: {
short_name: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "ImEX Online",
rome: "Rome Online"
}),
name: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "ImEX Online",
rome: "Rome Online"
}),
description: "The ultimate bodyshop management system.",
icons: [
{
src: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "favicon.png",
rome: "ro-favicon.png"
}),
sizes: "64x64 32x32 24x24 16x16",
type: "image/x-icon"
},
{
src: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "logo192.png",
rome: "logo192.png"
}),
type: "image/png",
sizes: "192x192"
},
{
src: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "logo512.png",
rome: "ro-favicon.png"
}),
type: "image/png",
sizes: "512x512"
}
}]
]
}
}),
eslint(),
// Sentry only for production builds (no dev overhead)
sentryVitePlugin({
apply: "build",
org: "imex",
reactComponentAnnotation: { enabled: true },
release: {
name: `${process.env.VITE_APP_IS_TEST ? "test" : "production"}-${currentDatePST}-${commitHash}`.trim()
},
project: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "imexonline",
rome: "rome-online"
})
})
],
define: {
APP_VERSION: JSON.stringify(process.env.npm_package_version),
__COMMIT_HASH__: JSON.stringify(commitHash)
},
server: {
host: true,
port: 3000,
open: true,
proxy: {
"/ws": {
target: "ws://localhost:4000",
secure: false,
ws: true
},
"/wss": {
target: "ws://localhost:4000",
secure: false,
ws: true
},
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
secure: false,
ws: false,
rewrite: (path) => {
const replacedValue = path.replace(/^\/api/, "");
logger.info(
`${chalk.grey.bold(getFormattedTimestamp())} ${chalk.cyan.bold("[vite]")} ${chalk.green.bold("[API]")} ${chalk.blue(replacedValue)}`
);
return replacedValue;
}
}
},
https: {
key: await fsPromises.readFile("../certs/key.pem"),
cert: await fsPromises.readFile("../certs/cert.pem")
}
},
preview: {
port: 6000,
host: true,
open: true,
https: {
key: await fsPromises.readFile("../certs/key.pem"),
cert: await fsPromises.readFile("../certs/cert.pem")
},
proxy: {
"/ws": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
"/wss": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
secure: false,
ws: false
}
}
},
build: {
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
antd: ["antd"],
"react-redux": ["react-redux"],
redux: ["redux"],
lodash: ["lodash"],
"@sentry/react": ["@sentry/react"],
"@splitsoftware/splitio-react": ["@splitsoftware/splitio-react"],
logrocket: ["logrocket"],
firebase: [
"@firebase/analytics",
"@firebase/app",
"@firebase/firestore",
"@firebase/auth",
"@firebase/messaging"
],
markerjs2: ["markerjs2"],
"@apollo/client": ["@apollo/client"],
"libphonenumber-js": ["libphonenumber-js"],
recharts: ["recharts"]
theme_color: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "#1890ff",
rome: "#fff"
}),
background_color: "#fff",
gcm_sender_id: "103953800507"
}
}),
react(
enableReactCompiler
? {
babel: {
plugins: [
[
"babel-plugin-react-compiler",
{
// Exclude third-party drag-and-drop library from compilation
sources: (filename) => {
return !filename.includes("trello-board/dnd");
}
}
]
]
}
}
: undefined
),
eslint(),
// Sentry only for production builds (no dev overhead)
sentryVitePlugin({
apply: "build",
org: "imex",
reactComponentAnnotation: { enabled: true },
release: {
name: `${process.env.VITE_APP_IS_TEST ? "test" : "production"}-${currentDatePST}-${commitHash}`.trim()
},
project: InstanceRenderManager({
instance: process.env.VITE_APP_INSTANCE,
imex: "imexonline",
rome: "rome-online"
})
})
],
define: {
APP_VERSION: JSON.stringify(process.env.npm_package_version),
__COMMIT_HASH__: JSON.stringify(commitHash)
},
server: {
host: true,
port: 3000,
open: true,
proxy: {
"/ws": {
target: "ws://localhost:4000",
secure: false,
ws: true
},
"/wss": {
target: "ws://localhost:4000",
secure: false,
ws: true
},
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
secure: false,
ws: false,
rewrite: (path) => {
const replacedValue = path.replace(/^\/api/, "");
logger.info(
`${chalk.grey.bold(getFormattedTimestamp())} ${chalk.cyan.bold("[vite]")} ${chalk.green.bold("[API]")} ${chalk.blue(replacedValue)}`
);
return replacedValue;
}
}
},
https: httpsCerts
},
preview: {
port: 6000,
host: true,
open: true,
https: httpsCerts,
proxy: {
"/ws": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
"/wss": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
secure: false,
ws: false
}
}
},
cssMinify: "lightningcss"
},
build: {
sourcemap: true,
// Strip console/debugger in prod to shrink bundles
esbuild: {
//drop: ["console", "debugger"]
},
rollupOptions: {
output: {
manualChunks: {
antd: ["antd"],
"react-redux": ["react-redux"],
redux: ["redux"],
lodash: ["lodash"],
"@sentry/react": ["@sentry/react"],
"@splitsoftware/splitio-react": ["@splitsoftware/splitio-react"],
logrocket: ["logrocket"],
firebase: [
"@firebase/analytics",
"@firebase/app",
"@firebase/firestore",
"@firebase/auth",
"@firebase/messaging"
],
markerjs2: ["markerjs2"],
"@apollo/client": ["@apollo/client"],
"libphonenumber-js": ["libphonenumber-js"],
recharts: ["recharts"]
}
}
},
optimizeDeps: {
include: [
"react",
"react-dom",
"antd",
"lodash",
"@sentry/react",
"@apollo/client",
"@reduxjs/toolkit",
"axios",
"react-router-dom",
"dayjs",
"redux",
"react-redux",
"@firebase/app",
"@firebase/analytics",
"@firebase/firestore",
"@firebase/auth",
"@firebase/messaging",
"@firebase/util"
],
esbuildOptions: {
loader: { ".jsx": "jsx", ".tsx": "tsx" }
}
},
css: {
transformer: "lightningcss",
lightningcss: {
targets: lightningCssTargets
cssMinify: "lightningcss"
},
preprocessorOptions: {
scss: { quietDeps: true }
// Strip console/debugger in prod to shrink bundles
esbuild: {
//drop: ["console", "debugger"]
},
optimizeDeps: {
include: [
"react",
"react-dom",
"antd",
"lodash",
"@sentry/react",
"@apollo/client",
"@reduxjs/toolkit",
"axios",
"react-router-dom",
"dayjs",
"redux",
"react-redux",
"@firebase/app",
"@firebase/analytics",
"@firebase/firestore",
"@firebase/auth",
"@firebase/messaging",
"@firebase/util"
],
esbuildOptions: {
loader: { ".jsx": "jsx", ".tsx": "tsx" }
}
},
css: {
transformer: "lightningcss",
lightningcss: {
targets: lightningCssTargets
},
preprocessorOptions: {
scss: { quietDeps: true }
}
}
}
};
});