Skip to content

Instantly share code, notes, and snippets.

@jay-babu
Created February 13, 2024 04:22
Show Gist options
  • Save jay-babu/feb3c93d34c50d67963c74c1afccc962 to your computer and use it in GitHub Desktop.
Save jay-babu/feb3c93d34c50d67963c74c1afccc962 to your computer and use it in GitHub Desktop.
diff --git a/.github/ISSUE_TEMPLATE/pull_request_template.md b/.github/ISSUE_TEMPLATE/pull_request_template.md
new file mode 100644
index 0000000..a4210a8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/pull_request_template.md
@@ -0,0 +1,32 @@
+### All Submissions:
+
+- [ ] Have you added authorization guards based on permission levels?
+- [ ] Is developer-facing telemtry added?
+
+### New Feature Submissions:
+
+- [ ] Describe how you tested this feature
+- [ ] Have you written tests?
+- [ ] Are customer-exposed audit logs added?
+
+### Changes to Core Features:
+
+- [ ] Have you written new tests for your core changes, as applicable?
+- [ ] Have you successfully ran tests with your changes locally?
+- [ ] Does this feature have any impact on transactions or the reporting of transactions?
+- [ ] If so, have you tested the following?
+ - [ ] Add item via barcode
+ - [ ] Add item via search
+ - [ ] Add item with qty multiplier
+ - [ ] Add item with keyboard hot keys
+ - [ ] Add item that does not exist and see that the modal cannot be closed
+ - [ ] Add item with parent item and verfify correct qty changing
+ - [ ] Add variant item
+ - [ ] Add tax except item
+ - [ ] Add dicounted item (during sale)
+ - [ ] Add promotional item
+ - [ ] Add multiple promotional items
+ - [ ] Add items from holds
+ - [ ] Checkout with credit
+ - [ ] Checkout with mutiple credit cards
+
diff --git a/POSServiceModel b/POSServiceModel
index 38b2d15..af64a57 160000
--- a/POSServiceModel
+++ b/POSServiceModel
@@ -1 +1 @@
-Subproject commit 38b2d151cc6ddbbbaffa73403d818050d352f5ff
+Subproject commit af64a571f142ca2c2a33a1f31d520e4ca6d68e05
diff --git a/package-lock.json b/package-lock.json
index e9c87c2..6259aeb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -42,12 +42,13 @@
"react-apexcharts": "^1.4.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.47.0",
+ "react-hook-form-chakra": "^1.0.2",
"react-icons": "^4.12.0",
"react-infinite-scroll-component": "^6.1.0",
"react-json-view-lite": "^1.2.1",
"react-router-dom": "^6.14.0",
"react-scripts": "5.0.1",
- "swagger-typescript-api": "^12.0.4",
+ "swagger-typescript-api": "^13.0.3",
"typescript": "^4.9.5",
"use-debounce": "^10.0.0",
"use-query-params": "^2.2.1",
@@ -10233,6 +10234,17 @@
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
"integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA=="
},
+ "node_modules/@sindresorhus/is": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.1.2.tgz",
+ "integrity": "sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
"node_modules/@sinonjs/commons": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
@@ -10250,30 +10262,41 @@
}
},
"node_modules/@stoplight/http-spec": {
- "version": "5.9.6",
- "resolved": "https://registry.npmjs.org/@stoplight/http-spec/-/http-spec-5.9.6.tgz",
- "integrity": "sha512-3BSNYLwUw/O8wXAeLalyNC6tMeDP7OffX3jiLBzxNKTqGiQJAbnRWdD6wcDqL2EtZLt6FBamHTI5vw9lNvUbew==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@stoplight/http-spec/-/http-spec-7.0.2.tgz",
+ "integrity": "sha512-4DvT0w5goAhLxVbHfdzkMqGcTdi9bU4LmBrYNrZBOCFV4JPAHRERSBdI7F7n/MfgVvzxWb3Vftrh6pCgTd/+Jg==",
"dev": true,
"dependencies": {
"@stoplight/json": "^3.18.1",
"@stoplight/json-schema-generator": "1.0.2",
- "@stoplight/types": "^13.15.0",
+ "@stoplight/types": "14.1.0",
"@types/json-schema": "7.0.11",
"@types/swagger-schema-official": "~2.0.22",
"@types/type-is": "^1.6.3",
"fnv-plus": "^1.3.1",
- "lodash.isequalwith": "^4.4.0",
- "lodash.pick": "^4.4.0",
- "lodash.pickby": "^4.6.0",
+ "lodash": "^4.17.21",
"openapi3-ts": "^2.0.2",
"postman-collection": "^4.1.2",
- "tslib": "^2.3.1",
+ "tslib": "^2.6.2",
"type-is": "^1.6.18"
},
"engines": {
"node": ">=14.13"
}
},
+ "node_modules/@stoplight/http-spec/node_modules/@stoplight/types": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-14.1.0.tgz",
+ "integrity": "sha512-fL8Nzw03+diALw91xHEHA5Q0WCGeW9WpPgZQjodNUWogAgJ56aJs03P9YzsQ1J6fT7/XjDqHMgn7/RlsBzB/SQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.4",
+ "utility-types": "^3.10.0"
+ },
+ "engines": {
+ "node": "^12.20 || >=14.13"
+ }
+ },
"node_modules/@stoplight/http-spec/node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@@ -10331,9 +10354,9 @@
}
},
"node_modules/@stoplight/json-schema-ref-parser": {
- "version": "9.2.5",
- "resolved": "https://registry.npmjs.org/@stoplight/json-schema-ref-parser/-/json-schema-ref-parser-9.2.5.tgz",
- "integrity": "sha512-7UI3pX5oyGzAdGPah001CyPnIsJZJW+38sGjvx862zXQFidBe0sxFO5MUety61Zr/RaygCQ2RU/KfD7hSfOLxg==",
+ "version": "9.2.7",
+ "resolved": "https://registry.npmjs.org/@stoplight/json-schema-ref-parser/-/json-schema-ref-parser-9.2.7.tgz",
+ "integrity": "sha512-1vNzJ7iSrFTAFNbZHPyhI6GiJJw74+WaV61bARUQEDR4Jm80f9s0Tq9uCvGoMYwIFmWDJAoTiyegnUs6SvVxDw==",
"dev": true,
"dependencies": {
"@jsdevtools/ono": "^7.1.3",
@@ -10346,19 +10369,32 @@
}
},
"node_modules/@stoplight/json-schema-sampler": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/@stoplight/json-schema-sampler/-/json-schema-sampler-0.2.2.tgz",
- "integrity": "sha512-QP4ZwXh3dEn5wHZs2361kdf4BmaKiiP+pxIImAuVTLmulv9sBTB+ETG7Y5z9u4DOUQu2GNxfUY10iSwuBQMXrg==",
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@stoplight/json-schema-sampler/-/json-schema-sampler-0.3.0.tgz",
+ "integrity": "sha512-G7QImi2xr9+8iPEg0D9YUi1BWhIiiEm19aMb91oWBSdxuhezOAqqRP3XNY6wczHV9jLWW18f+KkghTy9AG0BQA==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.7",
"json-pointer": "^0.6.1"
}
},
+ "node_modules/@stoplight/json/node_modules/@stoplight/types": {
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.20.0.tgz",
+ "integrity": "sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.4",
+ "utility-types": "^3.10.0"
+ },
+ "engines": {
+ "node": "^12.20 || >=14.13"
+ }
+ },
"node_modules/@stoplight/ordered-object-literal": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.4.tgz",
- "integrity": "sha512-OF8uib1jjDs5/cCU+iOVy+GJjU3X7vk/qJIkIJFqwmlJKrrtijFmqwbu8XToXrwTYLQTP+Hebws5gtZEmk9jag==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.5.tgz",
+ "integrity": "sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==",
"dev": true,
"engines": {
"node": ">=8"
@@ -10374,22 +10410,22 @@
}
},
"node_modules/@stoplight/prism-cli": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/@stoplight/prism-cli/-/prism-cli-5.0.1.tgz",
- "integrity": "sha512-A13olRGUOeAxWWdv2lJ7JaufRmsvdVRNCYcyOjg17CTyIkZF46I0y3mvlHtICDDBH/85GjOk3OXI7a/UkQsSDA==",
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/@stoplight/prism-cli/-/prism-cli-5.5.4.tgz",
+ "integrity": "sha512-MvJUQcd8Fb3cuJuggjWinclz/JlHBeqZd5cYJtyJYs1Cz/woXjYF+0KeDviTdUTXEcTLhFguXS8vUK9vxqZ01g==",
"dev": true,
"dependencies": {
- "@stoplight/http-spec": "^5.9.2",
+ "@stoplight/http-spec": "^7.0.2",
"@stoplight/json": "^3.18.1",
- "@stoplight/json-schema-ref-parser": "9.2.5",
- "@stoplight/prism-core": "^5.0.1",
- "@stoplight/prism-http": "^5.0.1",
- "@stoplight/prism-http-server": "^5.0.1",
- "@stoplight/types": "^13.15.0",
+ "@stoplight/json-schema-ref-parser": "9.2.7",
+ "@stoplight/prism-core": "^5.5.4",
+ "@stoplight/prism-http": "^5.5.4",
+ "@stoplight/prism-http-server": "^5.5.4",
+ "@stoplight/types": "^14.1.0",
"chalk": "^4.1.2",
"chokidar": "^3.5.2",
"fp-ts": "^2.11.5",
- "json-schema-faker": "0.5.0-rcv.40",
+ "json-schema-faker": "0.5.3",
"lodash": "^4.17.21",
"node-fetch": "^2.6.5",
"pino": "^6.13.3",
@@ -10498,9 +10534,9 @@
}
},
"node_modules/@stoplight/prism-core": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/@stoplight/prism-core/-/prism-core-5.0.1.tgz",
- "integrity": "sha512-rHdhmDrhBDg7yYipLJWlD/jRyECif5bAqDMVfobx1F6mzw+Yfc1YgXQbTgc+6oPwk8SgEr7+WQBq5jCi0ezHYw==",
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/@stoplight/prism-core/-/prism-core-5.5.4.tgz",
+ "integrity": "sha512-P/I1UD2HwP02EocTRFtjRJe3BeQbJASIGMbE1/rT5fHlYeOiMb+IerFfrTp+/AGO5a193UEDut3XBXR3dBuQcw==",
"dev": true,
"dependencies": {
"fp-ts": "^2.11.5",
@@ -10513,17 +10549,17 @@
}
},
"node_modules/@stoplight/prism-http": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/@stoplight/prism-http/-/prism-http-5.0.1.tgz",
- "integrity": "sha512-/esXjNBbXjxZPtl3WyuvkgHUH7l3eDgOwIOP26qLH10BE3jlKR7IHF1gwZgEKdtBhQKQ0/2tB4fPJSAPaU2Blg==",
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/@stoplight/prism-http/-/prism-http-5.5.4.tgz",
+ "integrity": "sha512-W7NV3W09iAbAWj4ft6tqcBC2kdXpgArWSHO15glxQwRu1Y7I/U1Nr6EUhqQwyBgF5DWB5WFW/mnj30fZuyxGzw==",
"dev": true,
"dependencies": {
"@faker-js/faker": "^6.0.0",
"@stoplight/json": "^3.18.1",
"@stoplight/json-schema-merge-allof": "0.7.8",
- "@stoplight/json-schema-sampler": "0.2.2",
- "@stoplight/prism-core": "^5.0.1",
- "@stoplight/types": "^13.15.0",
+ "@stoplight/json-schema-sampler": "0.3.0",
+ "@stoplight/prism-core": "^5.5.4",
+ "@stoplight/types": "^14.1.0",
"@stoplight/yaml": "^4.2.3",
"abstract-logging": "^2.0.1",
"accepts": "^1.3.7",
@@ -10535,27 +10571,29 @@
"fp-ts": "^2.11.5",
"http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.0",
- "json-schema-faker": "0.5.0-rcv.40",
+ "json-schema-faker": "0.5.3",
"lodash": "^4.17.21",
"node-fetch": "^2.6.5",
+ "parse-multipart-data": "^1.5.0",
"pino": "^6.13.3",
"tslib": "^2.3.1",
"type-is": "^1.6.18",
- "uri-template-lite": "^22.9.0"
+ "uri-template-lite": "^22.9.0",
+ "whatwg-mimetype": "^3.0.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@stoplight/prism-http-server": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/@stoplight/prism-http-server/-/prism-http-server-5.0.1.tgz",
- "integrity": "sha512-VeDvw35JvEluyO1h5VEcxTlYwGLrk3GILLZNOB9oRoy31kwXyPrP4mdaFoAVyqfqa+wMkXmWn/6HdPDBsZTb+w==",
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/@stoplight/prism-http-server/-/prism-http-server-5.5.4.tgz",
+ "integrity": "sha512-2k/hWfxq+P7HQkoFpB9Bb18s+JDiiIZADXIQLYvk/bx6LO/cVxoZst9kqtM7UxugYrfkemYPP9/7/z0v73+ifg==",
"dev": true,
"dependencies": {
- "@stoplight/prism-core": "^5.0.1",
- "@stoplight/prism-http": "^5.0.1",
- "@stoplight/types": "^13.15.0",
+ "@stoplight/prism-core": "^5.5.4",
+ "@stoplight/prism-http": "^5.5.4",
+ "@stoplight/types": "^14.1.0",
"fast-xml-parser": "^4.2.0",
"fp-ts": "^2.11.5",
"io-ts": "^2.2.16",
@@ -10571,9 +10609,9 @@
}
},
"node_modules/@stoplight/prism-http-server/node_modules/node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dev": true,
"dependencies": {
"whatwg-url": "^5.0.0"
@@ -10694,9 +10732,9 @@
"dev": true
},
"node_modules/@stoplight/prism-http/node_modules/node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dev": true,
"dependencies": {
"whatwg-url": "^5.0.0"
@@ -10725,10 +10763,19 @@
"node": ">=8"
}
},
+ "node_modules/@stoplight/prism-http/node_modules/whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@stoplight/types": {
- "version": "13.15.0",
- "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.15.0.tgz",
- "integrity": "sha512-pBLjVRrWGVd+KzTbL3qrmufSKIEp0UfziDBdt/nrTHPKrlrtVwaHdrrQMcpM23yJDU1Wcg4cHvhIuGtKCT5OmA==",
+ "version": "14.1.1",
+ "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-14.1.1.tgz",
+ "integrity": "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.4",
@@ -10759,6 +10806,19 @@
"integrity": "sha512-sV+51I7WYnLJnKPn2EMWgS4EUfoP4iWEbrWwbXsj0MZCB/xOK8j6+C9fntIdOM50kpx45ZLC3s6kwKivWuqvyg==",
"dev": true
},
+ "node_modules/@stoplight/yaml/node_modules/@stoplight/types": {
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.20.0.tgz",
+ "integrity": "sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.4",
+ "utility-types": "^3.10.0"
+ },
+ "engines": {
+ "node": "^12.20 || >=14.13"
+ }
+ },
"node_modules/@storybook/addon-actions": {
"version": "7.6.7",
"resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.7.tgz",
@@ -13012,14 +13072,14 @@
}
},
"node_modules/@storybook/preset-create-react-app": {
- "version": "7.6.7",
- "resolved": "https://registry.npmjs.org/@storybook/preset-create-react-app/-/preset-create-react-app-7.6.7.tgz",
- "integrity": "sha512-49m7yeyo1DiRoMqNk87UFg179C4+MYFPAy935K0WUwAlGKZ3/69ipYi8xYbtdAaBCXX5V86BI8HypEMLujWBVw==",
+ "version": "7.6.14",
+ "resolved": "https://registry.npmjs.org/@storybook/preset-create-react-app/-/preset-create-react-app-7.6.14.tgz",
+ "integrity": "sha512-XYSgBnLcLv/P+xV8QbIxgsaVF3BQwTXeEnkEjvU0Iyaiuw7HAPbFb3FRo+JSXU8QkFC3q5JTAUPtJ8KSKFEHsg==",
"dev": true,
"dependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0",
- "@storybook/types": "7.6.7",
+ "@storybook/types": "7.6.14",
"@types/babel__core": "^7.1.7",
"@types/semver": "^7.3.4",
"pnp-webpack-plugin": "^1.7.0",
@@ -13034,6 +13094,66 @@
"react-scripts": ">=5.0.0"
}
},
+ "node_modules/@storybook/preset-create-react-app/node_modules/@storybook/channels": {
+ "version": "7.6.14",
+ "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.14.tgz",
+ "integrity": "sha512-tyrnnXTh7Ca6HbtzYtZGZmbUkC+eYPdot41+YDERMxXCnejd18BnsH/pyGW66GwgY079Q7uhdDFyM63ynZrt/A==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/client-logger": "7.6.14",
+ "@storybook/core-events": "7.6.14",
+ "@storybook/global": "^5.0.0",
+ "qs": "^6.10.0",
+ "telejson": "^7.2.0",
+ "tiny-invariant": "^1.3.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/preset-create-react-app/node_modules/@storybook/client-logger": {
+ "version": "7.6.14",
+ "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.14.tgz",
+ "integrity": "sha512-rHa2hLU+80BN5E58Shf1g09YS6QEEOk5hwMuJ4WJfAypMDYPjnIsOYUboHClkCA9TDCH/iVhyRSPy83NWN2MZg==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/global": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/preset-create-react-app/node_modules/@storybook/core-events": {
+ "version": "7.6.14",
+ "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.14.tgz",
+ "integrity": "sha512-zuSMjOgju7WLFL+okTXVvOKKNzwqVGRVp5UhXeSikT4aXuVdpfepCfikkjntn12G1ybL7mfFCsBU2DV1lwwp6Q==",
+ "dev": true,
+ "dependencies": {
+ "ts-dedent": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
+ "node_modules/@storybook/preset-create-react-app/node_modules/@storybook/types": {
+ "version": "7.6.14",
+ "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.14.tgz",
+ "integrity": "sha512-sJ3qn45M2XLXlOi+wkhXK5xsXbSVzi8YGrusux//DttI3s8wCP3BQSnEgZkBiEktloxPferINHT1er8/9UK7Xw==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/channels": "7.6.14",
+ "@types/babel__core": "^7.0.0",
+ "@types/express": "^4.7.0",
+ "file-system-cache": "2.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ }
+ },
"node_modules/@storybook/preset-react-webpack": {
"version": "7.6.7",
"resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-7.6.7.tgz",
@@ -15074,9 +15194,9 @@
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
},
"node_modules/@types/type-is": {
- "version": "1.6.3",
- "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.3.tgz",
- "integrity": "sha512-PNs5wHaNcBgCQG5nAeeZ7OvosrEsI9O4W2jAOO9BCCg4ux9ZZvH2+0iSCOIDBiKuQsiNS8CBlmfX9f5YBQ22cA==",
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/@types/type-is/-/type-is-1.6.6.tgz",
+ "integrity": "sha512-fs1KHv/f9OvmTMsu4sBNaUu32oyda9Y9uK25naJG8gayxNrfqGIjPQsbLIYyfe7xFkppnPlJB+BuTldOaX9bXw==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -16322,11 +16442,11 @@
}
},
"node_modules/axios": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
- "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
+ "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"dependencies": {
- "follow-redirects": "^1.15.0",
+ "follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@@ -18258,9 +18378,9 @@
}
},
"node_modules/cross-fetch/node_modules/node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dev": true,
"dependencies": {
"whatwg-url": "^5.0.0"
@@ -19512,6 +19632,11 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
+ "node_modules/emojilib": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz",
+ "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="
+ },
"node_modules/emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
@@ -21113,9 +21238,9 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
},
"node_modules/fast-redact": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.2.0.tgz",
- "integrity": "sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz",
+ "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==",
"dev": true,
"engines": {
"node": ">=6"
@@ -21491,9 +21616,9 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
- "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
@@ -21791,9 +21916,9 @@
}
},
"node_modules/fp-ts": {
- "version": "2.16.0",
- "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.0.tgz",
- "integrity": "sha512-bLq+KgbiXdTEoT1zcARrWEpa5z6A/8b7PcDW7Gef3NSisQ+VS7ll2Xbf1E+xsgik0rWub/8u0qP/iTTjj+PhxQ==",
+ "version": "2.16.2",
+ "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.2.tgz",
+ "integrity": "sha512-CkqAjnIKFqvo3sCyoBTqgJvF+bHrSik584S9nhTjtBESLx26cbtVMR/T9a6ApChOcSDAaM3JydDmWDUn4EEXng==",
"dev": true
},
"node_modules/fraction.js": {
@@ -23004,9 +23129,9 @@
}
},
"node_modules/io-ts": {
- "version": "2.2.20",
- "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.20.tgz",
- "integrity": "sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA==",
+ "version": "2.2.21",
+ "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.21.tgz",
+ "integrity": "sha512-zz2Z69v9ZIC3mMLYWIeoUcwWD6f+O7yP92FMVVaXEOSZH1jnVBmET/urd/uoarD1WGBY4rCj8TAyMPzsGNzMFQ==",
"dev": true,
"peerDependencies": {
"fp-ts": "^2.5.0"
@@ -23615,9 +23740,9 @@
}
},
"node_modules/isomorphic-fetch/node_modules/node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dev": true,
"dependencies": {
"whatwg-url": "^5.0.0"
@@ -28218,16 +28343,16 @@
}
},
"node_modules/json-schema-faker": {
- "version": "0.5.0-rcv.40",
- "resolved": "https://registry.npmjs.org/json-schema-faker/-/json-schema-faker-0.5.0-rcv.40.tgz",
- "integrity": "sha512-BczZvu03jKrGh3ovCWrHusiX6MwiaKK2WZeyomKBNA8Nm/n7aBYz0mub1CnONB6cgxOZTNxx4afNmLblbUmZbA==",
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/json-schema-faker/-/json-schema-faker-0.5.3.tgz",
+ "integrity": "sha512-BeIrR0+YSrTbAR9dOMnjbFl1MvHyXnq+Wpdw1FpWZDHWKLzK229hZ5huyPcmzFUfVq1ODwf40WdGVoE266UBUg==",
"dev": true,
"dependencies": {
"json-schema-ref-parser": "^6.1.0",
- "jsonpath-plus": "^5.1.0"
+ "jsonpath-plus": "^7.2.0"
},
"bin": {
- "jsf": "bin/gen.js"
+ "jsf": "bin/gen.cjs"
}
},
"node_modules/json-schema-ref-parser": {
@@ -28281,12 +28406,12 @@
}
},
"node_modules/jsonpath-plus": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-5.1.0.tgz",
- "integrity": "sha512-890w2Pjtj0iswAxalRlt2kHthi6HKrXEfZcn+ZNZptv7F3rUGIeDuZo+C+h4vXBHLEsVjJrHeCm35nYeZLzSBQ==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz",
+ "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==",
"dev": true,
"engines": {
- "node": ">=10.0.0"
+ "node": ">=12.0.0"
}
},
"node_modules/jsonpointer": {
@@ -28810,12 +28935,6 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
- "node_modules/lodash.isequalwith": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz",
- "integrity": "sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ==",
- "dev": true
- },
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -28831,18 +28950,6 @@
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="
},
- "node_modules/lodash.pick": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
- "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==",
- "dev": true
- },
- "node_modules/lodash.pickby": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz",
- "integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==",
- "dev": true
- },
"node_modules/lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -30642,11 +30749,14 @@
}
},
"node_modules/node-emoji": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
- "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.0.tgz",
+ "integrity": "sha512-tcsBm9C6FmPN5Wo7OjFi9lgMyJjvkAeirmjR/ax8Ttfqy4N8PoFic26uqFTIgayHPNI5FH4ltUvfh9kHzwcK9A==",
"dependencies": {
- "lodash": "^4.17.21"
+ "@sindresorhus/is": "^3.1.2",
+ "char-regex": "^1.0.2",
+ "emojilib": "^2.4.0",
+ "skin-tone": "^2.0.0"
}
},
"node_modules/node-fetch": {
@@ -31631,6 +31741,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parse-multipart-data": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/parse-multipart-data/-/parse-multipart-data-1.5.0.tgz",
+ "integrity": "sha512-ck5zaMF0ydjGfejNMnlo5YU2oJ+pT+80Jb1y4ybanT27j+zbVP/jkYmCrUGsEln0Ox/hZmuvgy8Ra7AxbXP2Mw==",
+ "dev": true
+ },
"node_modules/parse-prefer-header": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parse-prefer-header/-/parse-prefer-header-1.0.0.tgz",
@@ -33296,9 +33412,9 @@
}
},
"node_modules/postman-collection": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.1.tgz",
- "integrity": "sha512-DFLt3/yu8+ldtOTIzmBUctoupKJBOVK4NZO0t68K2lIir9smQg7OdQTBjOXYy+PDh7u0pSDvD66tm93eBHEPHA==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.3.0.tgz",
+ "integrity": "sha512-QpmNOw1JhAVQTFWRz443/qpKs4/3T1MFrKqDZ84RS1akxOzhXXr15kD8+/+jeA877qyy9rfMsrFgLe2W7aCPjw==",
"dev": true,
"dependencies": {
"@faker-js/faker": "5.5.3",
@@ -33365,7 +33481,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
"integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
- "dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -34197,6 +34312,16 @@
"react": "^16.8.0 || ^17 || ^18"
}
},
+ "node_modules/react-hook-form-chakra": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/react-hook-form-chakra/-/react-hook-form-chakra-1.0.2.tgz",
+ "integrity": "sha512-QWoR9Sh5TXDPhPSX5mR4XZgdRXMBmH/R7iAqTTRRfOqsN3MM0oBvAHDxgPfhsJf68yrxTG3U5S1T3fsOrXeLdQ==",
+ "peerDependencies": {
+ "@chakra-ui/react": "^2",
+ "react": ">=17",
+ "react-hook-form": "^7"
+ }
+ },
"node_modules/react-icons": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz",
@@ -36910,6 +37035,17 @@
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
},
+ "node_modules/skin-tone": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
+ "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==",
+ "dependencies": {
+ "unicode-emoji-modifier-base": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -38099,24 +38235,24 @@
"integrity": "sha512-rCC0NWGKr/IJhtRuPq/t37qvZHI/mH4I4sxflVM+qgVe5Z2uOCivzWaVbuioJaB61kvm5UvB7b49E+oBY0M8jA=="
},
"node_modules/swagger-typescript-api": {
- "version": "12.0.4",
- "resolved": "https://registry.npmjs.org/swagger-typescript-api/-/swagger-typescript-api-12.0.4.tgz",
- "integrity": "sha512-04ZxlJzu3g15TupfPhS0Yk0jzV/MM23WU4uuOl2vSi4yHrxEwnkIsoBkP084ec61q4vr2FHcI3DKxC+Mt1u10Q==",
+ "version": "13.0.3",
+ "resolved": "https://registry.npmjs.org/swagger-typescript-api/-/swagger-typescript-api-13.0.3.tgz",
+ "integrity": "sha512-774ndLpGm2FNpUZpDugfoOO2pIcvSW9nlcqwLVSH9ju4YKCi1Gd83jPly7upcljOvZ8KO/edIUx+9eYViDYglg==",
"dependencies": {
"@types/swagger-schema-official": "2.0.22",
- "cosmiconfig": "7.0.1",
+ "cosmiconfig": "8.2.0",
"didyoumean": "^1.2.2",
- "eta": "^2.0.0",
+ "eta": "^2.2.0",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
- "make-dir": "3.1.0",
- "nanoid": "3.3.4",
- "node-emoji": "1.11.0",
- "node-fetch": "^3.2.10",
- "prettier": "2.7.1",
+ "make-dir": "4.0.0",
+ "nanoid": "3.3.6",
+ "node-emoji": "2.1.0",
+ "node-fetch": "^3.3.1",
+ "prettier": "3.0.0",
"swagger-schema-official": "2.0.0-bab6bed",
"swagger2openapi": "7.0.8",
- "typescript": "4.8.4"
+ "typescript": "5.1.6"
},
"bin": {
"sta": "index.js",
@@ -38129,18 +38265,20 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/swagger-typescript-api/node_modules/cosmiconfig": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
- "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz",
+ "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==",
"dependencies": {
- "@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
"parse-json": "^5.0.0",
- "path-type": "^4.0.0",
- "yaml": "^1.10.0"
+ "path-type": "^4.0.0"
},
"engines": {
- "node": ">=10"
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
}
},
"node_modules/swagger-typescript-api/node_modules/js-yaml": {
@@ -38154,41 +38292,47 @@
"js-yaml": "bin/js-yaml.js"
}
},
- "node_modules/swagger-typescript-api/node_modules/nanoid": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
+ "node_modules/swagger-typescript-api/node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dependencies": {
+ "semver": "^7.5.3"
},
"engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/swagger-typescript-api/node_modules/prettier": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
- "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
+ "node_modules/swagger-typescript-api/node_modules/nanoid": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"bin": {
- "prettier": "bin-prettier.js"
+ "nanoid": "bin/nanoid.cjs"
},
"engines": {
- "node": ">=10.13.0"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/swagger-typescript-api/node_modules/typescript": {
- "version": "4.8.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
- "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
+ "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=4.2.0"
+ "node": ">=14.17"
}
},
"node_modules/swagger2openapi": {
@@ -38863,9 +39007,9 @@
}
},
"node_modules/tslib": {
- "version": "2.5.3",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
- "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -39052,6 +39196,14 @@
"node": ">=4"
}
},
+ "node_modules/unicode-emoji-modifier-base": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz",
+ "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/unicode-match-property-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
@@ -39530,9 +39682,9 @@
"integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA=="
},
"node_modules/utility-types": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
- "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==",
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
+ "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
"dev": true,
"engines": {
"node": ">= 4"
diff --git a/package.json b/package.json
index e998f17..2524421 100644
--- a/package.json
+++ b/package.json
@@ -37,12 +37,13 @@
"react-apexcharts": "^1.4.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.47.0",
+ "react-hook-form-chakra": "^1.0.2",
"react-icons": "^4.12.0",
"react-infinite-scroll-component": "^6.1.0",
"react-json-view-lite": "^1.2.1",
"react-router-dom": "^6.14.0",
"react-scripts": "5.0.1",
- "swagger-typescript-api": "^12.0.4",
+ "swagger-typescript-api": "^13.0.3",
"typescript": "^4.9.5",
"use-debounce": "^10.0.0",
"use-query-params": "^2.2.1",
diff --git a/src/App.tsx b/src/App.tsx
index 3fea800..a55a763 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,5 @@
-import { lazy, Suspense } from "react";
+import { useColorMode } from "@chakra-ui/react";
+import { lazy, Suspense, useEffect } from "react";
import { createBrowserRouter, Outlet, RouterProvider } from "react-router-dom";
import { QueryParamProvider } from "use-query-params";
import { ReactRouter6Adapter } from "use-query-params/adapters/react-router-6";
@@ -395,6 +396,14 @@ const router = createBrowserRouter([
]);
export const App = () => {
+ const colorMode = useColorMode();
+ useEffect(() => {
+ if (colorMode.colorMode === "dark") {
+ document.body.classList.add("dark");
+ } else {
+ document.body.classList.remove("dark");
+ }
+ }, []);
return (
<AuthContextProvider>
<AuthorizationContextProvider>
diff --git a/src/components/Auth/AuthenticationCard/AuthenticationEmailSignInForm.tsx b/src/components/Auth/AuthenticationCard/AuthenticationEmailSignInForm.tsx
index 2bead40..183531b 100644
--- a/src/components/Auth/AuthenticationCard/AuthenticationEmailSignInForm.tsx
+++ b/src/components/Auth/AuthenticationCard/AuthenticationEmailSignInForm.tsx
@@ -40,6 +40,7 @@ export const AuthenticationEmailSignInForm: React.FC<
<FormLabel htmlFor="email">Email</FormLabel>
<Input
id="email"
+ name="email"
type="email"
onChange={(event) => {
setEmail(event.target.value);
diff --git a/src/components/Auth/AuthorizationFlowForms/SelectEmployeeForm.tsx b/src/components/Auth/AuthorizationFlowForms/SelectEmployeeForm.tsx
index 3c4de64..8914e90 100644
--- a/src/components/Auth/AuthorizationFlowForms/SelectEmployeeForm.tsx
+++ b/src/components/Auth/AuthorizationFlowForms/SelectEmployeeForm.tsx
@@ -1,12 +1,20 @@
import { ArrowBackIcon } from "@chakra-ui/icons";
-import { Button, ButtonGroup, Progress, Text, VStack } from "@chakra-ui/react";
+import {
+ Button,
+ ButtonGroup,
+ Center,
+ Spinner,
+ Text,
+ VStack,
+} from "@chakra-ui/react";
+import * as Sentry from "@sentry/react";
import { motion } from "framer-motion";
import { useCallback, useEffect, useState } from "react";
import { BiRefresh } from "react-icons/bi";
import { useLocalStorage } from "usehooks-ts";
import { useEntityApi } from "../../../config/ItemsApi";
import { useAuthorization } from "../../../context/AuthorizationContext/AuthorizationContext";
-import { EntityDTO, UserDTO } from "../../../model/data-contracts";
+import { EntityDTO, Role, UserDTO } from "../../../model/data-contracts";
import { SelectableList } from "../../common/SelectableList.tsx/SelectableList";
import { AvatarWithName } from "../AvatarWithName";
@@ -33,11 +41,14 @@ export const SelectEmployeeForm: React.FC<SelectEmployeeListProps> = ({
if (!entity) return;
setIsLoading(true);
try {
- const response = await entityApi?.getUsersByEntity(entity?.id);
+ const response = await entityApi?.getUsersByEntity(entity?.id, {
+ roles: [Role.OWNER, Role.ADMIN, Role.CASHIER, Role.MANAGER],
+ });
if (response) {
return response.data;
}
} catch (error) {
+ Sentry.captureException(error);
console.log(error);
} finally {
setIsLoading(false);
@@ -69,7 +80,9 @@ export const SelectEmployeeForm: React.FC<SelectEmployeeListProps> = ({
key={"selectEmployeeForm"}
>
{isLoading ? (
- <Progress isIndeterminate />
+ <Center>
+ <Spinner />
+ </Center>
) : (
<SelectableList
items={users.filter(
diff --git a/src/components/Auth/AuthorizationFlowForms/StoreCard.tsx b/src/components/Auth/AuthorizationFlowForms/StoreCard.tsx
index 258cec0..c606f89 100644
--- a/src/components/Auth/AuthorizationFlowForms/StoreCard.tsx
+++ b/src/components/Auth/AuthorizationFlowForms/StoreCard.tsx
@@ -7,7 +7,11 @@ export type StoreCardProps = {
export const StoreCard = ({ entity }: StoreCardProps) => {
return (
- <Card backgroundColor={"gray.50"} variant={"outline"}>
+ <Card
+ data-testid={`store-card-${entity.id}`}
+ backgroundColor={"gray.50"}
+ variant={"outline"}
+ >
<CardHeader>
<Heading size={"md"}>{entity.name}</Heading>
<Text>{entity.address}</Text>
diff --git a/src/components/Auth/AvatarWithName.tsx b/src/components/Auth/AvatarWithName.tsx
index 2093517..856c616 100644
--- a/src/components/Auth/AvatarWithName.tsx
+++ b/src/components/Auth/AvatarWithName.tsx
@@ -31,7 +31,7 @@ export const AvatarWithName: React.FC<AvatarWithNameProps> = ({
/>
<Divider orientation="vertical" />
<Heading size={size}>
- {user.firstName} {user.lastName}
+ {user.firstName ? `${user.firstName} ${user.lastName}` : user.email}
</Heading>
</HStack>
);
diff --git a/src/components/Auth/OverrideButton.tsx b/src/components/Auth/OverrideButton.tsx
new file mode 100644
index 0000000..8c1d999
--- /dev/null
+++ b/src/components/Auth/OverrideButton.tsx
@@ -0,0 +1,19 @@
+import { Button } from "@chakra-ui/react";
+import { useEmployeeOverride } from "../../context/AuthorizationContext/EmployeeOverrideHandler";
+
+export type OverrideButtonProps = {
+ children?: React.ReactNode;
+};
+
+export const OverrideButton: React.FC<OverrideButtonProps> = ({
+ children,
+ ...props
+}) => {
+ const { requestOverride } = useEmployeeOverride();
+
+ return (
+ <Button colorScheme="red" onClick={() => requestOverride()} {...props}>
+ {children ?? "Override"}
+ </Button>
+ );
+};
diff --git a/src/components/Auth/PermissionedButton.tsx b/src/components/Auth/PermissionedButton.tsx
index a903eb9..18cdfa5 100644
--- a/src/components/Auth/PermissionedButton.tsx
+++ b/src/components/Auth/PermissionedButton.tsx
@@ -8,7 +8,16 @@ export type PermissionedButtonProps = {
} & ButtonProps;
export const PermissionedButton = forwardRef(
- ({ requires, allowOverride, ...props }: PermissionedButtonProps, ref) => {
+ (
+ {
+ requires,
+ allowOverride,
+ isDisabled,
+ children,
+ ...props
+ }: PermissionedButtonProps,
+ ref,
+ ) => {
const { hasPermission } = usePermissions();
const isAllowed = Boolean(!requires || hasPermission(requires));
@@ -20,12 +29,8 @@ export const PermissionedButton = forwardRef(
}
isDisabled={isAllowed}
>
- <Button
- {...props}
- isDisabled={!isAllowed || props.isDisabled}
- ref={ref}
- >
- {props.children}
+ <Button {...props} isDisabled={!isAllowed || isDisabled} ref={ref}>
+ {children}
</Button>
</Tooltip>
);
diff --git a/src/components/Auth/PermissionedIconButton.tsx b/src/components/Auth/PermissionedIconButton.tsx
new file mode 100644
index 0000000..6c9e114
--- /dev/null
+++ b/src/components/Auth/PermissionedIconButton.tsx
@@ -0,0 +1,38 @@
+import {
+ forwardRef,
+ IconButton,
+ IconButtonProps,
+ Tooltip,
+} from "@chakra-ui/react";
+import { usePermissions } from "../../context/PermissionsContext";
+import { Permission } from "../../utils/Permission";
+
+export type PermissionedIconButtonProps = {
+ requires?: Permission | string;
+ allowOverride?: boolean;
+} & IconButtonProps;
+
+export const PermissionedIconButton = forwardRef(
+ ({ requires, allowOverride, ...props }: PermissionedIconButtonProps, ref) => {
+ const { hasPermission } = usePermissions();
+
+ const isAllowed = Boolean(!requires || hasPermission(requires));
+
+ return (
+ <Tooltip
+ label={
+ allowOverride ? "Requires Override" : !isAllowed ? "Not allowed" : ""
+ }
+ isDisabled={isAllowed}
+ >
+ <IconButton
+ {...props}
+ isDisabled={!isAllowed || props.isDisabled}
+ ref={ref}
+ >
+ {props.children}
+ </IconButton>
+ </Tooltip>
+ );
+ },
+);
diff --git a/src/components/Auth/UnauthorizedCard.tsx b/src/components/Auth/UnauthorizedCard.tsx
index e042287..3402219 100644
--- a/src/components/Auth/UnauthorizedCard.tsx
+++ b/src/components/Auth/UnauthorizedCard.tsx
@@ -1,6 +1,5 @@
import { LockIcon } from "@chakra-ui/icons";
import {
- Button,
ButtonGroup,
Card,
Center,
@@ -8,9 +7,9 @@ import {
Text,
VStack,
} from "@chakra-ui/react";
-import { useNavigate } from "react-router-dom";
import { useUserAuth } from "../../context/AuthenticationContext/AuthenticationContext";
-import { useEmployeeOverride } from "../../context/AuthorizationContext/EmployeeOverrideHandler";
+import { BackButton } from "../common/BackButton";
+import { OverrideButton } from "./OverrideButton";
export type UnauthorizedCardProps = {
overrideAllowed?: boolean;
@@ -25,12 +24,10 @@ export const UnauthorizedCard: React.FC<UnauthorizedCardProps> = ({
message = "Please contact your administrator to request access.",
children,
}) => {
- const navigate = useNavigate();
const { overriddenUser, user, storeUser } = useUserAuth();
overrideAllowed = storeUser ? overrideAllowed : false;
- const { requestOverride } = useEmployeeOverride();
return (
<Card p={8}>
<Center>
@@ -40,16 +37,8 @@ export const UnauthorizedCard: React.FC<UnauthorizedCardProps> = ({
<Text textAlign="center">{message}</Text>
{children ?? (
<ButtonGroup w="100%" justifyContent="center">
- {showBackButton && (
- <Button variant="ghost" onClick={() => navigate(-1)}>
- Back
- </Button>
- )}
- {user && !overriddenUser && overrideAllowed && (
- <Button colorScheme="red" onClick={() => requestOverride()}>
- Override
- </Button>
- )}
+ {showBackButton && <BackButton />}
+ {user && !overriddenUser && overrideAllowed && <OverrideButton />}
</ButtonGroup>
)}
</VStack>
diff --git a/src/components/CashDrawer.tsx b/src/components/CashDrawer.tsx
index 7be9f53..2992d5d 100644
--- a/src/components/CashDrawer.tsx
+++ b/src/components/CashDrawer.tsx
@@ -1,3 +1,4 @@
+import * as Sentry from "@sentry/react";
import { useCallback, useEffect, useState } from "react";
const BAUD_RATE = 9600;
@@ -33,7 +34,9 @@ const useCashDrawer: () => [
);
setDevice(deviceT);
})
- .catch(() => undefined);
+ .catch((err) => {
+ Sentry.captureException(err);
+ });
}
}, []);
diff --git a/src/components/DepartmentSummary.tsx b/src/components/DepartmentSummary.tsx
index cd65cff..83cf73a 100644
--- a/src/components/DepartmentSummary.tsx
+++ b/src/components/DepartmentSummary.tsx
@@ -55,7 +55,7 @@ const DepartmentSummary = ({
})}
<Tr>
<Td>Total</Td>
- <Td>
+ <Td data-testid="department-summary-total-amount">
{USDollar.format(
departmentSummary.reduce(
(partialSum, summary) => partialSum + summary.totalPrice,
@@ -66,11 +66,15 @@ const DepartmentSummary = ({
</Tr>
<Tr>
<Td>Total TAXABLE plus NON TAXABLE Sale</Td>
- <Td>{USDollar.format(taxNonTax)}</Td>
+ <Td data-testid="department-summary-total-taxable-non-taxable-sale">
+ {USDollar.format(taxNonTax)}
+ </Td>
</Tr>
<Tr>
<Td>Total Bottle Deposit+Environment fees Sale</Td>
- <Td>{USDollar.format(itemFees)}</Td>
+ <Td data-testid="department-summary-total-bottle-deposit-environment-fees">
+ {USDollar.format(itemFees)}
+ </Td>
</Tr>
</Tbody>
</Table>
diff --git a/src/components/Events/EventTypeTag.tsx b/src/components/Events/EventTypeTag.tsx
index a49d9b8..6ec1663 100644
--- a/src/components/Events/EventTypeTag.tsx
+++ b/src/components/Events/EventTypeTag.tsx
@@ -4,7 +4,7 @@ export interface EventTypeTagProps {
type: string;
}
-const EventType = {
+export const EventType = {
ITEM_UPDATED: {
name: "Item Updated",
color: "blue",
diff --git a/src/components/ItemDetailComponents/EditableItemTable/EditableItemRow1.tsx b/src/components/ItemDetailComponents/EditableItemTable/EditableItemRow1.tsx
index 4dbf5b9..d80826f 100644
--- a/src/components/ItemDetailComponents/EditableItemTable/EditableItemRow1.tsx
+++ b/src/components/ItemDetailComponents/EditableItemTable/EditableItemRow1.tsx
@@ -160,7 +160,6 @@ export const EditableItemRow1: React.FC<EditableItemRow1Props> = ({
</Editable>
<ItemSizeUnitSelect
minW="70px"
- variant="flushed"
value={row.original.sizeUnit ?? ItemSizeUnit.ML}
onChange={(sizeUnit: ItemSizeUnit) => {
onItemDetailsChange({
@@ -201,7 +200,7 @@ export const EditableItemRow1: React.FC<EditableItemRow1Props> = ({
header: "Department",
cell: ({ getValue, row }) => (
<Select
- variant={"flushed"}
+ w="200px"
placeholder={getValue().name}
onChange={(select) => {
onItemDetailsChange({
diff --git a/src/components/ItemDetailComponents/EditableItemTable/EditableItemRow3.tsx b/src/components/ItemDetailComponents/EditableItemTable/EditableItemRow3.tsx
index 0656d44..e82066b 100644
--- a/src/components/ItemDetailComponents/EditableItemTable/EditableItemRow3.tsx
+++ b/src/components/ItemDetailComponents/EditableItemTable/EditableItemRow3.tsx
@@ -88,7 +88,7 @@ export const EditableItemRow3: React.FC<EditableItemRow3Props> = ({
header: "Last Vendor",
cell: ({ getValue, row }) => (
<Select
- variant={"flushed"}
+ w="200px"
placeholder={
getValue()
? allVendors.find(
diff --git a/src/components/ItemSearchComponents/ItemSearchBox.tsx b/src/components/ItemSearchComponents/ItemSearchBox.tsx
index 5ca9c3e..931c1df 100644
--- a/src/components/ItemSearchComponents/ItemSearchBox.tsx
+++ b/src/components/ItemSearchComponents/ItemSearchBox.tsx
@@ -81,10 +81,10 @@ const customComponents: Partial<SelectComponent> = {
...props
}: MultiValueGenericProps<ItemDetails>) => {
return (
- <Tooltip label={itemLabelFormat(props.data)}>
+ <Tooltip label={itemLabelFormat(props.data.value)}>
<chakra.span display="inline-block" overflow="hidden">
<chakraComponents.MultiValueLabel {...props}>
- {itemLabelFormat(props.data)}
+ {itemLabelFormat(props.data.value)}
</chakraComponents.MultiValueLabel>
</chakra.span>
</Tooltip>
@@ -118,7 +118,7 @@ const customComponents: Partial<SelectComponent> = {
const ItemSearchBox: React.FC<ItemSearchBoxProps> = ({
isMulti,
placeholder,
- onSearchSelected,
+ onSearchSelected = () => undefined,
value,
persistSelection,
containerProps,
@@ -162,7 +162,7 @@ const ItemSearchBox: React.FC<ItemSearchBoxProps> = ({
);
});
return input;
- }, 500),
+ }, 300),
[api, entity?.id, filter],
);
@@ -175,7 +175,13 @@ const ItemSearchBox: React.FC<ItemSearchBoxProps> = ({
menuPortalTarget={document.body}
useBasicStyles
autoFocus={autoFocus}
- value={value}
+ value={
+ Array.isArray(value)
+ ? value.map((v) => ({ label: v.name, value: v }))
+ : value
+ ? { label: value.name, value }
+ : undefined
+ }
styles={{
// container: (base) => ({
// ...base,
@@ -200,11 +206,11 @@ const ItemSearchBox: React.FC<ItemSearchBoxProps> = ({
closeMenuOnSelect={closeMenuOnSelect}
blurInputOnSelect={closeMenuOnSelect} //https://stackoverflow.com/questions/65036191/react-select-component-closemenuonselect-false-still-closing
isDisabled={isDisabled}
- inputValue={inputValue}
- onInputChange={(input, { action }) => {
- if (action !== "set-value") setInputValue(input);
- return input;
- }}
+ // inputValue={inputValue}
+ // onInputChange={(input, { action }) => {
+ // if (action !== "set-value") setInputValue(input);
+ // return input;
+ // }}
onChange={(val: SingleValue<any> | MultiValue<any>) => {
if (isMulti) {
if (val.length === 0) {
@@ -212,8 +218,8 @@ const ItemSearchBox: React.FC<ItemSearchBoxProps> = ({
setSelected("");
return;
}
+ // @ts-ignore
onSearchSelected?.([
- ...(value ?? []),
...val.map((v: { value: ItemDetails }) => v.value),
]);
return;
@@ -222,6 +228,7 @@ const ItemSearchBox: React.FC<ItemSearchBoxProps> = ({
setSelected("");
return;
}
+ // @ts-ignore
onSearchSelected?.(val.value as ItemDetails);
setSelected(val.label);
}}
diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx
index 05e3813..423636b 100644
--- a/src/components/Loading.tsx
+++ b/src/components/Loading.tsx
@@ -1,11 +1,29 @@
-import { Modal, ModalContent, Progress, Stack } from "@chakra-ui/react";
+import {
+ Center,
+ Modal,
+ ModalContent,
+ Spinner,
+ Stack,
+ VStack,
+} from "@chakra-ui/react";
+import { Logo } from "../Logo";
const Loading = () => {
return (
- <Stack background={"gray.100"} height={"100vh"} width={"100vw"}>
+ <Stack
+ key="loading"
+ background={"gray.100"}
+ height={"100vh"}
+ width={"100vw"}
+ >
<Modal size={"xl"} isOpen={true} onClose={() => 0}>
- <ModalContent>
- <Progress size="lg" isIndeterminate />
+ <ModalContent m={0} bg="transparent" boxShadow="none" h="100%">
+ <Center h="100%">
+ <VStack w="100vw" justifyContent="center">
+ <Logo width="50%" />
+ <Spinner size="xl" />
+ </VStack>
+ </Center>
</ModalContent>
</Modal>
</Stack>
diff --git a/src/components/MenuLinks.tsx b/src/components/MenuLinks.tsx
index 364bd52..1ff36d4 100644
--- a/src/components/MenuLinks.tsx
+++ b/src/components/MenuLinks.tsx
@@ -116,6 +116,7 @@ const MenuLinks = () => {
}}
>
<MenuButton
+ data-testid="menu-button"
as={IconButton}
aria-label="Options"
icon={<HamburgerIcon />}
@@ -181,7 +182,7 @@ const MenuLinks = () => {
</MenuButton>
<MenuList>
<MenuGroup title="Transaction Reports">
- <MenuItem as={Link} to="/transaction/by/date">
+ <MenuItem as={Link} to="/transaction/by/date/">
Transaction History
</MenuItem>
<MenuItem as={Link} to="/transaction/by/closing/">
@@ -190,7 +191,7 @@ const MenuLinks = () => {
</MenuGroup>
<MenuDivider />
<MenuGroup title="Item Reports">
- <MenuItem as={Link} to="/item/sales/report">
+ <MenuItem as={Link} to="/item/sales/report/">
Item Sales
</MenuItem>
<MenuItem as={Link} to="/item/dead/stock/">
@@ -217,7 +218,7 @@ const MenuLinks = () => {
>
Provi
</MenuItem>
- <MenuItem as={Link} to="/employee/time" icon={<TimeIcon />}>
+ <MenuItem as={Link} to="/employee/time/" icon={<TimeIcon />}>
Employee Time-clock
</MenuItem>
<MenuItem
diff --git a/src/components/PoleDisplay.tsx b/src/components/PoleDisplay.tsx
index 4bb4ae4..049779c 100644
--- a/src/components/PoleDisplay.tsx
+++ b/src/components/PoleDisplay.tsx
@@ -1,3 +1,4 @@
+import * as Sentry from "@sentry/react";
import { useCallback, useState } from "react";
import { useEffectOnce } from "usehooks-ts";
@@ -31,7 +32,9 @@ const usePoleDisplay = () => {
);
setDevice(deviceT);
})
- .catch(() => undefined);
+ .catch((err) => {
+ Sentry.captureException(err);
+ });
}
});
@@ -41,7 +44,8 @@ const usePoleDisplay = () => {
try {
await device.open({ baudRate: BAUD_RATE });
- } catch {
+ } catch (e) {
+ Sentry.captureException(e);
return;
}
const writer = device.writable.getWriter();
diff --git a/src/components/QtyHotKeys.tsx b/src/components/QtyHotKeys.tsx
index 192a98b..3889306 100644
--- a/src/components/QtyHotKeys.tsx
+++ b/src/components/QtyHotKeys.tsx
@@ -27,7 +27,7 @@ export const QtyHotKeys = ({
return (
<Flex justifyContent="flex-end">
- <ButtonGroup gap="4">
+ <ButtonGroup data-testid="qty-hot-keys" gap="4">
{[2, 3, 4, 5, 6, 7, 8, 10, 12, 24].map((qty) => (
<Button
key={qty}
diff --git a/src/components/SalesScreenComponents/BottomLine/BottomLine.tsx b/src/components/SalesScreenComponents/BottomLine/BottomLine.tsx
index bb6bf63..2d150fa 100644
--- a/src/components/SalesScreenComponents/BottomLine/BottomLine.tsx
+++ b/src/components/SalesScreenComponents/BottomLine/BottomLine.tsx
@@ -11,8 +11,11 @@ import { BottomLineButtonGroup } from "./BottomLineButtonGroup";
export interface BottomLineProps {}
export const BottomLine: React.FC<BottomLineProps> = ({}) => {
- const date = new Date();
- date.setFullYear(date.getFullYear() - 21); // 21 years
+ const date = useMemo(() => {
+ const d = new Date();
+ d.setFullYear(d.getFullYear() - 21); // 21 years
+ return d;
+ }, []);
const {
items: itemDetails,
transactionTotalPrice,
@@ -34,11 +37,13 @@ export const BottomLine: React.FC<BottomLineProps> = ({}) => {
isDisabled: false,
isTag: false,
onClick: () => driversLicenseScannerDisclosure.onOpen(),
+ "data-testid": "id-verification",
},
{
label: `Legal Age: ${date.toLocaleDateString()}`,
colorScheme: "red",
isTag: true,
+ "data-testid": "legal-age-tag",
},
{
label: `Total Quantity: ${[...itemDetails.values()].reduce(
@@ -48,16 +53,19 @@ export const BottomLine: React.FC<BottomLineProps> = ({}) => {
0,
)}`,
isTag: true,
+ "data-testid": "total-quantity-tag",
},
{
label: `Outstanding: ${USDollar.format(
transactionTotalPrice() - amountReceived.amountPaid,
)}`,
isTag: true,
+ "data-testid": "outstanding-tag",
},
{
label: `Total: ${USDollar.format(transactionTotalPrice())}`,
isTag: true,
+ "data-testid": "transaction-total-tag",
},
],
[
diff --git a/src/components/SalesScreenComponents/Modals/CartHoldsModal.tsx b/src/components/SalesScreenComponents/Modals/CartHoldsModal.tsx
index 05921a7..c6eb0bd 100644
--- a/src/components/SalesScreenComponents/Modals/CartHoldsModal.tsx
+++ b/src/components/SalesScreenComponents/Modals/CartHoldsModal.tsx
@@ -21,7 +21,7 @@ import moment from "moment";
import { useCallback } from "react";
import { useForm } from "react-hook-form";
import { useSalesContext } from "../../../context/Sales/SalesContext";
-import { ItemWithQty } from "../../../pages/SalePage/SalePageUtils";
+import { LineItem } from "../../../pages/SalePage/SalePageUtils";
import { StyledInput } from "../../common/StyledInput";
export type CartHoldsModalProps = {
@@ -30,7 +30,7 @@ export type CartHoldsModalProps = {
};
export type Cart = {
- items: ItemWithQty[];
+ items: LineItem[];
taxExempt: boolean;
discount: number;
savedAt: Date;
@@ -42,11 +42,8 @@ export const CartHoldCard = (props: {
deleteCart: () => void;
closeModal: () => void;
}) => {
- const {
- setItems: setItemDetails,
- setDiscount,
- setTaxExempt,
- } = useSalesContext();
+ const { calculateCartPrice, setItems, setDiscount, setTaxExempt } =
+ useSalesContext();
return (
<Tooltip
@@ -89,9 +86,11 @@ export const CartHoldCard = (props: {
</HStack>
<Button
onClick={() => {
- setItemDetails(
- new Map(props.cart.items.map((item) => [item.item.id, item])),
+ const newItemDetails = new Map(
+ props.cart.items.map((item) => [item.item.id, item]),
);
+ setItems(newItemDetails);
+ calculateCartPrice(newItemDetails);
setTaxExempt(props.cart.taxExempt);
setDiscount(props.cart.discount);
props.deleteCart();
diff --git a/src/components/SalesScreenComponents/Modals/ChangeDueModal.tsx b/src/components/SalesScreenComponents/Modals/ChangeDueModal.tsx
index 9090a16..7794e0d 100644
--- a/src/components/SalesScreenComponents/Modals/ChangeDueModal.tsx
+++ b/src/components/SalesScreenComponents/Modals/ChangeDueModal.tsx
@@ -52,6 +52,7 @@ export const ChangeDueModal: React.FC<ChangeDueModalProps> = ({
<ModalFooter>
<Button
colorScheme="blue"
+ data-testid="paid"
mr={3}
isLoading={isPersisting}
isDisabled={isPersisting}
diff --git a/src/components/SalesScreenComponents/Modals/MultipleChoicesModal.tsx b/src/components/SalesScreenComponents/Modals/MultipleChoicesModal.tsx
index d497b8a..b07dd44 100644
--- a/src/components/SalesScreenComponents/Modals/MultipleChoicesModal.tsx
+++ b/src/components/SalesScreenComponents/Modals/MultipleChoicesModal.tsx
@@ -11,26 +11,19 @@ import {
} from "@chakra-ui/react";
import { useSalesContext } from "../../../context/Sales/SalesContext";
import { ItemDetails } from "../../../model/data-contracts";
-import {
- addSingleItem,
- ItemWithQty,
-} from "../../../pages/SalePage/SalePageUtils";
-import usePoleDisplay from "../../PoleDisplay";
export type MultipleChoicesModalProps = {
multipleChoicesModal: UseDisclosureReturn;
transactionTotalPrice: () => number;
- multipleChoices: ItemDetails[];
+ multipleChoices: { items: ItemDetails[]; quantity: number };
};
export const MultipleChoicesModal: React.FC<MultipleChoicesModalProps> = ({
multipleChoicesModal,
- transactionTotalPrice,
multipleChoices,
}) => {
const { isOpen, onClose } = multipleChoicesModal;
- const poleDisplay = usePoleDisplay();
- const { quantity, taxExempt, setItems: setItemDetails } = useSalesContext();
+ const { addSingleItemToCart } = useSalesContext();
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
@@ -39,7 +32,7 @@ export const MultipleChoicesModal: React.FC<MultipleChoicesModalProps> = ({
<ModalCloseButton />
<ModalBody>
<SimpleGrid columns={2} spacing="20px">
- {multipleChoices?.map((choice) => {
+ {multipleChoices.items.map((choice) => {
return (
<Button
key={choice.id}
@@ -47,27 +40,7 @@ export const MultipleChoicesModal: React.FC<MultipleChoicesModalProps> = ({
overflowX={"hidden"}
variant="ghost"
onClick={() => {
- setItemDetails((prev) => {
- const m = new Map<number, ItemWithQty>(prev);
- const item = addSingleItem(
- choice,
- m,
- quantity,
- taxExempt,
- );
- poleDisplay(
- `${item.quantity} ${item.item.name
- .trim()
- .substring(0, 9)} $${(
- item.item.sellPrice - item.discount
- ).toFixed(2)}`,
- `Grand Total $${(
- transactionTotalPrice() + item.totalPrice
- ).toFixed(2)}`,
- );
- m.set(choice.id, item);
- return m;
- });
+ addSingleItemToCart(choice, multipleChoices.quantity);
onClose();
}}
>
diff --git a/src/components/SalesScreenComponents/Modals/PendingTransactionsModal.tsx b/src/components/SalesScreenComponents/Modals/PendingTransactionsModal.tsx
index 2be75ae..2dab402 100644
--- a/src/components/SalesScreenComponents/Modals/PendingTransactionsModal.tsx
+++ b/src/components/SalesScreenComponents/Modals/PendingTransactionsModal.tsx
@@ -6,6 +6,7 @@ import {
ModalHeader,
ModalOverlay,
} from "@chakra-ui/react";
+import * as Sentry from "@sentry/react";
import { createColumnHelper } from "@tanstack/react-table";
import { useEffect, useMemo, useState } from "react";
import { useTransactionsApi } from "../../../config/ItemsApi";
@@ -58,17 +59,20 @@ export const PendingTransactionsModal: React.FC<
// effect to load data
useEffect(() => {
if (!entity || !transactionsApi || !isOpen) return;
- transactionsApi
- .getTransactions({
- entityId: entity.id,
- transactionStatus: TransactionStatus.PENDING,
- })
- .then((resp) => {
+
+ const getPendingTransactions = async () => {
+ try {
+ const resp = await transactionsApi.getTransactions({
+ entityId: entity.id,
+ transactionStatus: TransactionStatus.PENDING,
+ });
setPendingTransactions(resp.data);
- })
- .catch((err) => {
+ } catch (err) {
+ Sentry.captureException(err);
console.log(err);
- });
+ }
+ };
+ getPendingTransactions();
}, [transactionsApi, entity, isOpen]);
return (
diff --git a/src/components/SalesScreenComponents/Modals/QuickItemEditModal.tsx b/src/components/SalesScreenComponents/Modals/QuickItemEditModal.tsx
index 2fbc793..2fb5168 100644
--- a/src/components/SalesScreenComponents/Modals/QuickItemEditModal.tsx
+++ b/src/components/SalesScreenComponents/Modals/QuickItemEditModal.tsx
@@ -13,7 +13,7 @@ import {
ModalOverlay,
useToast,
} from "@chakra-ui/react";
-import { useCallback, useRef, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import { useItemsApi } from "../../../config/ItemsApi";
import { ItemDetails } from "../../../model/data-contracts";
import { PermissionedButton } from "../../Auth/PermissionedButton";
@@ -34,9 +34,15 @@ export const QuickItemEditModal: React.FC<QuickItemEditModalProps> = ({
}) => {
const itemsApi = useItemsApi();
const toast = useToast();
- const [itemDetails, setItemDetails] = useState<ItemDetails>(selectedItem);
+ const [itemDetails, setItemDetails] = useState<ItemDetails>();
const saveRef = useRef<HTMLButtonElement>(null);
+ useEffect(() => {
+ itemsApi?.detailsDetail(selectedItem.id).then((res) => {
+ setItemDetails(res.data);
+ });
+ }, [selectedItem.id, itemsApi]);
+
const saveItem = useCallback(
(item: ItemDetails) => {
if (!itemsApi) return;
@@ -61,6 +67,22 @@ export const QuickItemEditModal: React.FC<QuickItemEditModalProps> = ({
[toast, itemsApi, onUpdateSuccess, setItemDetails],
);
+ if (!itemDetails)
+ return (
+ <Modal
+ isOpen={isOpen}
+ onClose={onClose}
+ size="8xl"
+ initialFocusRef={saveRef}
+ >
+ <ModalOverlay />
+ <ModalContent>
+ <ModalHeader>Loading...</ModalHeader>
+ <ModalCloseButton />
+ </ModalContent>
+ </Modal>
+ );
+
return (
<Modal
isOpen={isOpen}
@@ -73,7 +95,12 @@ export const QuickItemEditModal: React.FC<QuickItemEditModalProps> = ({
<ModalHeader>
<Editable
value={itemDetails.name}
- onChange={(name) => setItemDetails((prev) => ({ ...prev, name }))}
+ onChange={(name) =>
+ setItemDetails((prev) => {
+ if (!prev) return prev;
+ return { ...prev, name };
+ })
+ }
>
<EditablePreview />
<Input as={EditableInput} />
diff --git a/src/components/SalesScreenComponents/SalesButtonGroups/CashOptions/CashOptions.tsx b/src/components/SalesScreenComponents/SalesButtonGroups/CashOptions/CashOptions.tsx
index 238f9d8..2bdbe0e 100644
--- a/src/components/SalesScreenComponents/SalesButtonGroups/CashOptions/CashOptions.tsx
+++ b/src/components/SalesScreenComponents/SalesButtonGroups/CashOptions/CashOptions.tsx
@@ -20,9 +20,10 @@ export const CashOptions: React.FC<CashOptionsProps> = ({
"$50",
`$${transactionTotalPrice().toFixed(2)}`,
`$${Math.ceil(transactionTotalPrice()).toFixed(2)}`,
- ].map((money) => {
+ ].map((money, i) => {
return {
isDisabled: !transactionItems.length || transactionTotalPrice() < 0,
+ "data-testid": `moneys-${i}`,
onClick: () => {
setAmountPaid((prev) => {
const newAmountPaid = prev.amountPaid + +money.replaceAll("$", "");
diff --git a/src/components/SalesScreenComponents/SalesButtonGroups/Miscellaneous/Miscellaneous.tsx b/src/components/SalesScreenComponents/SalesButtonGroups/Miscellaneous/Miscellaneous.tsx
index 92d52ae..2eec148 100644
--- a/src/components/SalesScreenComponents/SalesButtonGroups/Miscellaneous/Miscellaneous.tsx
+++ b/src/components/SalesScreenComponents/SalesButtonGroups/Miscellaneous/Miscellaneous.tsx
@@ -35,8 +35,10 @@ export const Miscellaneous: React.FC<MiscellaneousProps> = ({ deleteAll }) => {
() => [
{
label: "Discount All",
+ "data-testid": "discount-all",
leftIcon: <MdDiscount />,
isDisabled: !itemDetails.size,
+ disabled: !itemDetails.size,
onClick: () => {
onOpen();
},
@@ -45,8 +47,10 @@ export const Miscellaneous: React.FC<MiscellaneousProps> = ({ deleteAll }) => {
},
{
label: "Delete All",
+ "data-testid": "delete-all",
leftIcon: <DeleteIcon />,
isDisabled: !itemDetails.size,
+ disabled: !itemDetails.size,
onClick: () => {
deleteAll();
},
@@ -54,6 +58,7 @@ export const Miscellaneous: React.FC<MiscellaneousProps> = ({ deleteAll }) => {
},
{
label: "Holds",
+ "data-testid": "holds",
leftIcon: <FaShoppingCart />,
onClick: cartHoldsDisclosure.onOpen,
},
@@ -68,6 +73,7 @@ export const Miscellaneous: React.FC<MiscellaneousProps> = ({ deleteAll }) => {
e.currentTarget.blur();
setTaxExempt(e.currentTarget.checked);
}}
+ data-testid="tax-exempt-switch"
/>
</FormControl>,
],
diff --git a/src/components/SalesScreenComponents/SalesButtonGroups/QuickPicks/QuickPicks.tsx b/src/components/SalesScreenComponents/SalesButtonGroups/QuickPicks/QuickPicks.tsx
index ecb7772..c3ae2bc 100644
--- a/src/components/SalesScreenComponents/SalesButtonGroups/QuickPicks/QuickPicks.tsx
+++ b/src/components/SalesScreenComponents/SalesButtonGroups/QuickPicks/QuickPicks.tsx
@@ -12,17 +12,16 @@ export interface QuickPicksProps {
retrieveItemsLikeUpc: (upc: string) => void;
}
+export const NEW_ITEM_UPC = "NEW ITEM";
+
export const QuickPicks: React.FC<QuickPicksProps> = ({
retrieveItemsLikeUpc,
}) => {
- const {
- setItems: setItemDetails,
- taxExempt,
- transactionTotalPrice,
- } = useSalesContext();
+ const { calculateCartPrice, setItems, taxExempt, transactionTotalPrice } =
+ useSalesContext();
const { isOpen, onOpen, onClose } = useDisclosure();
const poleDisplay = usePoleDisplay();
- const [entity] = useEntitySelected();
+ const [entity, , isEntityLoading] = useEntitySelected();
const QUICK_PICKS = useMemo(() => {
if (!entity?.id) return [];
@@ -53,12 +52,12 @@ export const QuickPicks: React.FC<QuickPicksProps> = ({
},
<NewItem
onSubmit={(price, department, bottleFee, envFee) =>
- setItemDetails((prev) => {
+ setItems((prev) => {
const m = new Map(prev);
- const item = addSingleItem(
+ const lineItem = addSingleItem(
{
id: Math.floor(Math.random() * 900000), // any random id is fine as new item don't have ids when saved
- upc: "NEW ITEM",
+ upc: NEW_ITEM_UPC,
onHandQuantity: 1,
name: "UNKNOWN ITEM",
sellPrice: price,
@@ -76,14 +75,15 @@ export const QuickPicks: React.FC<QuickPicksProps> = ({
taxExempt,
);
poleDisplay(
- `${item.quantity} ${item.item.name.trim().substring(0, 9)} $${(
- item.item.sellPrice - item.discount
- ).toFixed(2)}`,
+ `${lineItem.quantity} ${lineItem.item.name
+ .trim()
+ .substring(0, 9)} $${lineItem.item.sellPrice.toFixed(2)}`,
`Grand Total $${(
- transactionTotalPrice() + item.totalPrice
+ transactionTotalPrice() + lineItem.totalPrice
).toFixed(2)}`,
);
- m.set(item.item.id, item);
+ m.set(lineItem.item.id, lineItem);
+ calculateCartPrice(m);
return m;
})
}
@@ -96,14 +96,17 @@ export const QuickPicks: React.FC<QuickPicksProps> = ({
// },
];
}, [
+ calculateCartPrice,
+ QUICK_PICKS,
retrieveItemsLikeUpc,
- setItemDetails,
+ setItems,
taxExempt,
transactionTotalPrice,
poleDisplay,
- onOpen,
]);
+ if (isEntityLoading) return null;
+
return (
<>
<SalesButtonGroup colorScheme="yellow" elements={elements} />
diff --git a/src/components/SalesScreenComponents/SalesScreenItemsTable.tsx b/src/components/SalesScreenComponents/SalesScreenItemsTable.tsx
index 17fac3b..62efc34 100644
--- a/src/components/SalesScreenComponents/SalesScreenItemsTable.tsx
+++ b/src/components/SalesScreenComponents/SalesScreenItemsTable.tsx
@@ -1,5 +1,6 @@
import { DeleteIcon } from "@chakra-ui/icons";
-import { HStack, Icon, IconButton, Text } from "@chakra-ui/react";
+import { HStack, Icon, IconButton, Text, Tooltip } from "@chakra-ui/react";
+import * as Sentry from "@sentry/react";
import { createColumnHelper } from "@tanstack/react-table";
import {
Dispatch,
@@ -14,25 +15,28 @@ import { useItemsApi, useTransactionApi } from "../../config/ItemsApi";
import { useEntitySelected } from "../../context/EntityProvider";
import { usePermissions } from "../../context/PermissionsContext";
import { useRegisterProvider } from "../../context/RegisterProvider";
-import { useSalesContext } from "../../context/Sales/SalesContext";
-import { Item, ItemDetails } from "../../model/data-contracts";
import {
- calculateTotalPrice,
- ItemWithQty,
-} from "../../pages/SalePage/SalePageUtils";
+ SalesContextType,
+ useSalesContext,
+} from "../../context/Sales/SalesContext";
+import { Item, ItemDetails } from "../../model/data-contracts";
+import { USDollar } from "../../pages/SalePage/SalePage";
+import { LineItem } from "../../pages/SalePage/SalePageUtils";
import { NumberInputWithSideSteppers } from "../common/NumberInputWithSideSteppers";
import usePoleDisplay from "../PoleDisplay";
import { CurrencyRow } from "../Table/CurrencyRow";
import { TransformityTable } from "../Table/TransformityTable";
import { QuickItemEditModal } from "./Modals/QuickItemEditModal";
+import { NEW_ITEM_UPC } from "./SalesButtonGroups/QuickPicks/QuickPicks";
export interface SalesScreenItemsTableProps {
transactionTotalPrice: () => number;
}
-type ItemWithQtyProps = ItemWithQty & {
+type ItemWithQtyProps = LineItem & {
poleDisplay: ReturnType<typeof usePoleDisplay>;
- setItemDetails: Dispatch<SetStateAction<Map<number, ItemWithQty>>>;
+ setItemDetails: Dispatch<SetStateAction<Map<number, LineItem>>>;
+ calculateCartPrice: SalesContextType["calculateCartPrice"];
transactionTotalPrice: () => number;
removeItem: (id: number) => void;
taxExempt: boolean;
@@ -52,20 +56,23 @@ export const SalesScreenItemsTable: React.FC<SalesScreenItemsTableProps> = ({
items: itemDetails,
setItems: setItemDetails,
taxExempt,
+ calculateCartPrice,
} = useSalesContext();
const poleDisplay = usePoleDisplay();
const [entity] = useEntitySelected();
const { register } = useRegisterProvider();
const removeItem = useCallback(
- (id: number) => {
+ async (id: number) => {
if (!entity?.id) return;
- transactionApi
- ?.emptyCart({
+ try {
+ await transactionApi?.emptyCart({
entityId: entity?.id,
registerNumber: register.registerNumber,
itemId: id,
- })
- .catch((err) => console.error(err));
+ });
+ } catch (err) {
+ Sentry.captureException(err);
+ }
setItemDetails((prev) => {
const m = new Map(prev);
const item = m.get(id);
@@ -78,6 +85,7 @@ export const SalesScreenItemsTable: React.FC<SalesScreenItemsTableProps> = ({
)}`,
);
}
+ calculateCartPrice(m);
return m;
});
},
@@ -88,6 +96,7 @@ export const SalesScreenItemsTable: React.FC<SalesScreenItemsTableProps> = ({
setItemDetails,
transactionApi,
transactionTotalPrice,
+ calculateCartPrice,
],
);
@@ -103,16 +112,20 @@ export const SalesScreenItemsTable: React.FC<SalesScreenItemsTableProps> = ({
const columns: any[] = useMemo(
() => [
- columnHelper.accessor((_row: ItemWithQty, i: number) => i + 1, {
+ columnHelper.accessor((_row: LineItem, i: number) => i + 1, {
header: "#",
}),
columnHelper.accessor("item.name", {
header: "Name",
cell: ({ getValue, row }) => (
<HStack
- cursor={"pointer"}
+ cursor={
+ row.original.item.upc !== NEW_ITEM_UPC ? "pointer" : undefined
+ }
onClick={() =>
- hasPermission("item/*:read") && setSelectedItem(row.original.item)
+ hasPermission("item/*:read") &&
+ row.original.item.upc !== NEW_ITEM_UPC &&
+ setSelectedItem(row.original.item)
}
>
<Text>{getValue()}</Text>
@@ -122,99 +135,125 @@ export const SalesScreenItemsTable: React.FC<SalesScreenItemsTableProps> = ({
}),
columnHelper.accessor("item.sellPrice", {
header: "Price",
- cell: ({ getValue }) => (
- <CurrencyRow
- value={getValue()}
- containerProps={{ textAlign: undefined }}
- />
- ),
- }),
- columnHelper.accessor("discount", {
- header: "Discount",
- cell: ({ getValue }) => (
+ cell: ({ getValue, row }) => (
<CurrencyRow
+ data-testid={`price-${row.index}`
+ .replaceAll(" ", "-")
+ .toLowerCase()}
+ maxW={"fit-content"}
value={getValue()}
- containerProps={{ textAlign: undefined }}
+ textAlign={undefined}
/>
),
}),
columnHelper.accessor("quantity", {
header: "Quantity",
- cell: ({ getValue, row: { original: item } }) => (
+ cell: ({ getValue, row: { original: lineItem } }) => (
<NumberInputWithSideSteppers
- key={item.item.id}
+ key={lineItem.item.id}
maxWidth={"190px"}
precision={0}
step={1}
value={getValue()}
onChange={(val) => {
- item.quantityAsString = val.toString();
+ lineItem.quantityAsString = val.toString();
try {
- item.quantity = val;
- item.setItemDetails((prev) => {
+ lineItem.quantity = val;
+ lineItem.setItemDetails((prev) => {
const m = new Map(prev);
- item.totalPrice = calculateTotalPrice(item, item.taxExempt);
- m.set(item.item.id, item);
+
+ m.set(lineItem.item.id, lineItem);
+ lineItem.calculateCartPrice(m);
return m;
});
- item.poleDisplay(
- `${item.quantity} ${item.item.name
+ lineItem.poleDisplay(
+ `${lineItem.quantity} ${lineItem.item.name
.trim()
- .substring(0, 9)} ${(
- item.item.sellPrice - item.discount
- ).toFixed(2)}`,
- `${item.transactionTotalPrice().toFixed(2)}`,
+ .substring(0, 9)} ${lineItem.item.sellPrice.toFixed(2)}`,
+ `${lineItem.transactionTotalPrice().toFixed(2)}`,
);
- } catch {
+ } catch (err) {
// Do nothing
+ Sentry.captureException(err);
}
}}
/>
),
}),
- columnHelper.accessor(
- (row: ItemWithQty) =>
- (row.item.sellPrice - row.discount) * row.quantity,
- {
- header: "Sub-total",
- cell: ({ getValue }) => (
- <CurrencyRow
- value={getValue()}
- containerProps={{ textAlign: undefined }}
- />
- ),
- },
- ),
- columnHelper.accessor(
- (row: ItemWithQty) => row.item.department.environmentFee * row.quantity,
- {
- header: "Env Fee",
- cell: ({ getValue }) => (
- <CurrencyRow
- value={getValue()}
- containerProps={{ textAlign: undefined }}
- />
- ),
- },
- ),
- columnHelper.accessor(
- (row: ItemWithQty) => row.item.department.bottleDeposit * row.quantity,
- {
- header: "Deposit",
- cell: ({ getValue }) => (
- <CurrencyRow
- value={getValue()}
- containerProps={{ textAlign: undefined }}
- />
- ),
+ columnHelper.accessor("discount", {
+ header: "DISCOUNT",
+ cell: ({ getValue, row }) => {
+ return (
+ <Tooltip
+ label={row.original.promotionAmounts?.map((p) => (
+ <p>
+ Promotion: {p.promotionName} - {USDollar.format(p.amount)}
+ </p>
+ ))}
+ isDisabled={row.original.promotionAmounts?.length === 0}
+ hasArrow
+ >
+ <CurrencyRow
+ data-testid={`discount-${row.index}`
+ .replaceAll(" ", "-")
+ .toLowerCase()}
+ maxW={"fit-content"}
+ value={getValue()}
+ textAlign={undefined}
+ />
+ </Tooltip>
+ );
},
- ),
+ }),
+ columnHelper.accessor((row: LineItem) => row.subtotal, {
+ header: "Sub-total",
+ cell: ({ getValue, row }) => (
+ <CurrencyRow
+ data-testid={`subtotal-${row.index}`
+ .replaceAll(" ", "-")
+ .toLowerCase()}
+ maxW={"fit-content"}
+ value={getValue()}
+ textAlign={undefined}
+ />
+ ),
+ }),
+ columnHelper.accessor((row: LineItem) => row.environmentFee, {
+ header: "Env Fee",
+ cell: ({ getValue, row }) => (
+ <CurrencyRow
+ data-testid={`env-fee-${row.index}`
+ .replaceAll(" ", "-")
+ .toLowerCase()}
+ maxW={"fit-content"}
+ value={getValue()}
+ textAlign={undefined}
+ />
+ ),
+ }),
+ columnHelper.accessor((row: LineItem) => row.bottleDeposit, {
+ header: "Deposit",
+ cell: ({ getValue, row }) => (
+ <CurrencyRow
+ data-testid={`deposit-${row.index}`
+ .replaceAll(" ", "-")
+ .toLowerCase()}
+ maxW={"fit-content"}
+ value={getValue()}
+ textAlign={undefined}
+ />
+ ),
+ }),
columnHelper.accessor("totalPrice", {
header: "Item Total",
- cell: ({ getValue }) => (
+ cell: ({ getValue, row }) => (
<CurrencyRow
+ data-testid={`total-price-${row.index}`
+ .replaceAll(" ", "-")
+ .toLowerCase()}
+ maxW={"fit-content"}
value={getValue()}
- containerProps={{ textAlign: undefined }}
+ textAlign={undefined}
/>
),
}),
@@ -227,7 +266,7 @@ export const SalesScreenItemsTable: React.FC<SalesScreenItemsTableProps> = ({
size="sm"
colorScheme="red"
onFocus={(e) => e.currentTarget.blur()}
- onClick={(e) => {
+ onClick={() => {
item.removeItem(item.item.id);
}}
aria-label={""}
@@ -251,6 +290,7 @@ export const SalesScreenItemsTable: React.FC<SalesScreenItemsTableProps> = ({
setItemDetails,
removeItem,
taxExempt,
+ calculateCartPrice,
}))}
/>
{selectedItemDetails && (
@@ -264,12 +304,9 @@ export const SalesScreenItemsTable: React.FC<SalesScreenItemsTableProps> = ({
const itemDetails = m.get(item.id);
if (itemDetails) {
itemDetails.item = item;
- itemDetails.totalPrice = calculateTotalPrice(
- itemDetails,
- taxExempt,
- );
m.set(item.id, itemDetails);
}
+ calculateCartPrice(m);
return m;
});
}}
diff --git a/src/components/Table/CurrencyRow.tsx b/src/components/Table/CurrencyRow.tsx
index fb5dbd5..a361de2 100644
--- a/src/components/Table/CurrencyRow.tsx
+++ b/src/components/Table/CurrencyRow.tsx
@@ -1,20 +1,18 @@
-import { Box, BoxProps } from "@chakra-ui/react";
+import { Box, BoxProps, forwardRef } from "@chakra-ui/react";
import { Currency, currencyFormat } from "../../utils/currencyUtils";
-export interface CurrencyRowProps {
+export interface CurrencyRowProps extends BoxProps {
currency?: Currency;
value?: number;
- containerProps?: BoxProps;
}
-export const CurrencyRow: React.FC<CurrencyRowProps> = ({
- currency = "USD",
- value,
- containerProps,
-}) => {
+export const CurrencyRow: React.FC<CurrencyRowProps> = forwardRef<
+ CurrencyRowProps,
+ "div"
+>(({ currency = "USD", value, ...containerProps }, ref) => {
return (
- <Box w="100%" textAlign="right" {...containerProps}>
+ <Box w="100%" textAlign="right" ref={ref} {...containerProps}>
{currencyFormat(currency, value)}
</Box>
);
-};
+});
diff --git a/src/components/Table/TransformityTable.tsx b/src/components/Table/TransformityTable.tsx
index 6aab01f..b9e13f4 100644
--- a/src/components/Table/TransformityTable.tsx
+++ b/src/components/Table/TransformityTable.tsx
@@ -103,6 +103,10 @@ export function TransformityTable<T>({
getSortedRowModel: getSortedRowModel(),
getExpandedRowModel: getExpandedRowModel(),
getSubRows,
+ state: {
+ sorting: sortingState,
+ expanded: expanded,
+ },
onExpandedChange: (updaterOrValue) => {
if (typeof updaterOrValue === "function") {
expanded && setExpanded && setExpanded(updaterOrValue(expanded));
diff --git a/src/components/TransactionByDate.tsx b/src/components/TransactionByDate.tsx
index c6bbe62..8c09c0d 100644
--- a/src/components/TransactionByDate.tsx
+++ b/src/components/TransactionByDate.tsx
@@ -17,6 +17,7 @@ import { FaPrint } from "react-icons/fa";
import { Link } from "react-router-dom";
import { TransactionReportByDateOutput } from "../model/data-contracts";
import { USDollar } from "../pages/SalePage/SalePage";
+import { calculateSubtotal } from "../utils/numberUtils";
export type TransactionByDateProps = {
data: TransactionReportByDateOutput;
@@ -33,12 +34,12 @@ function itemComp(transactions: TransactionReportByDateOutput["transactions"]) {
let environmentFee = 0;
let discount = 0;
- for (const item of transaction.transactionItems) {
- tax += item.tax;
- subTotal += item.price * item.quantity;
- bottleFee += item.bottleFee;
- environmentFee += item.environmentFee;
- discount += item.discount;
+ for (const transactionItem of transaction.transactionItems) {
+ tax += transactionItem.tax;
+ subTotal += calculateSubtotal(transactionItem);
+ bottleFee += transactionItem.bottleFee;
+ environmentFee += transactionItem.environmentFee;
+ discount += transactionItem.discount;
}
items.push(
@@ -94,7 +95,7 @@ const TransactionByDate = ({ data }: TransactionByDateProps) => {
0,
);
totalSubtotal += transactionItems.reduce(
- (partialSum, a) => partialSum + a.price * a.quantity,
+ (partialSum, a) => partialSum + calculateSubtotal(a),
0,
);
totalBottleFee += transactionItems.reduce(
diff --git a/src/components/TransactionsComponents/SummaryTable.tsx b/src/components/TransactionsComponents/SummaryTable.tsx
index 9afed95..7fb6f48 100644
--- a/src/components/TransactionsComponents/SummaryTable.tsx
+++ b/src/components/TransactionsComponents/SummaryTable.tsx
@@ -87,7 +87,14 @@ export const SummaryTable = ({
{body.map((b, i) => (
<Tr key={i}>
{b.map((j, k) => (
- <Td key={k}>{j}</Td>
+ <Td
+ data-testid={`${header.at(k)}-${i}-${k}`
+ .replaceAll(" ", "-")
+ .toLowerCase()}
+ key={k}
+ >
+ {j}
+ </Td>
))}
</Tr>
))}
diff --git a/src/components/TransactionsComponents/TransactionsReportTable.tsx b/src/components/TransactionsComponents/TransactionsReportTable.tsx
index 764b0ff..21001a6 100644
--- a/src/components/TransactionsComponents/TransactionsReportTable.tsx
+++ b/src/components/TransactionsComponents/TransactionsReportTable.tsx
@@ -45,6 +45,7 @@ export const TransactionsReportTable: React.FC<
cell: ({ getValue }) => <CurrencyRow value={getValue()} />,
footer: ({ table }) => (
<CurrencyRow
+ data-testid="tax-footer"
value={table
.getRowModel()
.rows.reduce((acc, row) => acc + row.original.tax, 0)}
@@ -57,6 +58,7 @@ export const TransactionsReportTable: React.FC<
cell: ({ getValue }) => <CurrencyRow value={getValue()} />,
footer: ({ table }) => (
<CurrencyRow
+ data-testid="subtotal-footer"
value={table
.getRowModel()
.rows.reduce((acc, row) => acc + row.original.subtotal, 0)}
@@ -69,6 +71,7 @@ export const TransactionsReportTable: React.FC<
cell: ({ getValue }) => <CurrencyRow value={getValue()} />,
footer: ({ table }) => (
<CurrencyRow
+ data-testid="bottle-deposit-footer"
value={table
.getRowModel()
.rows.reduce((acc, row) => acc + row.original.bottleDeposit, 0)}
@@ -81,6 +84,7 @@ export const TransactionsReportTable: React.FC<
cell: ({ getValue }) => <CurrencyRow value={getValue()} />,
footer: ({ table }) => (
<CurrencyRow
+ data-testid="environment-fee-footer"
value={table
.getRowModel()
.rows.reduce((acc, row) => acc + row.original.environmentFee, 0)}
@@ -93,6 +97,7 @@ export const TransactionsReportTable: React.FC<
cell: ({ getValue }) => <CurrencyRow value={getValue()} />,
footer: ({ table }) => (
<CurrencyRow
+ data-testid="discount-footer"
value={table
.getRowModel()
.rows.reduce((acc, row) => acc + row.original.discount, 0)}
@@ -111,6 +116,7 @@ export const TransactionsReportTable: React.FC<
(acc, row) => acc + row.original.transactionTotal,
0,
)}
+ data-testid="transaction-total-footer"
/>
),
}),
@@ -142,7 +148,7 @@ export const TransactionsReportTable: React.FC<
},
}),
columnHelper.accessor((tx) => tx.allNames.substring(0, 200), {
- header: "Items(s) Sold",
+ header: "Item(s) Sold",
}),
columnHelper.display({
id: "actions",
diff --git a/src/components/UnauthenticatedUserOnlyRoute.tsx b/src/components/UnauthenticatedUserOnlyRoute.tsx
index 0bc6a75..e9650b6 100644
--- a/src/components/UnauthenticatedUserOnlyRoute.tsx
+++ b/src/components/UnauthenticatedUserOnlyRoute.tsx
@@ -11,7 +11,7 @@ const UnauthenticatedUserOnlyRoute: React.FC<{}> = () => {
} else if (user === null) {
return <Outlet />;
} else {
- return <Navigate to="/sale" />;
+ return <Navigate to="/sale/" />;
}
};
diff --git a/src/components/VoidSale.tsx b/src/components/VoidSale.tsx
index 9f0cfdc..1e5e95a 100644
--- a/src/components/VoidSale.tsx
+++ b/src/components/VoidSale.tsx
@@ -6,12 +6,22 @@ import {
AlertDialogHeader,
AlertDialogOverlay,
Button,
+ HStack,
Input,
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalHeader,
+ ModalOverlay,
+ PinInput,
+ PinInputField,
Spacer,
Text,
useDisclosure,
useToast,
} from "@chakra-ui/react";
+import * as Sentry from "@sentry/react";
import { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useTransactionApi } from "../config/ItemsApi";
@@ -51,10 +61,13 @@ const VoidSale = ({ txId, isVoided, inputtedItemDetails }: VoidSaleType) => {
setTransactionOutput(inputtedItemDetails);
}, [inputtedItemDetails]);
+ // TEMPORARY FOR BROWNSTONE
+ const approvalModal = useDisclosure();
+
useEffect(() => {
if (isVoided === true) return;
if (inputtedItemDetails) return;
- if (!blockingModal.isOpen) return;
+ if (!(blockingModal.isOpen || approvalModal.isOpen)) return;
transactionApi?.getTransactionById(txId).then((res) => {
setTransactionOutput(res.data);
@@ -65,6 +78,7 @@ const VoidSale = ({ txId, isVoided, inputtedItemDetails }: VoidSaleType) => {
transactionApi,
txId,
inputtedItemDetails,
+ approvalModal.isOpen,
]);
const VOID_SALE = ["Void"].map((value) => {
@@ -74,9 +88,18 @@ const VoidSale = ({ txId, isVoided, inputtedItemDetails }: VoidSaleType) => {
allowOverride
key={value}
colorScheme="red"
- isDisabled={transactionOutput?.isVoided || isVoided}
+ isDisabled={
+ transactionOutput?.isVoided ||
+ isVoided ||
+ !(transactionOutput?.isTransactionOpen ?? true)
+ }
onClick={() => {
if (!txId) return;
+ // TEMPORARY FOR BROWNSTONE
+ if (entity?.id === 6) {
+ approvalModal.onOpen();
+ return;
+ }
blockingModal.onOpen();
}}
>
@@ -136,7 +159,9 @@ const VoidSale = ({ txId, isVoided, inputtedItemDetails }: VoidSaleType) => {
</Button>
<Spacer />
<Button
- isDisabled={disableButton}
+ isDisabled={
+ disableButton || !transactionOutput.isTransactionOpen
+ }
isLoading={isVoiding}
onClick={async () => {
if (!transactionApi) return;
@@ -158,6 +183,7 @@ const VoidSale = ({ txId, isVoided, inputtedItemDetails }: VoidSaleType) => {
duration: 5000,
isClosable: true,
});
+ Sentry.captureException(err);
} finally {
blockingModal.onClose();
}
@@ -169,8 +195,64 @@ const VoidSale = ({ txId, isVoided, inputtedItemDetails }: VoidSaleType) => {
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
+ <ManagerPinInputModal
+ {...approvalModal}
+ onSuccess={() => {
+ blockingModal.onOpen();
+ }}
+ />
</>
);
};
+// TEMPORARY FOR BROWNSTONE
+type ManagerPinInputModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ onSuccess: () => void;
+};
+
+const ManagerPinInputModal: React.FC<ManagerPinInputModalProps> = ({
+ isOpen,
+ onClose,
+ onSuccess,
+}) => {
+ const [approvalCode, setApprovalCode] = useState<string>("");
+
+ useEffect(() => {
+ if (approvalCode === "2285") {
+ setApprovalCode("");
+ onSuccess();
+ onClose();
+ }
+ }, [approvalCode, onSuccess, onClose]);
+
+ return (
+ <Modal isOpen={isOpen} onClose={onClose}>
+ <ModalOverlay />
+ <ModalContent>
+ <ModalHeader>Requires Manager Approval</ModalHeader>
+ <ModalCloseButton />
+ <ModalBody>
+ <HStack w="100%" justifyContent="center">
+ <PinInput
+ otp
+ mask
+ size="lg"
+ onChange={setApprovalCode}
+ value={approvalCode}
+ autoFocus={true}
+ >
+ <PinInputField autoComplete="off" />
+ <PinInputField autoComplete="off" />
+ <PinInputField autoComplete="off" />
+ <PinInputField autoComplete="off" />
+ </PinInput>
+ </HStack>
+ </ModalBody>
+ </ModalContent>
+ </Modal>
+ );
+};
+
export default VoidSale;
diff --git a/src/components/common/BackButton.tsx b/src/components/common/BackButton.tsx
new file mode 100644
index 0000000..de0ad1d
--- /dev/null
+++ b/src/components/common/BackButton.tsx
@@ -0,0 +1,18 @@
+import { Button, ButtonProps } from "@chakra-ui/react";
+import { useNavigate } from "react-router-dom";
+
+export type BackButtonProps = ButtonProps & {
+ children?: React.ReactNode;
+};
+
+export const BackButton: React.FC<BackButtonProps> = ({
+ children,
+ ...props
+}) => {
+ const navigate = useNavigate();
+ return (
+ <Button variant="ghost" onClick={() => navigate(-1)} {...props}>
+ {children ?? "Back"}
+ </Button>
+ );
+};
diff --git a/src/components/common/CardList/CardList.tsx b/src/components/common/CardList/CardList.tsx
index 97f1707..fdd190d 100644
--- a/src/components/common/CardList/CardList.tsx
+++ b/src/components/common/CardList/CardList.tsx
@@ -1,4 +1,4 @@
-import { Center, Skeleton, StackProps, VStack } from "@chakra-ui/react";
+import { Box, Center, Skeleton, StackProps, VStack } from "@chakra-ui/react";
export type CardListProps<T> = {
items: T[];
@@ -30,7 +30,7 @@ export function CardList<T>({
justifyContent="space-between"
{...containerProps}
>
- <VStack w="100%" h="100%">
+ <VStack data-testid="card-list" w="100%" h="100%" flexGrow={2}>
{isLoading
? Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={i} w="100%" h={`${estimatedItemHeight ?? 50}px`} />
@@ -42,7 +42,7 @@ export function CardList<T>({
</Center>
)}
</VStack>
- {children}
+ <Box w="100%">{children}</Box>
</VStack>
);
}
diff --git a/src/components/common/EditableInput/EditableInput2.tsx b/src/components/common/EditableInput/EditableInput2.tsx
index b00a9b7..cf82825 100644
--- a/src/components/common/EditableInput/EditableInput2.tsx
+++ b/src/components/common/EditableInput/EditableInput2.tsx
@@ -1,4 +1,5 @@
import { Input, InputProps, Text, TextProps } from "@chakra-ui/react";
+import * as Sentry from "@sentry/react";
import { useEffect, useRef, useState } from "react";
import {
InputConverter,
@@ -127,7 +128,9 @@ export function EditableInput<T extends string | number>({
const val = converter.parse(e.target.value);
setDisplayValue(converter.format(val));
onChange?.(e.target.value, isMoney || isNumber ? val : undefined);
- } catch (e) {}
+ } catch (e) {
+ Sentry.captureException(e);
+ }
}}
{...inputProps}
/>
diff --git a/src/components/common/NumberInputWithSideSteppers.tsx b/src/components/common/NumberInputWithSideSteppers.tsx
index 3505f63..55ae98a 100644
--- a/src/components/common/NumberInputWithSideSteppers.tsx
+++ b/src/components/common/NumberInputWithSideSteppers.tsx
@@ -33,23 +33,26 @@ export const NumberInputWithSideSteppers = ({
icon={<MinusIcon />}
variant={"ghost"}
onFocus={(e) => e.currentTarget.blur()}
- isDisabled={!isEditing || (min !== undefined && props.value <= min)}
onClick={() => {
- const newValue = props.value - step;
- if (min === undefined || newValue >= min) {
- props.onChange(newValue);
- }
+ let value = props.value - (step ?? 1);
+ if (value < -1000) value = -1000;
+ if (value > 1000) value = 1000;
+ return props.onChange(value);
}}
/>
<NumberInput
- isDisabled={!isEditing}
+ name="quantity"
value={props.value}
defaultValue={props.value}
precision={props.precision}
- min={min}
+ max={1000}
+ min={-1000}
width={"auto"}
onChange={(valueString) => {
- props.onChange(parseInt(valueString));
+ let value = parseInt(valueString);
+ if (value > 1000) value = 1000;
+ if (value < -1000) value = -1000;
+ props.onChange(value);
}}
>
<NumberInputField onFocus={(item) => item.currentTarget.select()} />
@@ -63,10 +66,11 @@ export const NumberInputWithSideSteppers = ({
onFocus={(e) => e.currentTarget.blur()}
isDisabled={!isEditing || (max !== undefined && props.value >= max)}
onClick={() => {
- const newValue = props.value + step;
- if (max === undefined || newValue <= max) {
- props.onChange(newValue);
- }
+ let value = props.value + (step ?? 1);
+
+ if (value < -1000) value = -1000;
+ if (value > 1000) value = 1000;
+ return props.onChange(value);
}}
/>
</HStack>
diff --git a/src/components/common/RadioCard/RadioCardV1.tsx b/src/components/common/RadioCard/RadioCardV1.tsx
new file mode 100644
index 0000000..7f7b38e
--- /dev/null
+++ b/src/components/common/RadioCard/RadioCardV1.tsx
@@ -0,0 +1,40 @@
+import { Box, useRadio, UseRadioProps } from "@chakra-ui/react";
+
+export interface RadioCardV1Props extends UseRadioProps {
+ children: React.ReactNode;
+}
+
+export const RadioCardV1: React.FC<RadioCardV1Props> = ({
+ children,
+ ...props
+}) => {
+ const { getInputProps, getRadioProps } = useRadio(props);
+
+ const input = getInputProps();
+ const checkbox = getRadioProps();
+
+ return (
+ <Box as="label">
+ <input {...input} />
+ <Box
+ {...checkbox}
+ cursor="pointer"
+ borderWidth="1px"
+ borderRadius="md"
+ boxShadow="md"
+ _checked={{
+ bg: "blue.600",
+ color: "white",
+ borderColor: "blue.600",
+ }}
+ _focus={{
+ boxShadow: "outline",
+ }}
+ px={5}
+ py={1}
+ >
+ {children}
+ </Box>
+ </Box>
+ );
+};
diff --git a/src/components/common/StyledPaginationControls/PaginationComponents.tsx b/src/components/common/StyledPaginationControls/PaginationComponents.tsx
index bc8a6f0..f5ed582 100644
--- a/src/components/common/StyledPaginationControls/PaginationComponents.tsx
+++ b/src/components/common/StyledPaginationControls/PaginationComponents.tsx
@@ -57,6 +57,7 @@ const PaginationPrevious: React.FC<LinkProps & { isDisabled?: boolean }> = ({
>
<IconButton
variant={"outline"}
+ data-testid="pagination-previous"
aria-label="Go to previous page"
icon={<ChevronLeftIcon />}
isDisabled={isDisabled}
@@ -73,6 +74,7 @@ const PaginationNext: React.FC<LinkProps & { isDisabled?: boolean }> = ({
<Link {...props}>
<IconButton
variant={"outline"}
+ data-testid="pagination-next"
aria-label="Go to next page"
icon={<ChevronRightIcon />}
isDisabled={isDisabled}
diff --git a/src/components/navbar/PopRegisterButton.tsx b/src/components/navbar/PopRegisterButton.tsx
index e1182da..736fafd 100644
--- a/src/components/navbar/PopRegisterButton.tsx
+++ b/src/components/navbar/PopRegisterButton.tsx
@@ -8,8 +8,9 @@ export const PopRegisterButton = () => {
const [device, cashDrawerOnOpen] = useCashDrawer();
const [entity] = useEntitySelected();
+ // TODO: remove Knotty Pine check after RBAC setup
// If there isn't a device, return null
- if (!device) return null;
+ if (!device || entity.id === 5) return null;
return (
<Tooltip hasArrow label={"Pop cash drawer"} placement={"bottom"}>
diff --git a/src/components/navbar/StoreInfo.tsx b/src/components/navbar/StoreInfo.tsx
index 7620370..e165dd4 100644
--- a/src/components/navbar/StoreInfo.tsx
+++ b/src/components/navbar/StoreInfo.tsx
@@ -9,7 +9,7 @@ export const StoreInfo: React.FC<StoreInfoProps> = ({}) => {
const [entity] = useEntitySelected();
return (
- <VStack spacing={0} alignItems="end">
+ <VStack data-testid="store-info" spacing={0} alignItems="end">
<Text>
<strong>{entity?.name}</strong>
</Text>
diff --git a/src/components/navbar/StoreInfoWithSelect.tsx b/src/components/navbar/StoreInfoWithSelect.tsx
index 99193bc..d54d7e9 100644
--- a/src/components/navbar/StoreInfoWithSelect.tsx
+++ b/src/components/navbar/StoreInfoWithSelect.tsx
@@ -1,5 +1,12 @@
import { ChevronDownIcon } from "@chakra-ui/icons";
-import { Button, Menu, MenuButton, MenuItem, MenuList } from "@chakra-ui/react";
+import {
+ Button,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Portal,
+} from "@chakra-ui/react";
import { useAuthorization } from "../../context/AuthorizationContext/AuthorizationContext";
import { useEntitySelected } from "../../context/EntityProvider";
import { useRegisterProvider } from "../../context/RegisterProvider";
@@ -18,27 +25,35 @@ export const StoreInfoWithSelect: React.FC<StoreInfoWithSelectProps> = ({}) => {
}
return (
- <Menu isLazy>
- <MenuButton as={Button} variant="outline" rightIcon={<ChevronDownIcon />}>
+ <Menu>
+ <MenuButton
+ data-testid="store-info-select"
+ as={Button}
+ variant="outline"
+ rightIcon={<ChevronDownIcon />}
+ onFocus={(e) => e.currentTarget.blur()}
+ >
<StoreInfo />
</MenuButton>
- <MenuList>
- {authorizedUser?.user?.entities?.map((entity: UserEntityAuth) => (
- <MenuItem
- key={entity.entity.id}
- onClick={() => {
- setSelectedEntity(entity.entity);
- setRegister({
- registerNumber: entity.entity.registers[0].registerId,
- isRegisterOpen: true,
- openDateTime: new Date().toISOString(),
- });
- }}
- >
- {entity.entity.name}
- </MenuItem>
- ))}
- </MenuList>
+ <Portal>
+ <MenuList>
+ {authorizedUser?.user?.entities?.map((entity: UserEntityAuth) => (
+ <MenuItem
+ key={entity.entity.id}
+ onClick={() => {
+ setSelectedEntity(entity.entity);
+ setRegister({
+ registerNumber: entity.entity.registers[0].registerId,
+ isRegisterOpen: true,
+ openDateTime: new Date().toISOString(),
+ });
+ }}
+ >
+ {entity.entity.name}
+ </MenuItem>
+ ))}
+ </MenuList>
+ </Portal>
</Menu>
);
};
diff --git a/src/components/promotions/PromotionMatcher/PromotionMatcherForm.tsx b/src/components/promotions/PromotionMatcher/PromotionMatcherForm.tsx
index 70afdcb..d169c26 100644
--- a/src/components/promotions/PromotionMatcher/PromotionMatcherForm.tsx
+++ b/src/components/promotions/PromotionMatcher/PromotionMatcherForm.tsx
@@ -75,7 +75,7 @@ export const PromotionMatcherForm: React.FC<PromotionMatcherFormProps> = ({
}, [defaultMatchers, matchers, uncontrolledMatchers.length]);
return (
- <VStack alignItems={"flex-start"} w="100%">
+ <VStack alignItems={"flex-start"} w="100%" h="100%">
<CardList
emptyText="No matchers"
items={displayedMatchers}
diff --git a/src/components/promotions/PromotionsForm.tsx b/src/components/promotions/PromotionsForm.tsx
index 48453a0..75e2766 100644
--- a/src/components/promotions/PromotionsForm.tsx
+++ b/src/components/promotions/PromotionsForm.tsx
@@ -139,11 +139,7 @@ export const PromotionsForm: React.FC<PromotionsFormProps> = ({
}}
/>
</HStack>
- <HStack
- w="100%"
- justifyContent="space-between"
- alignItems="flex-start"
- >
+ <HStack w="100%" justifyContent="space-between" alignItems="stretch">
<VStack alignItems="flex-start" w="50%">
<FormLabel>Include Items</FormLabel>
<PromotionMatcherForm
diff --git a/src/components/promotions/PromotionsList.tsx b/src/components/promotions/PromotionsList.tsx
index f74af5d..d1788a6 100644
--- a/src/components/promotions/PromotionsList.tsx
+++ b/src/components/promotions/PromotionsList.tsx
@@ -65,6 +65,10 @@ export const PromotionsList: React.FC<PromotionsListProps> = ({
return (
<VStack w="100%" h="100%" justifyContent="space-between" flexGrow={2}>
<CardList
+ containerProps={{
+ // @ts-ignore
+ "data-testid": "promotions-list",
+ }}
isLoading={isLoading}
items={promotions}
estimatedItemHeight={100}
diff --git a/src/config/ItemsApi.ts b/src/config/ItemsApi.ts
index ce0b1f4..e06baf9 100644
--- a/src/config/ItemsApi.ts
+++ b/src/config/ItemsApi.ts
@@ -1,7 +1,7 @@
-import * as Sentry from "@sentry/react";
import { useEffect, useState } from "react";
import { useUserAuth } from "../context/AuthenticationContext/AuthenticationContext";
import { Canny } from "../model/Canny";
+import { Cart } from "../model/Cart";
import { Department } from "../model/Department";
import { Entity } from "../model/Entity";
import { GiftCardApi } from "../model/GiftCardApi";
@@ -56,10 +56,6 @@ export function useBackendAxios() {
}
return config;
});
- i.instance.interceptors.response.use(undefined, (error) => {
- Sentry.captureException(error);
- return Promise.reject(error);
- });
setAxiosInstance(i);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [firebaseAuth.currentUser]);
@@ -199,9 +195,21 @@ export function useInvoiceApi() {
return api;
}
+export function useCartApi() {
+ const [api, setApi] = useState<Cart>();
+ const axiosInstance = axios;
+
+ useEffect(() => {
+ if (!axiosInstance) return;
+ setApi(new Cart(axiosInstance));
+ }, [axiosInstance]);
+
+ return api;
+}
+
export function usePromotionApi() {
const [api, setApi] = useState<PromotionApi>();
- const axiosInstance = useBackendAxios();
+ const axiosInstance = axios;
useEffect(() => {
if (!axiosInstance) return;
diff --git a/src/context/AuthorizationContext/AuthorizationContext.tsx b/src/context/AuthorizationContext/AuthorizationContext.tsx
index 6f46796..9dc3f72 100644
--- a/src/context/AuthorizationContext/AuthorizationContext.tsx
+++ b/src/context/AuthorizationContext/AuthorizationContext.tsx
@@ -1,3 +1,4 @@
+import { Button } from "@chakra-ui/react";
import { jwtDecode } from "jwt-decode";
import {
createContext,
@@ -13,6 +14,7 @@ import {
firebaseAuthStore,
} from "../../config/Firebase/firebase";
import { AuthorizedUserDTO, EntityDTO } from "../../model/data-contracts";
+import { UnauthorizedPage } from "../../pages/Auth/UnauthorizedPage";
import { ErrorPage } from "../../pages/ErrorPage";
import { Permission } from "../../utils/Permission";
import { useUserAuth } from "../AuthenticationContext/AuthenticationContext";
@@ -200,7 +202,21 @@ export const AuthorizationContextProvider = ({
}, [isAuthenticationLoading, refreshPermissions]);
if (error) {
- return <ErrorPage error={error} />;
+ if (error.errorCode === 401) {
+ return (
+ <UnauthorizedPage showNavbar={false} showBackButton={false}>
+ <Button
+ onClick={async () => {
+ await handleLogout();
+ return window.location.reload();
+ }}
+ >
+ Logout
+ </Button>
+ </UnauthorizedPage>
+ );
+ }
+ return <ErrorPage error={error.message} />;
}
return (
diff --git a/src/context/AuthorizationContext/useAuthorizeEmployee.tsx b/src/context/AuthorizationContext/useAuthorizeEmployee.tsx
index 789c88d..64a3308 100644
--- a/src/context/AuthorizationContext/useAuthorizeEmployee.tsx
+++ b/src/context/AuthorizationContext/useAuthorizeEmployee.tsx
@@ -1,3 +1,4 @@
+import * as Sentry from "@sentry/react";
import { AxiosError } from "axios";
import { useCallback, useState } from "react";
import { useUserApi } from "../../config/ItemsApi";
@@ -35,8 +36,10 @@ export const useAuthorizeEmployee = () => {
const error = e as AxiosError;
if (error.response?.status === 401) {
setError("Invalid PIN");
+ return;
}
}
+ Sentry.captureException(e);
}
};
return [authorizeEmployee, { isLoading, error, clear }] as const;
diff --git a/src/context/AuthorizationContext/useAuthorizeUser.tsx b/src/context/AuthorizationContext/useAuthorizeUser.tsx