Updated react qr scanner

This commit is contained in:
Donkie
2026-01-06 13:05:54 +01:00
parent b627e151a8
commit cda756557d
3 changed files with 110 additions and 62 deletions

120
client/package-lock.json generated
View File

@@ -20,7 +20,7 @@
"@types/loadable__component": "^5.13.10", "@types/loadable__component": "^5.13.10",
"@types/lodash": "^4.17.21", "@types/lodash": "^4.17.21",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@yudiel/react-qr-scanner": "^1.2.10", "@yudiel/react-qr-scanner": "^2.5.0",
"axios": "^1.13.2", "axios": "^1.13.2",
"html-to-image": "^1.11.13", "html-to-image": "^1.11.13",
"i18next": "^25.7.3", "i18next": "^25.7.3",
@@ -4511,6 +4511,12 @@
"@babel/types": "^7.28.2" "@babel/types": "^7.28.2"
} }
}, },
"node_modules/@types/emscripten": {
"version": "1.41.5",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz",
"integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==",
"license": "MIT"
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -4905,40 +4911,19 @@
} }
}, },
"node_modules/@yudiel/react-qr-scanner": { "node_modules/@yudiel/react-qr-scanner": {
"version": "1.2.10", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/@yudiel/react-qr-scanner/-/react-qr-scanner-1.2.10.tgz", "resolved": "https://registry.npmjs.org/@yudiel/react-qr-scanner/-/react-qr-scanner-2.5.0.tgz",
"integrity": "sha512-I94AyrEu/2oZxXT6TEOK9+K2IfjqfPmic/GUah7alMIxfgXP9rs9+IZE56JhQr4pZ7Iz2RUC4JFfeaysFYNEIA==", "integrity": "sha512-wVWvm0z5kGGc3tiMcxyr2aji3C6aU5K3Q7R08MOdyFDFkvMbFMF1Hz5P5WFYW+zuuqxDPBcRZPCvmVs6jBheTQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@zxing/library": "^0.20.0" "barcode-detector": "3.0.8",
"webrtc-adapter": "9.0.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^18.0.0", "react": "^17 || ^18 || ^19",
"react-dom": "^18.0.0" "react-dom": "^17 || ^18 || ^19"
} }
}, },
"node_modules/@zxing/library": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.20.0.tgz",
"integrity": "sha512-6Ev6rcqVjMakZFIDvbUf0dtpPGeZMTfyxYg4HkVWioWeN7cRcnUWT3bU6sdohc82O1nPXcjq6WiGfXX2Pnit6A==",
"license": "MIT",
"dependencies": {
"ts-custom-error": "^3.2.1"
},
"engines": {
"node": ">= 10.4.0"
},
"optionalDependencies": {
"@zxing/text-encoding": "~0.9.0"
}
},
"node_modules/@zxing/text-encoding": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
"license": "(Unlicense OR Apache-2.0)",
"optional": true
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -5377,6 +5362,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/barcode-detector": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-3.0.8.tgz",
"integrity": "sha512-Z9jzzE8ngEDyN9EU7lWdGgV07mcnEQnrX8W9WecXDqD2v+5CcVjt9+a134a5zb+kICvpsrDx6NYA6ay4LGFs8A==",
"license": "MIT",
"dependencies": {
"zxing-wasm": "2.2.4"
}
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -12555,6 +12549,12 @@
"compute-scroll-into-view": "^3.0.2" "compute-scroll-into-view": "^3.0.2"
} }
}, },
"node_modules/sdp": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.1.tgz",
"integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==",
"license": "MIT"
},
"node_modules/section-matter": { "node_modules/section-matter": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
@@ -13399,6 +13399,18 @@
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
}, },
"node_modules/tagged-tag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
"integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/temp": { "node_modules/temp": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
@@ -13601,15 +13613,6 @@
"typescript": ">=4.8.4" "typescript": ">=4.8.4"
} }
}, },
"node_modules/ts-custom-error": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz",
"integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -14317,6 +14320,19 @@
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/webrtc-adapter": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.3.tgz",
"integrity": "sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==",
"license": "BSD-3-Clause",
"dependencies": {
"sdp": "^3.2.0"
},
"engines": {
"node": ">=6.0.0",
"npm": ">=3.10.0"
}
},
"node_modules/whatwg-url": { "node_modules/whatwg-url": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -15036,6 +15052,34 @@
"type": "github", "type": "github",
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
},
"node_modules/zxing-wasm": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-2.2.4.tgz",
"integrity": "sha512-1gq5zs4wuNTs5umWLypzNNeuJoluFvwmvjiiT3L9z/TMlVveeJRWy7h90xyUqCe+Qq0zL0w7o5zkdDMWDr9aZA==",
"license": "MIT",
"dependencies": {
"@types/emscripten": "^1.41.5",
"type-fest": "^5.2.0"
},
"peerDependencies": {
"@types/emscripten": ">=1.39.6"
}
},
"node_modules/zxing-wasm/node_modules/type-fest": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz",
"integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==",
"license": "(MIT OR CC0-1.0)",
"dependencies": {
"tagged-tag": "^1.0.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
} }
} }
} }

View File

@@ -19,7 +19,7 @@
"@types/loadable__component": "^5.13.10", "@types/loadable__component": "^5.13.10",
"@types/lodash": "^4.17.21", "@types/lodash": "^4.17.21",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@yudiel/react-qr-scanner": "^1.2.10", "@yudiel/react-qr-scanner": "^2.5.0",
"axios": "^1.13.2", "axios": "^1.13.2",
"html-to-image": "^1.11.13", "html-to-image": "^1.11.13",
"i18next": "^25.7.3", "i18next": "^25.7.3",

View File

@@ -1,6 +1,6 @@
import { CameraOutlined } from "@ant-design/icons"; import { CameraOutlined } from "@ant-design/icons";
import { useTranslate } from "@refinedev/core"; import { useTranslate } from "@refinedev/core";
import { QrScanner } from "@yudiel/react-qr-scanner"; import { IDetectedBarcode, Scanner } from "@yudiel/react-qr-scanner";
import { FloatButton, Modal, Space } from "antd"; import { FloatButton, Modal, Space } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
@@ -11,7 +11,12 @@ const QRCodeScannerModal: React.FC = () => {
const t = useTranslate(); const t = useTranslate();
const navigate = useNavigate(); const navigate = useNavigate();
const onScan = (result: string) => { const onScan = (detectedCodes: IDetectedBarcode[]) => {
if (detectedCodes.length === 0) {
return;
}
const result = detectedCodes[0].rawValue;
// Check for the spoolman ID format // Check for the spoolman ID format
const match = result.match(/^web\+spoolman:s-(?<id>[0-9]+)$/); const match = result.match(/^web\+spoolman:s-(?<id>[0-9]+)$/);
if (match && match.groups) { if (match && match.groups) {
@@ -28,31 +33,17 @@ const QRCodeScannerModal: React.FC = () => {
return ( return (
<> <>
<FloatButton type="primary" onClick={() => setVisible(true)} icon={<CameraOutlined />} shape="circle" /> <FloatButton type="primary" onClick={() => setVisible(true)} icon={<CameraOutlined />} shape="circle" />
<Modal open={visible} destroyOnClose onCancel={() => setVisible(false)} footer={null} title={t("scanner.title")}> <Modal open={visible} destroyOnHidden onCancel={() => setVisible(false)} footer={null} title={t("scanner.title")}>
<Space direction="vertical" style={{ width: "100%" }}> <Space direction="vertical" style={{ width: "100%" }}>
<p>{t("scanner.description")}</p> <p>{t("scanner.description")}</p>
<QrScanner <Scanner
constraints={{ constraints={{
facingMode: "environment", facingMode: "environment",
}} }}
viewFinder={ onScan={onScan}
lastError formats={["qr_code"]}
? () => ( onError={(err: unknown) => {
<div const error = err as Error;
style={{
position: "absolute",
textAlign: "center",
width: "100%",
top: "50%",
}}
>
<p>{lastError}</p>
</div>
)
: undefined
}
onDecode={onScan}
onError={(error: Error) => {
console.error(error); console.error(error);
if (error.name === "NotAllowedError") { if (error.name === "NotAllowedError") {
setLastError(t("scanner.error.notAllowed")); setLastError(t("scanner.error.notAllowed"));
@@ -71,7 +62,20 @@ const QRCodeScannerModal: React.FC = () => {
setLastError(t("scanner.error.unknown", { error: error.name })); setLastError(t("scanner.error.unknown", { error: error.name }));
} }
}} }}
/> >
{lastError && (
<div
style={{
position: "absolute",
textAlign: "center",
width: "100%",
top: "50%",
}}
>
<p>{lastError}</p>
</div>
)}
</Scanner>
</Space> </Space>
</Modal> </Modal>
</> </>