ekyc

Electronic KYC process with uploading ID document using OAuth 2.1 (experimental)
Log | Files | Refs | README | LICENSE

photo_capture_input.tsx (3279B)


      1 import { useComputed, useSignal } from "@preact/signals";
      2 import { useEffect, useRef } from "preact/hooks";
      3 import { ComponentChildren, JSX } from "preact";
      4 
      5 export type PhotoCaptureInputProps = {
      6   capture?: unknown;
      7   retry?: unknown;
      8   send?: unknown;
      9   children: ComponentChildren;
     10   camera: "user" | "environment";
     11 };
     12 
     13 export function PhotoCaptureInput(props: PhotoCaptureInputProps) {
     14   const image = useSignal<string | undefined>(undefined);
     15   const videoRef = useRef<HTMLVideoElement>(null);
     16   const canvasRef = useRef<HTMLCanvasElement>(null);
     17   const unauthorized = useSignal(true);
     18   const capturedShow = useComputed(() =>
     19     image.value !== undefined ? undefined : "display:none;"
     20   );
     21   const capturedHide = useComputed(() =>
     22     image.value !== undefined ? "display:none;" : undefined
     23   );
     24 
     25   useEffect(() => {
     26     if (videoRef.current && canvasRef.current) {
     27       navigator.mediaDevices.getUserMedia({
     28         video: { facingMode: props.camera },
     29         audio: false,
     30       })
     31         .then((stream) => {
     32           unauthorized.value = false;
     33           videoRef.current!.srcObject = stream;
     34         })
     35         .catch(() => {
     36           unauthorized.value = true;
     37         });
     38     }
     39   }, [videoRef.current, canvasRef.current]);
     40 
     41   const capture = () => {
     42     if (!videoRef.current || !canvasRef.current!) return;
     43     const rect = videoRef.current.getBoundingClientRect();
     44     canvasRef.current.width = rect.width;
     45     canvasRef.current.height = rect.height;
     46     const context = canvasRef.current.getContext("2d")!;
     47     context.drawImage(videoRef.current, 0, 0, rect.width, rect.height);
     48     image.value = String(canvasRef.current.toDataURL("image/png"));
     49   };
     50 
     51   const retry = () => {
     52     image.value = undefined;
     53   };
     54 
     55   const submit = (event: JSX.TargetedMouseEvent<HTMLButtonElement>) => {
     56     event.currentTarget.form?.submit();
     57   };
     58 
     59   return (
     60     <fieldset>
     61       <section aria-describedby="legend">
     62         <input type="hidden" name="picture" value={image} />
     63         <video
     64           autoPlay
     65           playsinline
     66           ref={videoRef}
     67           style={useComputed(() =>
     68             image.value !== undefined
     69               ? "width:100%; display:none;"
     70               : "width:100%;"
     71           )}
     72         />
     73         <canvas
     74           ref={canvasRef}
     75           style={useComputed(() =>
     76             image.value === undefined ? "display: none" : ""
     77           )}
     78         />
     79         <small>{props.children}</small>
     80       </section>
     81       <div role="group">
     82         <button
     83           type="button"
     84           className="secondary"
     85           style={capturedShow}
     86           onClick={debounce(retry)}
     87         >
     88           {props.retry ?? "Retry"}
     89         </button>
     90         <button
     91           type="button"
     92           disabled={unauthorized}
     93           style={capturedHide}
     94           onClick={debounce(capture)}
     95         >
     96           {props.capture ?? "Capture"}
     97         </button>
     98         <button
     99           type="submit"
    100           style={capturedShow}
    101           onClick={submit}
    102         >
    103           {props.send ?? "Send"}
    104         </button>
    105       </div>
    106     </fieldset>
    107   );
    108 }
    109 
    110 function debounce<A extends unknown[]>(fn: (...args: A) => unknown) {
    111   let timer: number;
    112   return (...args: A) => {
    113     clearTimeout(timer);
    114     timer = setTimeout(fn, 300, ...args);
    115   };
    116 }