/ client / src / components / qrCodeScanner.tsx
qrCodeScanner.tsx
 1  import { CameraOutlined } from "@ant-design/icons";
 2  import { useTranslate } from "@refinedev/core";
 3  import { IDetectedBarcode, Scanner } from "@yudiel/react-qr-scanner";
 4  import { FloatButton, Modal, Space } from "antd";
 5  import { useState } from "react";
 6  import { useNavigate } from "react-router";
 7  
 8  const QRCodeScannerModal = () => {
 9    const [visible, setVisible] = useState(false);
10    const [lastError, setLastError] = useState<string | null>(null);
11    const t = useTranslate();
12    const navigate = useNavigate();
13  
14    const onScan = (detectedCodes: IDetectedBarcode[]) => {
15      if (detectedCodes.length === 0) {
16        return;
17      }
18      const result = detectedCodes[0].rawValue;
19  
20      // Check for the spoolman ID format
21      const match = result.match(/^web\+spoolman:s-(?<id>[0-9]+)$/i);
22      if (match && match.groups) {
23        setVisible(false);
24        navigate(`/spool/show/${match.groups.id}`);
25      }
26      const fullURLmatch = result.match(/^https?:\/\/[^/]+\/spool\/show\/(?<id>[0-9]+)$/i);
27      if (fullURLmatch && fullURLmatch.groups) {
28        setVisible(false);
29        navigate(`/spool/show/${fullURLmatch.groups.id}`);
30      }
31    };
32  
33    return (
34      <>
35        <FloatButton type="primary" onClick={() => setVisible(true)} icon={<CameraOutlined />} shape="circle" />
36        <Modal open={visible} destroyOnHidden onCancel={() => setVisible(false)} footer={null} title={t("scanner.title")}>
37          <Space direction="vertical" style={{ width: "100%" }}>
38            <p>{t("scanner.description")}</p>
39            <Scanner
40              constraints={{
41                facingMode: "environment",
42              }}
43              onScan={onScan}
44              formats={["qr_code"]}
45              onError={(err: unknown) => {
46                const error = err as Error;
47                console.error(error);
48                if (error.name === "NotAllowedError") {
49                  setLastError(t("scanner.error.notAllowed"));
50                } else if (
51                  error.name === "InsecureContextError" ||
52                  (location.protocol !== "https:" && navigator.mediaDevices === undefined)
53                ) {
54                  setLastError(t("scanner.error.insecureContext"));
55                } else if (error.name === "StreamApiNotSupportedError") {
56                  setLastError(t("scanner.error.streamApiNotSupported"));
57                } else if (error.name === "NotReadableError") {
58                  setLastError(t("scanner.error.notReadable"));
59                } else if (error.name === "NotFoundError") {
60                  setLastError(t("scanner.error.notFound"));
61                } else {
62                  setLastError(t("scanner.error.unknown", { error: error.name }));
63                }
64              }}
65            >
66              {lastError && (
67                <div
68                  style={{
69                    position: "absolute",
70                    textAlign: "center",
71                    width: "100%",
72                    top: "50%",
73                  }}
74                >
75                  <p>{lastError}</p>
76                </div>
77              )}
78            </Scanner>
79          </Space>
80        </Modal>
81      </>
82    );
83  };
84  
85  export default QRCodeScannerModal;