@mostajs/ticketing 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/detect.route.d.ts +19 -0
- package/dist/api/detect.route.d.ts.map +1 -0
- package/dist/api/detect.route.js +31 -0
- package/dist/api/detect.route.js.map +1 -0
- package/dist/api/recognize.route.d.ts +32 -0
- package/dist/api/recognize.route.d.ts.map +1 -0
- package/dist/api/recognize.route.js +86 -0
- package/dist/api/recognize.route.js.map +1 -0
- package/dist/components/FaceDetector.d.ts +28 -0
- package/dist/components/FaceDetector.d.ts.map +1 -0
- package/dist/components/FaceDetector.js +184 -0
- package/dist/components/FaceDetector.js.map +1 -0
- package/dist/components/ScanResultCard.d.ts +10 -0
- package/dist/components/ScanResultCard.d.ts.map +1 -0
- package/dist/components/ScanResultCard.js +63 -0
- package/dist/components/ScanResultCard.js.map +1 -0
- package/dist/components/ScannerView.d.ts +9 -0
- package/dist/components/ScannerView.d.ts.map +1 -0
- package/dist/components/ScannerView.js +62 -0
- package/dist/components/ScannerView.js.map +1 -0
- package/dist/hooks/useCamera.d.ts +18 -0
- package/dist/hooks/useCamera.d.ts.map +1 -0
- package/dist/hooks/useCamera.js +74 -0
- package/dist/hooks/useCamera.js.map +1 -0
- package/dist/hooks/useFaceDetection.d.ts +22 -0
- package/dist/hooks/useFaceDetection.d.ts.map +1 -0
- package/dist/hooks/useFaceDetection.js +62 -0
- package/dist/hooks/useFaceDetection.js.map +1 -0
- package/dist/hooks/useScan.d.ts +9 -0
- package/dist/hooks/useScan.d.ts.map +1 -0
- package/dist/hooks/useScan.js +101 -0
- package/dist/hooks/useScan.js.map +1 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/audio.d.ts +10 -0
- package/dist/lib/audio.d.ts.map +1 -0
- package/dist/lib/audio.js +29 -0
- package/dist/lib/audio.js.map +1 -0
- package/dist/lib/face-api.d.ts +24 -0
- package/dist/lib/face-api.d.ts.map +1 -0
- package/dist/lib/face-api.js +66 -0
- package/dist/lib/face-api.js.map +1 -0
- package/dist/lib/face-matcher.d.ts +19 -0
- package/dist/lib/face-matcher.d.ts.map +1 -0
- package/dist/lib/face-matcher.js +53 -0
- package/dist/lib/face-matcher.js.map +1 -0
- package/dist/lib/face-utils.d.ts +21 -0
- package/dist/lib/face-utils.d.ts.map +1 -0
- package/dist/lib/face-utils.js +40 -0
- package/dist/lib/face-utils.js.map +1 -0
- package/dist/pages/ScanPage.d.ts +2 -0
- package/dist/pages/ScanPage.d.ts.map +1 -0
- package/dist/pages/ScanPage.js +32 -0
- package/dist/pages/ScanPage.js.map +1 -0
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +9 -3
- package/dist/register.js.map +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5 -2
- package/dist/server.js.map +1 -1
- package/dist/types/index.d.ts +115 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -2
- package/dist/types/index.js.map +1 -1
- package/i18n/fr/scan.json +25 -0
- package/package.json +81 -3
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the detect handler.
|
|
3
|
+
*/
|
|
4
|
+
export interface DetectHandlerConfig {
|
|
5
|
+
/** Check auth/permission — return null if OK, or a Response to deny */
|
|
6
|
+
checkAuth?: (req: Request) => Promise<Response | null>;
|
|
7
|
+
/** Check if face recognition is enabled (default: always true) */
|
|
8
|
+
isEnabled?: () => Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Factory for POST /api/face/detect
|
|
12
|
+
*
|
|
13
|
+
* Placeholder endpoint — face detection runs client-side via face-api.js.
|
|
14
|
+
* This route exists for permission gating and future server-side detection.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createDetectHandler(config?: DetectHandlerConfig): {
|
|
17
|
+
POST: (req: Request) => Promise<Response>;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=detect.route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.route.d.ts","sourceRoot":"","sources":["../../api/detect.route.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IACtD,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,GAAE,mBAAwB;gBACzC,OAAO;EAyBjC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// @mostajs/ticketing — Detect API route factory (from @mostajs/face)
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
/**
|
|
4
|
+
* Factory for POST /api/face/detect
|
|
5
|
+
*
|
|
6
|
+
* Placeholder endpoint — face detection runs client-side via face-api.js.
|
|
7
|
+
* This route exists for permission gating and future server-side detection.
|
|
8
|
+
*/
|
|
9
|
+
export function createDetectHandler(config = {}) {
|
|
10
|
+
async function POST(req) {
|
|
11
|
+
if (config.isEnabled) {
|
|
12
|
+
const enabled = await config.isEnabled();
|
|
13
|
+
if (!enabled) {
|
|
14
|
+
return Response.json({ error: { code: 'FEATURE_DISABLED', message: 'Face recognition is disabled' } }, { status: 403 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (config.checkAuth) {
|
|
18
|
+
const denied = await config.checkAuth(req);
|
|
19
|
+
if (denied)
|
|
20
|
+
return denied;
|
|
21
|
+
}
|
|
22
|
+
return Response.json({
|
|
23
|
+
data: {
|
|
24
|
+
message: 'Face detection runs client-side. Use the FaceDetector component or useFaceDetection hook.',
|
|
25
|
+
hint: 'For recognition, send the descriptor to POST /api/face/recognize',
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return { POST };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=detect.route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.route.js","sourceRoot":"","sources":["../../api/detect.route.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,wCAAwC;AAYxC;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAA8B,EAAE;IAClE,KAAK,UAAU,IAAI,CAAC,GAAY;QAC9B,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAA;YACxC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,QAAQ,CAAC,IAAI,CAClB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,8BAA8B,EAAE,EAAE,EAChF,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAA;YACH,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;YAC1C,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAA;QAC3B,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC;YACnB,IAAI,EAAE;gBACJ,OAAO,EAAE,2FAA2F;gBACpG,IAAI,EAAE,kEAAkE;aACzE;SACF,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Candidate shape: must have an id, faceDescriptor, and any extra fields.
|
|
3
|
+
*/
|
|
4
|
+
export interface FaceCandidate {
|
|
5
|
+
id: string;
|
|
6
|
+
faceDescriptor: number[];
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Configuration for the recognize handler.
|
|
11
|
+
*/
|
|
12
|
+
export interface RecognizeHandlerConfig {
|
|
13
|
+
/** Fetch all active candidates with a faceDescriptor from the database */
|
|
14
|
+
getCandidates: () => Promise<FaceCandidate[]>;
|
|
15
|
+
/** Check auth/permission — return null if OK, or a Response to deny */
|
|
16
|
+
checkAuth?: (req: Request) => Promise<Response | null>;
|
|
17
|
+
/** Get the matching distance threshold (default: 0.6) */
|
|
18
|
+
getThreshold?: () => Promise<number>;
|
|
19
|
+
/** Check if face recognition is enabled (default: always true) */
|
|
20
|
+
isEnabled?: () => Promise<boolean>;
|
|
21
|
+
/** Fields to return from the matched candidate (default: all except faceDescriptor) */
|
|
22
|
+
publicFields?: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Factory for POST /api/face/recognize
|
|
26
|
+
*
|
|
27
|
+
* Receives a 128-float face descriptor, searches candidates, returns best match.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createRecognizeHandler(config: RecognizeHandlerConfig): {
|
|
30
|
+
POST: (req: Request) => Promise<Response>;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=recognize.route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recognize.route.d.ts","sourceRoot":"","sources":["../../api/recognize.route.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,aAAa,EAAE,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IAC7C,uEAAuE;IACvE,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IACtD,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACpC,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAClC,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB;gBAC1C,OAAO;EA8FjC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @mostajs/ticketing — Recognize API route factory (from @mostajs/face)
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
import { findMatch } from '../lib/face-matcher';
|
|
4
|
+
/**
|
|
5
|
+
* Factory for POST /api/face/recognize
|
|
6
|
+
*
|
|
7
|
+
* Receives a 128-float face descriptor, searches candidates, returns best match.
|
|
8
|
+
*/
|
|
9
|
+
export function createRecognizeHandler(config) {
|
|
10
|
+
async function POST(req) {
|
|
11
|
+
// Check if enabled
|
|
12
|
+
if (config.isEnabled) {
|
|
13
|
+
const enabled = await config.isEnabled();
|
|
14
|
+
if (!enabled) {
|
|
15
|
+
return Response.json({ error: { code: 'FEATURE_DISABLED', message: 'Face recognition is disabled' } }, { status: 403 });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Check auth
|
|
19
|
+
if (config.checkAuth) {
|
|
20
|
+
const denied = await config.checkAuth(req);
|
|
21
|
+
if (denied)
|
|
22
|
+
return denied;
|
|
23
|
+
}
|
|
24
|
+
// Parse body
|
|
25
|
+
let body;
|
|
26
|
+
try {
|
|
27
|
+
body = await req.json();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return Response.json({ error: { code: 'VALIDATION_ERROR', message: 'Invalid JSON' } }, { status: 400 });
|
|
31
|
+
}
|
|
32
|
+
// Validate descriptor
|
|
33
|
+
const desc = body.faceDescriptor;
|
|
34
|
+
if (!Array.isArray(desc) ||
|
|
35
|
+
desc.length !== 128 ||
|
|
36
|
+
!desc.every((v) => typeof v === 'number')) {
|
|
37
|
+
return Response.json({ error: { code: 'VALIDATION_ERROR', message: 'faceDescriptor must be an array of 128 numbers' } }, { status: 400 });
|
|
38
|
+
}
|
|
39
|
+
// Get candidates
|
|
40
|
+
const candidates = await config.getCandidates();
|
|
41
|
+
if (candidates.length === 0) {
|
|
42
|
+
return Response.json({
|
|
43
|
+
data: { match: false, message: 'No candidates with face data' },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Find match
|
|
47
|
+
const threshold = config.getThreshold ? await config.getThreshold() : 0.6;
|
|
48
|
+
const result = findMatch(desc, candidates, threshold);
|
|
49
|
+
if (result) {
|
|
50
|
+
// Strip faceDescriptor from response
|
|
51
|
+
const { faceDescriptor: _fd, ...publicData } = result.match;
|
|
52
|
+
const filtered = config.publicFields
|
|
53
|
+
? Object.fromEntries(Object.entries(publicData).filter(([k]) => config.publicFields.includes(k) || k === 'id'))
|
|
54
|
+
: publicData;
|
|
55
|
+
return Response.json({
|
|
56
|
+
data: {
|
|
57
|
+
match: true,
|
|
58
|
+
distance: Math.round(result.distance * 1000) / 1000,
|
|
59
|
+
candidate: filtered,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// No match — still report best distance for debugging
|
|
64
|
+
let bestDistance = null;
|
|
65
|
+
for (const c of candidates) {
|
|
66
|
+
if (!c.faceDescriptor || c.faceDescriptor.length !== 128)
|
|
67
|
+
continue;
|
|
68
|
+
let sum = 0;
|
|
69
|
+
for (let i = 0; i < 128; i++) {
|
|
70
|
+
const diff = desc[i] - c.faceDescriptor[i];
|
|
71
|
+
sum += diff * diff;
|
|
72
|
+
}
|
|
73
|
+
const d = Math.sqrt(sum);
|
|
74
|
+
if (bestDistance === null || d < bestDistance)
|
|
75
|
+
bestDistance = d;
|
|
76
|
+
}
|
|
77
|
+
return Response.json({
|
|
78
|
+
data: {
|
|
79
|
+
match: false,
|
|
80
|
+
distance: bestDistance !== null ? Math.round(bestDistance * 1000) / 1000 : null,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return { POST };
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=recognize.route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recognize.route.js","sourceRoot":"","sources":["../../api/recognize.route.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface FaceDetectorProps {
|
|
2
|
+
/** Existing photo (base64) */
|
|
3
|
+
photo: string;
|
|
4
|
+
/** Callback when a photo is captured */
|
|
5
|
+
onCapture: (data: {
|
|
6
|
+
photo: string;
|
|
7
|
+
faceDescriptor: number[] | null;
|
|
8
|
+
}) => void;
|
|
9
|
+
/** Callback when photo is cleared */
|
|
10
|
+
onClear: () => void;
|
|
11
|
+
/** Verification mode: compare against existing descriptor */
|
|
12
|
+
verifyDescriptor?: number[];
|
|
13
|
+
/** Callback with verification result */
|
|
14
|
+
onVerifyResult?: (result: {
|
|
15
|
+
match: boolean;
|
|
16
|
+
distance: number;
|
|
17
|
+
} | null) => void;
|
|
18
|
+
/** Enable face detection (default: true) */
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
/** Match threshold (default: 0.6) */
|
|
21
|
+
threshold?: number;
|
|
22
|
+
/** Require face detected before capture (default: true) */
|
|
23
|
+
requireForCapture?: boolean;
|
|
24
|
+
/** Error callback instead of console.error */
|
|
25
|
+
onError?: (message: string) => void;
|
|
26
|
+
}
|
|
27
|
+
export default function FaceDetector({ photo, onCapture, onClear, verifyDescriptor, onVerifyResult, enabled, threshold, requireForCapture, onError, }: FaceDetectorProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=FaceDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaceDetector.d.ts","sourceRoot":"","sources":["../../components/FaceDetector.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,iBAAiB;IAChC,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,wCAAwC;IACxC,SAAS,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,CAAA;IAC7E,qCAAqC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,wCAAwC;IACxC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,KAAK,IAAI,CAAA;IAC9E,4CAA4C;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,KAAK,EACL,SAAS,EACT,OAAO,EACP,gBAAgB,EAChB,cAAc,EACd,OAAc,EACd,SAAe,EACf,iBAAwB,EACxB,OAAO,GACR,EAAE,iBAAiB,2CAiTnB"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// @mostajs/ticketing — FaceDetector component (from @mostajs/face)
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
'use client';
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
6
|
+
import { loadModels, detectFace, extractDescriptor } from '../lib/face-api';
|
|
7
|
+
import { compareFaces } from '../lib/face-matcher';
|
|
8
|
+
import { drawDetection } from '../lib/face-utils';
|
|
9
|
+
export default function FaceDetector({ photo, onCapture, onClear, verifyDescriptor, onVerifyResult, enabled = true, threshold = 0.6, requireForCapture = true, onError, }) {
|
|
10
|
+
const videoRef = useRef(null);
|
|
11
|
+
const canvasRef = useRef(null);
|
|
12
|
+
const overlayRef = useRef(null);
|
|
13
|
+
const animFrameRef = useRef(0);
|
|
14
|
+
const [streaming, setStreaming] = useState(false);
|
|
15
|
+
const [modelsReady, setModelsReady] = useState(false);
|
|
16
|
+
const [loadingModels, setLoadingModels] = useState(false);
|
|
17
|
+
const [faceDetected, setFaceDetected] = useState(false);
|
|
18
|
+
const [verifyResult, setVerifyResult] = useState(null);
|
|
19
|
+
const reportError = useCallback((msg) => {
|
|
20
|
+
if (onError)
|
|
21
|
+
onError(msg);
|
|
22
|
+
else
|
|
23
|
+
console.error('[FaceDetector]', msg);
|
|
24
|
+
}, [onError]);
|
|
25
|
+
// Load face-api models on mount
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!enabled)
|
|
28
|
+
return;
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
async function init() {
|
|
31
|
+
setLoadingModels(true);
|
|
32
|
+
try {
|
|
33
|
+
await loadModels();
|
|
34
|
+
if (!cancelled)
|
|
35
|
+
setModelsReady(true);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error('Face-api model loading error:', err);
|
|
39
|
+
if (!cancelled)
|
|
40
|
+
reportError('Impossible de charger les modeles de detection faciale');
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
if (!cancelled)
|
|
44
|
+
setLoadingModels(false);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
init();
|
|
48
|
+
return () => { cancelled = true; };
|
|
49
|
+
}, [enabled, reportError]);
|
|
50
|
+
// Real-time detection loop
|
|
51
|
+
const detectLoop = useCallback(async () => {
|
|
52
|
+
if (!videoRef.current || !overlayRef.current)
|
|
53
|
+
return;
|
|
54
|
+
if (videoRef.current.paused || videoRef.current.ended)
|
|
55
|
+
return;
|
|
56
|
+
const video = videoRef.current;
|
|
57
|
+
const overlay = overlayRef.current;
|
|
58
|
+
const detection = await detectFace(video);
|
|
59
|
+
setFaceDetected(!!detection);
|
|
60
|
+
if (!videoRef.current || !overlayRef.current)
|
|
61
|
+
return;
|
|
62
|
+
drawDetection(overlay, detection, video.videoWidth, video.videoHeight);
|
|
63
|
+
animFrameRef.current = requestAnimationFrame(detectLoop);
|
|
64
|
+
}, []);
|
|
65
|
+
const startCamera = useCallback(async () => {
|
|
66
|
+
setVerifyResult(null);
|
|
67
|
+
try {
|
|
68
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
69
|
+
video: { width: 320, height: 240, facingMode: 'user' },
|
|
70
|
+
});
|
|
71
|
+
if (!videoRef.current) {
|
|
72
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
videoRef.current.srcObject = stream;
|
|
76
|
+
await videoRef.current.play().catch(() => { });
|
|
77
|
+
setStreaming(true);
|
|
78
|
+
if (enabled && modelsReady) {
|
|
79
|
+
videoRef.current.onloadeddata = () => {
|
|
80
|
+
animFrameRef.current = requestAnimationFrame(detectLoop);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
reportError("Impossible d'acceder a la camera");
|
|
86
|
+
}
|
|
87
|
+
}, [enabled, modelsReady, detectLoop, reportError]);
|
|
88
|
+
const stopCamera = useCallback(() => {
|
|
89
|
+
cancelAnimationFrame(animFrameRef.current);
|
|
90
|
+
if (videoRef.current?.srcObject) {
|
|
91
|
+
const tracks = videoRef.current.srcObject.getTracks();
|
|
92
|
+
tracks.forEach((track) => track.stop());
|
|
93
|
+
videoRef.current.srcObject = null;
|
|
94
|
+
}
|
|
95
|
+
setStreaming(false);
|
|
96
|
+
setFaceDetected(false);
|
|
97
|
+
}, []);
|
|
98
|
+
// Cleanup on unmount
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
return () => {
|
|
101
|
+
cancelAnimationFrame(animFrameRef.current);
|
|
102
|
+
const video = videoRef.current;
|
|
103
|
+
if (video) {
|
|
104
|
+
video.pause();
|
|
105
|
+
const stream = video.srcObject;
|
|
106
|
+
if (stream) {
|
|
107
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
108
|
+
}
|
|
109
|
+
video.srcObject = null;
|
|
110
|
+
video.removeAttribute('src');
|
|
111
|
+
video.load();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}, []);
|
|
115
|
+
const capturePhoto = useCallback(async () => {
|
|
116
|
+
if (!videoRef.current || !canvasRef.current)
|
|
117
|
+
return;
|
|
118
|
+
const canvas = canvasRef.current;
|
|
119
|
+
const video = videoRef.current;
|
|
120
|
+
canvas.width = video.videoWidth;
|
|
121
|
+
canvas.height = video.videoHeight;
|
|
122
|
+
const ctx = canvas.getContext('2d');
|
|
123
|
+
if (!ctx)
|
|
124
|
+
return;
|
|
125
|
+
ctx.drawImage(video, 0, 0);
|
|
126
|
+
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
|
|
127
|
+
let descriptor = null;
|
|
128
|
+
if (enabled && modelsReady) {
|
|
129
|
+
try {
|
|
130
|
+
const raw = await extractDescriptor(canvas);
|
|
131
|
+
if (raw)
|
|
132
|
+
descriptor = Array.from(raw);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
console.error('Descriptor extraction error:', err);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
stopCamera();
|
|
139
|
+
onCapture({ photo: dataUrl, faceDescriptor: descriptor });
|
|
140
|
+
}, [enabled, modelsReady, stopCamera, onCapture]);
|
|
141
|
+
const verifyFace = useCallback(async () => {
|
|
142
|
+
if (!videoRef.current || !verifyDescriptor)
|
|
143
|
+
return;
|
|
144
|
+
try {
|
|
145
|
+
const raw = await extractDescriptor(videoRef.current);
|
|
146
|
+
if (!raw) {
|
|
147
|
+
setVerifyResult(null);
|
|
148
|
+
onVerifyResult?.(null);
|
|
149
|
+
reportError('Aucun visage detecte');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const distance = compareFaces(raw, verifyDescriptor);
|
|
153
|
+
const match = distance < threshold;
|
|
154
|
+
const result = { match, distance };
|
|
155
|
+
setVerifyResult(result);
|
|
156
|
+
onVerifyResult?.(result);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
console.error('Face verification error:', err);
|
|
160
|
+
reportError('Erreur lors de la verification');
|
|
161
|
+
}
|
|
162
|
+
}, [verifyDescriptor, onVerifyResult, threshold, reportError]);
|
|
163
|
+
const captureDisabled = enabled && requireForCapture && !faceDetected;
|
|
164
|
+
// Basic webcam mode (face detection disabled)
|
|
165
|
+
if (!enabled) {
|
|
166
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '1rem' }, children: [photo ? (_jsxs("div", { style: { position: 'relative' }, children: [_jsx("img", { src: photo, alt: "Photo", style: { width: '100%', borderRadius: '0.5rem' } }), _jsx("button", { type: "button", onClick: onClear, style: { position: 'absolute', top: 8, right: 8, background: '#ef4444', color: 'white', border: 'none', borderRadius: '50%', width: 24, height: 24, cursor: 'pointer', fontSize: 14, lineHeight: 1 }, children: "x" })] })) : (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '0.5rem' }, children: [_jsx("video", { ref: videoRef, style: { width: '100%', borderRadius: '0.5rem', display: streaming ? 'block' : 'none' }, autoPlay: true, playsInline: true, muted: true }), streaming ? (_jsxs("div", { style: { display: 'flex', gap: '0.5rem' }, children: [_jsx("button", { type: "button", onClick: capturePhoto, style: { flex: 1, padding: '0.5rem', cursor: 'pointer' }, children: "Capturer" }), _jsx("button", { type: "button", onClick: stopCamera, style: { padding: '0.5rem', cursor: 'pointer' }, children: "x" })] })) : (_jsx("button", { type: "button", onClick: startCamera, style: { width: '100%', padding: '0.5rem', cursor: 'pointer' }, children: "Prendre photo" }))] })), _jsx("canvas", { ref: canvasRef, style: { display: 'none' } })] }));
|
|
167
|
+
}
|
|
168
|
+
// Full face detection mode
|
|
169
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '1rem' }, children: [photo ? (_jsxs("div", { style: { position: 'relative' }, children: [_jsx("img", { src: photo, alt: "Photo", style: { width: '100%', borderRadius: '0.5rem' } }), _jsx("button", { type: "button", onClick: onClear, style: { position: 'absolute', top: 8, right: 8, background: '#ef4444', color: 'white', border: 'none', borderRadius: '50%', width: 24, height: 24, cursor: 'pointer', fontSize: 14, lineHeight: 1 }, children: "x" })] })) : (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '0.5rem' }, children: [_jsxs("div", { style: { position: 'relative', display: streaming ? 'block' : 'none' }, children: [_jsx("video", { ref: videoRef, style: { width: '100%', borderRadius: '0.5rem' }, autoPlay: true, playsInline: true, muted: true }), _jsx("canvas", { ref: overlayRef, style: { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' } }), _jsx("div", { style: {
|
|
170
|
+
position: 'absolute', bottom: 8, left: 8,
|
|
171
|
+
padding: '2px 8px', borderRadius: 4, fontSize: 12, fontWeight: 500,
|
|
172
|
+
background: faceDetected ? 'rgba(34,197,94,0.9)' : 'rgba(239,68,68,0.9)',
|
|
173
|
+
color: 'white',
|
|
174
|
+
}, children: faceDetected ? 'Visage detecte' : 'Aucun visage' })] }), streaming ? (_jsxs("div", { style: { display: 'flex', gap: '0.5rem' }, children: [verifyDescriptor ? (_jsx("button", { type: "button", onClick: verifyFace, disabled: !faceDetected, style: { flex: 1, padding: '0.5rem', cursor: faceDetected ? 'pointer' : 'not-allowed', opacity: faceDetected ? 1 : 0.5 }, children: "Verifier visage" })) : (_jsx("button", { type: "button", onClick: capturePhoto, disabled: captureDisabled, style: { flex: 1, padding: '0.5rem', cursor: captureDisabled ? 'not-allowed' : 'pointer', opacity: captureDisabled ? 0.5 : 1 }, children: captureDisabled ? 'Cadrez votre visage...' : 'Capturer' })), _jsx("button", { type: "button", onClick: stopCamera, style: { padding: '0.5rem', cursor: 'pointer' }, children: "x" })] })) : (_jsx("div", { children: loadingModels ? (_jsx("button", { type: "button", disabled: true, style: { width: '100%', padding: '0.5rem', opacity: 0.5 }, children: "Chargement detection faciale..." })) : (_jsx("button", { type: "button", onClick: startCamera, style: { width: '100%', padding: '0.5rem', cursor: 'pointer' }, children: verifyDescriptor ? 'Verifier visage' : 'Prendre photo' })) }))] })), verifyResult && (_jsx("div", { style: {
|
|
175
|
+
display: 'flex', alignItems: 'center', gap: '0.5rem',
|
|
176
|
+
padding: '0.75rem', borderRadius: '0.5rem', fontSize: 14, fontWeight: 500,
|
|
177
|
+
background: verifyResult.match ? '#f0fdf4' : '#fef2f2',
|
|
178
|
+
color: verifyResult.match ? '#15803d' : '#b91c1c',
|
|
179
|
+
border: `1px solid ${verifyResult.match ? '#bbf7d0' : '#fecaca'}`,
|
|
180
|
+
}, children: verifyResult.match
|
|
181
|
+
? `Visage verifie (confiance: ${Math.round((1 - verifyResult.distance) * 100)}%)`
|
|
182
|
+
: `Visage non reconnu (distance: ${verifyResult.distance.toFixed(2)})` })), _jsx("canvas", { ref: canvasRef, style: { display: 'none' } })] }));
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=FaceDetector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaceDetector.js","sourceRoot":"","sources":["../../components/FaceDetector.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ScanResultCardProps } from '../types/index';
|
|
2
|
+
/**
|
|
3
|
+
* Displays the result of a ticket scan (granted, denied, or reentry).
|
|
4
|
+
*/
|
|
5
|
+
export default function ScanResultCard({ data, t, renderExtra, }: ScanResultCardProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
/** Empty state placeholder when no scan result yet */
|
|
7
|
+
export declare function ScanEmptyState({ message }: {
|
|
8
|
+
message?: string;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=ScanResultCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScanResultCard.d.ts","sourceRoot":"","sources":["../../components/ScanResultCard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAkB,MAAM,gBAAgB,CAAA;AAazE;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,IAAI,EACJ,CAAgB,EAChB,WAAW,GACZ,EAAE,mBAAmB,2CA+GrB;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,2CAe/D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @mostajs/ticketing — ScanResultCard component (from @mostajs/scan)
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
'use client';
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { ScanLine } from 'lucide-react';
|
|
6
|
+
const colors = {
|
|
7
|
+
granted: { bg: '#f0fdf4', text: '#15803d', border: '#22c55e', badge: '#166534' },
|
|
8
|
+
reentry: { bg: '#eff6ff', text: '#1d4ed8', border: '#3b82f6', badge: '#1e40af' },
|
|
9
|
+
denied: { bg: '#fef2f2', text: '#dc2626', border: '#ef4444', badge: '#991b1b' },
|
|
10
|
+
};
|
|
11
|
+
function getColors(data) {
|
|
12
|
+
if (data.result === 'granted')
|
|
13
|
+
return data.isReentry ? colors.reentry : colors.granted;
|
|
14
|
+
return colors.denied;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Displays the result of a ticket scan (granted, denied, or reentry).
|
|
18
|
+
*/
|
|
19
|
+
export default function ScanResultCard({ data, t = (key) => key, renderExtra, }) {
|
|
20
|
+
const c = getColors(data);
|
|
21
|
+
return (_jsx("div", { style: {
|
|
22
|
+
border: `2px solid ${c.border}`,
|
|
23
|
+
borderRadius: 8,
|
|
24
|
+
overflow: 'hidden',
|
|
25
|
+
}, children: _jsxs("div", { style: { padding: 24 }, children: [_jsxs("div", { style: {
|
|
26
|
+
textAlign: 'center',
|
|
27
|
+
padding: 24,
|
|
28
|
+
borderRadius: 8,
|
|
29
|
+
backgroundColor: c.bg,
|
|
30
|
+
}, children: [_jsx("div", { style: { fontSize: 40, fontWeight: 700, marginBottom: 8, color: c.text }, children: data.result === 'granted' ? (data.isReentry ? '\u{1f504}' : '\u{2705}') : '\u{274c}' }), _jsx("div", { style: { fontSize: 24, fontWeight: 700, color: c.text }, children: data.isReentry
|
|
31
|
+
? t('scan.result.reentry')
|
|
32
|
+
: t(`scan.result.${data.result}`) }), data.isReentry && (_jsx("div", { style: { fontSize: 14, marginTop: 4, color: c.text }, children: t('scan.result.reentryHint') })), data.reason && (_jsx("div", { style: { fontSize: 14, marginTop: 8, color: colors.denied.text }, children: data.reason.startsWith('ticket_') || data.reason.startsWith('quota_') ||
|
|
33
|
+
data.reason.startsWith('access_') || data.reason.startsWith('client_') ||
|
|
34
|
+
data.reason === 'invalid_ticket'
|
|
35
|
+
? t(`scan.denyReasons.${data.reason}`)
|
|
36
|
+
: data.reason }))] }), data.client && (_jsxs("div", { style: { marginTop: 16 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 12 }, children: [data.client.photo ? (_jsx("img", { src: data.client.photo, alt: "", style: {
|
|
37
|
+
width: 48, height: 48, borderRadius: '50%', objectFit: 'cover',
|
|
38
|
+
} })) : (_jsx("div", { style: {
|
|
39
|
+
width: 48, height: 48, borderRadius: '50%',
|
|
40
|
+
backgroundColor: '#e5e7eb',
|
|
41
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
42
|
+
fontSize: 18, fontWeight: 700, color: '#6b7280',
|
|
43
|
+
}, children: data.client.name.charAt(0) })), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 700 }, children: data.client.name }), _jsx("div", { style: { fontSize: 14, color: '#6b7280' }, children: data.client.clientNumber })] })] }), data.ticket && (_jsxs("div", { style: { fontSize: 14, marginTop: 12 }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', marginBottom: 4 }, children: [_jsx("span", { style: { color: '#6b7280' }, children: t('scan.info.activity') }), _jsx("span", { style: { fontWeight: 500 }, children: data.ticket.activityName })] }), data.access && data.access.remainingQuota != null && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', marginBottom: 4 }, children: [_jsx("span", { style: { color: '#6b7280' }, children: t('scan.info.quotaRemaining') }), _jsx("span", { style: { fontWeight: 700, fontSize: 18 }, children: data.access.remainingQuota })] })), data.ticket.ticketType === 'cadeau' && (_jsx("span", { style: {
|
|
44
|
+
display: 'inline-block',
|
|
45
|
+
padding: '2px 8px',
|
|
46
|
+
borderRadius: 4,
|
|
47
|
+
backgroundColor: '#fef3c7',
|
|
48
|
+
color: '#92400e',
|
|
49
|
+
fontSize: 12,
|
|
50
|
+
fontWeight: 500,
|
|
51
|
+
}, children: t('tickets.types.cadeau') }))] })), renderExtra?.(data)] }))] }) }));
|
|
52
|
+
}
|
|
53
|
+
/** Empty state placeholder when no scan result yet */
|
|
54
|
+
export function ScanEmptyState({ message }) {
|
|
55
|
+
return (_jsxs("div", { style: {
|
|
56
|
+
border: '1px solid #e5e7eb',
|
|
57
|
+
borderRadius: 8,
|
|
58
|
+
padding: '48px 24px',
|
|
59
|
+
textAlign: 'center',
|
|
60
|
+
color: '#9ca3af',
|
|
61
|
+
}, children: [_jsx(ScanLine, { style: { width: 48, height: 48, margin: '0 auto 16px' } }), _jsx("p", { children: message || 'Scan a QR ticket to verify access' })] }));
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=ScanResultCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScanResultCard.js","sourceRoot":"","sources":["../../components/ScanResultCard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScannerViewProps } from '../types/index';
|
|
2
|
+
/**
|
|
3
|
+
* Self-contained QR scanner view with start/stop controls.
|
|
4
|
+
*
|
|
5
|
+
* Renders a camera view + controls. When a QR code is detected,
|
|
6
|
+
* it calls the configured API endpoint and fires `onResult`.
|
|
7
|
+
*/
|
|
8
|
+
export default function ScannerView({ apiEndpoint, onResult, onError, startLabel, stopLabel, soundFrequencies, }: ScannerViewProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=ScannerView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScannerView.d.ts","sourceRoot":"","sources":["../../components/ScannerView.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,WAAyB,EACzB,QAAQ,EACR,OAAO,EACP,UAA4B,EAC5B,SAAkB,EAClB,gBAAgB,GACjB,EAAE,gBAAgB,2CAyFlB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @mostajs/ticketing — ScannerView component (from @mostajs/scan)
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
'use client';
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { ScanLine, Camera, RefreshCw } from 'lucide-react';
|
|
6
|
+
import { useScan } from '../hooks/useScan';
|
|
7
|
+
/**
|
|
8
|
+
* Self-contained QR scanner view with start/stop controls.
|
|
9
|
+
*
|
|
10
|
+
* Renders a camera view + controls. When a QR code is detected,
|
|
11
|
+
* it calls the configured API endpoint and fires `onResult`.
|
|
12
|
+
*/
|
|
13
|
+
export default function ScannerView({ apiEndpoint = '/api/scan', onResult, onError, startLabel = 'Start Scanner', stopLabel = 'Stop', soundFrequencies, }) {
|
|
14
|
+
const { scanning, result, startScanner, stopScanner } = useScan({
|
|
15
|
+
apiEndpoint,
|
|
16
|
+
onResult,
|
|
17
|
+
onError,
|
|
18
|
+
soundFrequencies,
|
|
19
|
+
});
|
|
20
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(ScanLine, { style: { width: 20, height: 20 } }), _jsx("span", { className: "font-semibold", children: "Scanner" })] }), _jsx("div", { id: "qr-reader", style: {
|
|
21
|
+
width: '100%',
|
|
22
|
+
minHeight: 300,
|
|
23
|
+
backgroundColor: '#111827',
|
|
24
|
+
borderRadius: 8,
|
|
25
|
+
overflow: 'hidden',
|
|
26
|
+
} }), _jsxs("div", { style: { marginTop: 16, display: 'flex', gap: 8 }, children: [!scanning ? (_jsxs("button", { onClick: startScanner, style: {
|
|
27
|
+
flex: 1,
|
|
28
|
+
display: 'flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
justifyContent: 'center',
|
|
31
|
+
gap: 8,
|
|
32
|
+
padding: '10px 16px',
|
|
33
|
+
backgroundColor: '#0284c7',
|
|
34
|
+
color: 'white',
|
|
35
|
+
border: 'none',
|
|
36
|
+
borderRadius: 6,
|
|
37
|
+
fontSize: 14,
|
|
38
|
+
fontWeight: 500,
|
|
39
|
+
cursor: 'pointer',
|
|
40
|
+
}, children: [_jsx(Camera, { style: { width: 16, height: 16 } }), startLabel] })) : (_jsx("button", { onClick: stopScanner, style: {
|
|
41
|
+
flex: 1,
|
|
42
|
+
padding: '10px 16px',
|
|
43
|
+
backgroundColor: 'white',
|
|
44
|
+
color: '#374151',
|
|
45
|
+
border: '1px solid #d1d5db',
|
|
46
|
+
borderRadius: 6,
|
|
47
|
+
fontSize: 14,
|
|
48
|
+
fontWeight: 500,
|
|
49
|
+
cursor: 'pointer',
|
|
50
|
+
}, children: stopLabel })), result && (_jsx("button", { onClick: startScanner, style: {
|
|
51
|
+
display: 'flex',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
justifyContent: 'center',
|
|
54
|
+
padding: '10px 12px',
|
|
55
|
+
backgroundColor: 'white',
|
|
56
|
+
color: '#374151',
|
|
57
|
+
border: '1px solid #d1d5db',
|
|
58
|
+
borderRadius: 6,
|
|
59
|
+
cursor: 'pointer',
|
|
60
|
+
}, children: _jsx(RefreshCw, { style: { width: 16, height: 16 } }) }))] })] }));
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=ScannerView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScannerView.js","sourceRoot":"","sources":["../../components/ScannerView.tsx"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,wCAAwC;AACxC,YAAY,CAAA;;AAEZ,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAG1C;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,WAAW,GAAG,WAAW,EACzB,QAAQ,EACR,OAAO,EACP,UAAU,GAAG,eAAe,EAC5B,SAAS,GAAG,MAAM,EAClB,gBAAgB,GACC;IACjB,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;QAC9D,WAAW;QACX,QAAQ;QACR,OAAO;QACP,gBAAgB;KACjB,CAAC,CAAA;IAEF,OAAO,CACL,0BACE,eAAK,SAAS,EAAC,8BAA8B,aAC3C,KAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAI,EAC9C,eAAM,SAAS,EAAC,eAAe,wBAAe,IAC1C,EAEN,cACE,EAAE,EAAC,WAAW,EACd,KAAK,EAAE;oBACL,KAAK,EAAE,MAAM;oBACb,SAAS,EAAE,GAAG;oBACd,eAAe,EAAE,SAAS;oBAC1B,YAAY,EAAE,CAAC;oBACf,QAAQ,EAAE,QAAQ;iBACnB,GACD,EAEF,eAAK,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,aACnD,CAAC,QAAQ,CAAC,CAAC,CAAC,CACX,kBACE,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE;4BACL,IAAI,EAAE,CAAC;4BACP,OAAO,EAAE,MAAM;4BACf,UAAU,EAAE,QAAQ;4BACpB,cAAc,EAAE,QAAQ;4BACxB,GAAG,EAAE,CAAC;4BACN,OAAO,EAAE,WAAW;4BACpB,eAAe,EAAE,SAAS;4BAC1B,KAAK,EAAE,OAAO;4BACd,MAAM,EAAE,MAAM;4BACd,YAAY,EAAE,CAAC;4BACf,QAAQ,EAAE,EAAE;4BACZ,UAAU,EAAE,GAAG;4BACf,MAAM,EAAE,SAAS;yBAClB,aAED,KAAC,MAAM,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAI,EAC3C,UAAU,IACJ,CACV,CAAC,CAAC,CAAC,CACF,iBACE,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE;4BACL,IAAI,EAAE,CAAC;4BACP,OAAO,EAAE,WAAW;4BACpB,eAAe,EAAE,OAAO;4BACxB,KAAK,EAAE,SAAS;4BAChB,MAAM,EAAE,mBAAmB;4BAC3B,YAAY,EAAE,CAAC;4BACf,QAAQ,EAAE,EAAE;4BACZ,UAAU,EAAE,GAAG;4BACf,MAAM,EAAE,SAAS;yBAClB,YAEA,SAAS,GACH,CACV,EAEA,MAAM,IAAI,CACT,iBACE,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE;4BACL,OAAO,EAAE,MAAM;4BACf,UAAU,EAAE,QAAQ;4BACpB,cAAc,EAAE,QAAQ;4BACxB,OAAO,EAAE,WAAW;4BACpB,eAAe,EAAE,OAAO;4BACxB,KAAK,EAAE,SAAS;4BAChB,MAAM,EAAE,mBAAmB;4BAC3B,YAAY,EAAE,CAAC;4BACf,MAAM,EAAE,SAAS;yBAClB,YAED,KAAC,SAAS,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAI,GACxC,CACV,IACG,IACF,CACP,CAAA;AACH,CAAC"}
|