@asteby/storefront-react 0.22.0 → 0.24.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/account.cjs +88 -3
- package/dist/account.cjs.map +1 -1
- package/dist/account.d.cts +34 -2
- package/dist/account.d.ts +34 -2
- package/dist/account.js +89 -5
- package/dist/account.js.map +1 -1
- package/dist/{chunk-PPQEUTXJ.js → chunk-5XYQLOE3.js} +64 -8
- package/dist/chunk-5XYQLOE3.js.map +1 -0
- package/dist/{chunk-Q7UTJIKY.js → chunk-D5L7UZIA.js} +25 -4
- package/dist/chunk-D5L7UZIA.js.map +1 -0
- package/dist/fiscal-server.cjs +8 -9
- package/dist/fiscal-server.cjs.map +1 -1
- package/dist/fiscal-server.d.cts +8 -5
- package/dist/fiscal-server.d.ts +8 -5
- package/dist/fiscal-server.js +9 -10
- package/dist/fiscal-server.js.map +1 -1
- package/dist/fiscal.cjs +464 -24
- package/dist/fiscal.cjs.map +1 -1
- package/dist/fiscal.css +103 -0
- package/dist/fiscal.d.cts +120 -11
- package/dist/fiscal.d.ts +120 -11
- package/dist/fiscal.js +326 -23
- package/dist/fiscal.js.map +1 -1
- package/dist/{types-BnvSbV6t.d.cts → types-Cgx_65eM.d.cts} +14 -2
- package/dist/{types-BnvSbV6t.d.ts → types-Cgx_65eM.d.ts} +14 -2
- package/package.json +2 -2
- package/dist/chunk-PPQEUTXJ.js.map +0 -1
- package/dist/chunk-Q7UTJIKY.js.map +0 -1
package/dist/fiscal.cjs
CHANGED
|
@@ -146,6 +146,26 @@ function personTypeByLength(lengths) {
|
|
|
146
146
|
return Object.keys(lengths).find((t) => lengths[t] === n) ?? null;
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
|
+
function personTypeFromPatterns(fid, taxId) {
|
|
150
|
+
const patterns = fid?.person_patterns;
|
|
151
|
+
if (!patterns) return null;
|
|
152
|
+
const v = normalizeTaxId(taxId);
|
|
153
|
+
for (const type of Object.keys(patterns).sort()) {
|
|
154
|
+
const re = compile(patterns[type]);
|
|
155
|
+
if (re?.test(v)) return type;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function personTypeResolver(fid, override) {
|
|
160
|
+
return override ?? ((taxId) => personTypeFromPatterns(fid, taxId));
|
|
161
|
+
}
|
|
162
|
+
function suggestInvoiceUse(fid, regime) {
|
|
163
|
+
if (!fid || !regime) return null;
|
|
164
|
+
const allowed = usesFor(fid, regime);
|
|
165
|
+
const preferred = fid.default_invoice_use;
|
|
166
|
+
if (preferred && allowed.some((u) => u.code === preferred)) return preferred;
|
|
167
|
+
return allowed.find((u) => u.suggested_for_regimes?.includes(regime))?.code ?? null;
|
|
168
|
+
}
|
|
149
169
|
function regimesFor(fid, personType) {
|
|
150
170
|
const all = fid?.regimes ?? [];
|
|
151
171
|
if (!personType) return all;
|
|
@@ -156,8 +176,9 @@ function usesFor(fid, regime) {
|
|
|
156
176
|
if (!regime) return all;
|
|
157
177
|
return all.filter((u) => !u.allowed_regimes || u.allowed_regimes.length === 0 || u.allowed_regimes.includes(regime));
|
|
158
178
|
}
|
|
159
|
-
function validateFiscalValues(region, v,
|
|
179
|
+
function validateFiscalValues(region, v, personTypeOverride) {
|
|
160
180
|
const fid = region.fiscal_id;
|
|
181
|
+
const personTypeOf = personTypeResolver(fid, personTypeOverride);
|
|
161
182
|
const out = {};
|
|
162
183
|
const tax = taxIdProblem(fid, v.tax_id ?? "");
|
|
163
184
|
if (tax) out.tax_id = tax;
|
|
@@ -215,6 +236,7 @@ var ES2 = {
|
|
|
215
236
|
useHint: "Solo mostramos los usos que admite tu r\xE9gimen.",
|
|
216
237
|
useChoose: "Elige el uso",
|
|
217
238
|
fromDocument: "De tu {document}",
|
|
239
|
+
suggested: "Sugerido",
|
|
218
240
|
errors: {
|
|
219
241
|
required: "Completa este campo.",
|
|
220
242
|
taxIdFormat: "Revisa el formato: {kind} no v\xE1lido.",
|
|
@@ -247,14 +269,34 @@ var ES2 = {
|
|
|
247
269
|
qrMismatch: "El c\xF3digo QR de tu documento no coincide con los datos le\xEDdos. Revisa con calma tu RFC y tu nombre.",
|
|
248
270
|
stale: "Tu documento tiene {days} d\xEDas de emitido. Si algo cambi\xF3 en tu registro, descarga uno nuevo.",
|
|
249
271
|
regimeMissing: "No pudimos detectar tu r\xE9gimen. El\xEDgelo de la lista.",
|
|
250
|
-
fieldsMissing: "No encontramos algunos datos en el documento. Compl\xE9talos para continuar."
|
|
272
|
+
fieldsMissing: "No encontramos algunos datos en el documento. Compl\xE9talos para continuar.",
|
|
273
|
+
qrPartial: "Del c\xF3digo QR solo le\xEDmos tu identificador fiscal. Completa los dem\xE1s datos o sube el PDF."
|
|
274
|
+
},
|
|
275
|
+
scan: {
|
|
276
|
+
title: "Escanea tu {document}",
|
|
277
|
+
photo: "Tomar foto del QR",
|
|
278
|
+
photoHint: "Enfoca el c\xF3digo QR de tu documento.",
|
|
279
|
+
camera: "Escanear con la c\xE1mara",
|
|
280
|
+
cameraTitle: "Apunta al c\xF3digo QR",
|
|
281
|
+
cameraGuide: "Centra el c\xF3digo QR dentro del marco.",
|
|
282
|
+
cameraStarting: "Abriendo la c\xE1mara\u2026",
|
|
283
|
+
cameraRead: "C\xF3digo le\xEDdo",
|
|
284
|
+
cameraDenied: "No pudimos usar la c\xE1mara. Elige una foto o sube el PDF.",
|
|
285
|
+
cameraUnavailable: "Este dispositivo no permite usar la c\xE1mara aqu\xED. Elige una foto o sube el PDF.",
|
|
286
|
+
closeCamera: "Cerrar la c\xE1mara",
|
|
287
|
+
choosePhoto: "Elegir una foto",
|
|
288
|
+
open: "Escanear {document}",
|
|
289
|
+
manual: "Capturar a mano",
|
|
290
|
+
close: "Cerrar"
|
|
251
291
|
},
|
|
252
292
|
errors: {
|
|
253
293
|
not_pdf: "Ese archivo no es un PDF. Sube el PDF que descargaste o captura tus datos a mano.",
|
|
254
294
|
too_large: "El archivo pesa m\xE1s de {size}. Sube el PDF original o captura tus datos a mano.",
|
|
255
295
|
no_text: "Este PDF parece una imagen escaneada y no podemos leerlo. Captura tus datos a mano.",
|
|
256
296
|
password: "Este PDF tiene contrase\xF1a y no podemos abrirlo. Desc\xE1rgalo de nuevo sin contrase\xF1a o captura tus datos a mano.",
|
|
257
|
-
unreadable: "No pudimos leer este archivo. Int\xE9ntalo con otro o captura tus datos a mano."
|
|
297
|
+
unreadable: "No pudimos leer este archivo. Int\xE9ntalo con otro o captura tus datos a mano.",
|
|
298
|
+
no_qr: "No encontramos el c\xF3digo QR en la foto. Ac\xE9rcate, evita reflejos o sube el PDF.",
|
|
299
|
+
not_image: "Ese archivo no es una foto que podamos leer. Toma otra foto o sube el PDF."
|
|
258
300
|
},
|
|
259
301
|
summaryTitleOne: "Revisa 1 campo para continuar",
|
|
260
302
|
summaryTitleMany: "Revisa {n} campos para continuar"
|
|
@@ -297,7 +339,14 @@ var ES2 = {
|
|
|
297
339
|
windowTitle: "Ya pas\xF3 el plazo para facturar",
|
|
298
340
|
windowBody: "Este pedido est\xE1 fuera del plazo para pedir factura. Si necesitas ayuda, contacta a la tienda.",
|
|
299
341
|
unavailableTitle: "Factura no disponible",
|
|
300
|
-
unavailableBody: "
|
|
342
|
+
unavailableBody: "Este pedido no se puede facturar por ahora. Si la necesitas, contacta a la tienda.",
|
|
343
|
+
notEnabledTitle: "Facturaci\xF3n a\xFAn no activa",
|
|
344
|
+
notEnabledBody: "La tienda todav\xEDa no tiene la facturaci\xF3n en l\xEDnea activa. Si necesitas factura, cont\xE1ctala.",
|
|
345
|
+
unknownTitle: "Factura",
|
|
346
|
+
unknownBody: "Por ahora no podemos mostrar el estado de la facturaci\xF3n de este pedido. Si necesitas factura, contacta a la tienda.",
|
|
347
|
+
unreachableBody: "No pudimos verificar la facturaci\xF3n en este momento. Int\xE9ntalo de nuevo en unos minutos o contacta a la tienda.",
|
|
348
|
+
sandbox: "Facturaci\xF3n en pruebas (sandbox): las facturas no tienen validez fiscal.",
|
|
349
|
+
windowNote: "Puedes pedirla hasta {days} d\xEDas despu\xE9s de la compra.",
|
|
301
350
|
cancelledTitle: "Factura cancelada",
|
|
302
351
|
cancelledBody: "Esta factura fue cancelada. Si necesitas una nueva, contacta a la tienda.",
|
|
303
352
|
reasons: {
|
|
@@ -334,6 +383,7 @@ var EN2 = {
|
|
|
334
383
|
useHint: "We only list the uses your regime admits.",
|
|
335
384
|
useChoose: "Choose the use",
|
|
336
385
|
fromDocument: "From your {document}",
|
|
386
|
+
suggested: "Suggested",
|
|
337
387
|
errors: {
|
|
338
388
|
required: "Fill in this field.",
|
|
339
389
|
taxIdFormat: "Check the format: not a valid {kind}.",
|
|
@@ -366,14 +416,34 @@ var EN2 = {
|
|
|
366
416
|
qrMismatch: "The QR code of your document does not match what we read. Check your tax ID and name carefully.",
|
|
367
417
|
stale: "Your document was issued {days} days ago. If something changed in your registration, download a new one.",
|
|
368
418
|
regimeMissing: "We could not detect your regime. Choose it from the list.",
|
|
369
|
-
fieldsMissing: "We did not find some details in the document. Fill them in to continue."
|
|
419
|
+
fieldsMissing: "We did not find some details in the document. Fill them in to continue.",
|
|
420
|
+
qrPartial: "The QR code only gave us your tax ID. Fill in the rest or upload the PDF."
|
|
421
|
+
},
|
|
422
|
+
scan: {
|
|
423
|
+
title: "Scan your {document}",
|
|
424
|
+
photo: "Take a photo of the QR",
|
|
425
|
+
photoHint: "Point at the QR code of your document.",
|
|
426
|
+
camera: "Scan with the camera",
|
|
427
|
+
cameraTitle: "Point at the QR code",
|
|
428
|
+
cameraGuide: "Center the QR code inside the frame.",
|
|
429
|
+
cameraStarting: "Opening the camera\u2026",
|
|
430
|
+
cameraRead: "Code read",
|
|
431
|
+
cameraDenied: "We could not use the camera. Choose a photo or upload the PDF.",
|
|
432
|
+
cameraUnavailable: "This device does not allow the camera here. Choose a photo or upload the PDF.",
|
|
433
|
+
closeCamera: "Close the camera",
|
|
434
|
+
choosePhoto: "Choose a photo",
|
|
435
|
+
open: "Scan {document}",
|
|
436
|
+
manual: "Enter by hand",
|
|
437
|
+
close: "Close"
|
|
370
438
|
},
|
|
371
439
|
errors: {
|
|
372
440
|
not_pdf: "That file is not a PDF. Upload the PDF you downloaded or enter your details by hand.",
|
|
373
441
|
too_large: "The file is larger than {size}. Upload the original PDF or enter your details by hand.",
|
|
374
442
|
no_text: "This PDF looks like a scanned image and we cannot read it. Enter your details by hand.",
|
|
375
443
|
password: "This PDF is password protected and we cannot open it. Download it again without a password or enter your details by hand.",
|
|
376
|
-
unreadable: "We could not read this file. Try another one or enter your details by hand."
|
|
444
|
+
unreadable: "We could not read this file. Try another one or enter your details by hand.",
|
|
445
|
+
no_qr: "We could not find the QR code in the photo. Move closer, avoid glare or upload the PDF.",
|
|
446
|
+
not_image: "That file is not a photo we can read. Take another photo or upload the PDF."
|
|
377
447
|
},
|
|
378
448
|
summaryTitleOne: "Check 1 field to continue",
|
|
379
449
|
summaryTitleMany: "Check {n} fields to continue"
|
|
@@ -416,7 +486,14 @@ var EN2 = {
|
|
|
416
486
|
windowTitle: "The invoicing period has ended",
|
|
417
487
|
windowBody: "This order is outside the period to request an invoice. If you need help, contact the store.",
|
|
418
488
|
unavailableTitle: "Invoice not available",
|
|
419
|
-
unavailableBody: "This
|
|
489
|
+
unavailableBody: "This order cannot be invoiced right now. If you need one, contact the store.",
|
|
490
|
+
notEnabledTitle: "Invoicing not active yet",
|
|
491
|
+
notEnabledBody: "The store has not turned on online invoicing yet. If you need an invoice, contact them.",
|
|
492
|
+
unknownTitle: "Invoice",
|
|
493
|
+
unknownBody: "We cannot show the invoicing status of this order right now. If you need an invoice, contact the store.",
|
|
494
|
+
unreachableBody: "We could not check invoicing right now. Try again in a few minutes or contact the store.",
|
|
495
|
+
sandbox: "Invoicing in test mode (sandbox): invoices have no fiscal validity.",
|
|
496
|
+
windowNote: "You can request it up to {days} days after the purchase.",
|
|
420
497
|
cancelledTitle: "Invoice cancelled",
|
|
421
498
|
cancelledBody: "This invoice was cancelled. If you need a new one, contact the store.",
|
|
422
499
|
reasons: {
|
|
@@ -480,7 +557,8 @@ function FiscalIdentityFields({
|
|
|
480
557
|
errors = {},
|
|
481
558
|
namePrefix = "invoice",
|
|
482
559
|
idPrefix,
|
|
483
|
-
personTypeOf,
|
|
560
|
+
personTypeOf: personTypeOverride,
|
|
561
|
+
suggestedUse,
|
|
484
562
|
disabled,
|
|
485
563
|
locale: localeProp,
|
|
486
564
|
messages,
|
|
@@ -495,6 +573,8 @@ function FiscalIdentityFields({
|
|
|
495
573
|
const base = idPrefix ?? `sf-fiscal-${reactId.replace(/:/g, "")}`;
|
|
496
574
|
const [v, setV] = react.useState({ ...EMPTY, ...values });
|
|
497
575
|
const [shown, setShown] = react.useState(/* @__PURE__ */ new Set());
|
|
576
|
+
const [autoUse, setAutoUse] = react.useState(null);
|
|
577
|
+
const personTypeOf = react.useMemo(() => personTypeResolver(region.fiscal_id, personTypeOverride), [region.fiscal_id, personTypeOverride]);
|
|
498
578
|
const root = react.useRef(null);
|
|
499
579
|
const valuesKey = JSON.stringify(values ?? null);
|
|
500
580
|
const first2 = react.useRef(true);
|
|
@@ -509,10 +589,17 @@ function FiscalIdentityFields({
|
|
|
509
589
|
const set = (patch) => {
|
|
510
590
|
const next = { ...v, ...patch };
|
|
511
591
|
if (patch.tax_regime !== void 0 && next.use && !usesFor(fid, next.tax_regime).some((u) => u.code === next.use)) next.use = "";
|
|
592
|
+
if (patch.tax_regime !== void 0 && !next.use) {
|
|
593
|
+
const suggestion = suggestInvoiceUse(fid, next.tax_regime);
|
|
594
|
+
if (suggestion) {
|
|
595
|
+
next.use = suggestion;
|
|
596
|
+
setAutoUse(suggestion);
|
|
597
|
+
} else setAutoUse(null);
|
|
598
|
+
} else if (patch.use !== void 0) setAutoUse(null);
|
|
512
599
|
setV(next);
|
|
513
600
|
onChange?.(next);
|
|
514
601
|
};
|
|
515
|
-
const person = personTypeOf
|
|
602
|
+
const person = personTypeOf(v.tax_id);
|
|
516
603
|
const taxIdOk = !validateFiscalValues(region, { tax_id: v.tax_id }, personTypeOf).tax_id;
|
|
517
604
|
const problems = react.useMemo(() => validateFiscalValues(region, v, personTypeOf), [region, v, personTypeOf]);
|
|
518
605
|
const candidates = regimeCandidates?.length ? regimeCandidates : null;
|
|
@@ -680,7 +767,12 @@ function FiscalIdentityFields({
|
|
|
680
767
|
name: name("use"),
|
|
681
768
|
id: `${base}-use`,
|
|
682
769
|
label: m.use,
|
|
683
|
-
hint:
|
|
770
|
+
hint: v.use && (v.use === autoUse || v.use === suggestedUse) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
771
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.mark, "data-sf-suggested": true, children: m.suggested }),
|
|
772
|
+
" ",
|
|
773
|
+
"\xB7 ",
|
|
774
|
+
m.useHint
|
|
775
|
+
] }) : m.useHint,
|
|
684
776
|
value: v.use,
|
|
685
777
|
onChange: (e) => set({ use: e.target.value }),
|
|
686
778
|
onBlur: () => touch("use"),
|
|
@@ -731,10 +823,93 @@ function summarizeExtraction(x, evidence, validRegimes) {
|
|
|
731
823
|
evidence
|
|
732
824
|
};
|
|
733
825
|
}
|
|
826
|
+
function cameraSupported() {
|
|
827
|
+
return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function";
|
|
828
|
+
}
|
|
829
|
+
function useCameraQr({ active, onCode, intervalMs = 250 }) {
|
|
830
|
+
const videoRef = react.useRef(null);
|
|
831
|
+
const [status, setStatus] = react.useState("idle");
|
|
832
|
+
const streamRef = react.useRef(null);
|
|
833
|
+
const onCodeRef = react.useRef(onCode);
|
|
834
|
+
onCodeRef.current = onCode;
|
|
835
|
+
const stop = react.useCallback(() => {
|
|
836
|
+
for (const track of streamRef.current?.getTracks() ?? []) track.stop();
|
|
837
|
+
streamRef.current = null;
|
|
838
|
+
const v = videoRef.current;
|
|
839
|
+
if (v) v.srcObject = null;
|
|
840
|
+
setStatus((s) => s === "denied" || s === "unsupported" ? s : "idle");
|
|
841
|
+
}, []);
|
|
842
|
+
react.useEffect(() => {
|
|
843
|
+
if (!active) return;
|
|
844
|
+
if (!cameraSupported()) {
|
|
845
|
+
setStatus("unsupported");
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
let cancelled = false;
|
|
849
|
+
let timer;
|
|
850
|
+
let busy = false;
|
|
851
|
+
setStatus("starting");
|
|
852
|
+
const onHidden = () => {
|
|
853
|
+
if (document.visibilityState === "hidden") stop();
|
|
854
|
+
};
|
|
855
|
+
document.addEventListener("visibilitychange", onHidden);
|
|
856
|
+
(async () => {
|
|
857
|
+
let stream;
|
|
858
|
+
try {
|
|
859
|
+
stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: "environment" } }, audio: false });
|
|
860
|
+
} catch {
|
|
861
|
+
if (!cancelled) setStatus("denied");
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (cancelled) {
|
|
865
|
+
for (const t of stream.getTracks()) t.stop();
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
streamRef.current = stream;
|
|
869
|
+
const video = videoRef.current;
|
|
870
|
+
if (!video) {
|
|
871
|
+
for (const t of stream.getTracks()) t.stop();
|
|
872
|
+
streamRef.current = null;
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
video.srcObject = stream;
|
|
876
|
+
video.muted = true;
|
|
877
|
+
void video.play?.().catch(() => void 0);
|
|
878
|
+
setStatus("scanning");
|
|
879
|
+
const eng = await import('@asteby/storefront/fiscal-document');
|
|
880
|
+
timer = setInterval(async () => {
|
|
881
|
+
if (busy || cancelled || !videoRef.current || videoRef.current.readyState < 2) return;
|
|
882
|
+
busy = true;
|
|
883
|
+
try {
|
|
884
|
+
const text = await eng.decodeImageQr(videoRef.current);
|
|
885
|
+
if (text && !cancelled) {
|
|
886
|
+
cancelled = true;
|
|
887
|
+
clearInterval(timer);
|
|
888
|
+
stop();
|
|
889
|
+
onCodeRef.current(text);
|
|
890
|
+
}
|
|
891
|
+
} catch {
|
|
892
|
+
} finally {
|
|
893
|
+
busy = false;
|
|
894
|
+
}
|
|
895
|
+
}, intervalMs);
|
|
896
|
+
})();
|
|
897
|
+
return () => {
|
|
898
|
+
cancelled = true;
|
|
899
|
+
if (timer) clearInterval(timer);
|
|
900
|
+
document.removeEventListener("visibilitychange", onHidden);
|
|
901
|
+
for (const t of streamRef.current?.getTracks() ?? []) t.stop();
|
|
902
|
+
streamRef.current = null;
|
|
903
|
+
if (videoRef.current) videoRef.current.srcObject = null;
|
|
904
|
+
};
|
|
905
|
+
}, [active, intervalMs, stop]);
|
|
906
|
+
return { videoRef, status, stop };
|
|
907
|
+
}
|
|
734
908
|
var isPdf = (f, accept) => {
|
|
735
909
|
const okType = f.type ? accept.some((a) => a === f.type) || f.type === "application/pdf" : false;
|
|
736
910
|
return okType || /\.pdf$/i.test(f.name);
|
|
737
911
|
};
|
|
912
|
+
var isImage = (f) => f.type.startsWith("image/") || /\.(jpe?g|png|webp|heic|heif)$/i.test(f.name);
|
|
738
913
|
var mib = (bytes) => `${Math.round(bytes / (1024 * 1024) * 10) / 10} MB`;
|
|
739
914
|
function IdentityDocumentReader(props) {
|
|
740
915
|
const { region, workerSrc, onConfirm, onManual, initialValues, data, canSave, serverUpload, personTypeOf, now, namePrefix = "invoice", messages, classNames: cx = {}, confirmLabel } = props;
|
|
@@ -766,14 +941,29 @@ function IdentityDocumentReader(props) {
|
|
|
766
941
|
react.useEffect(() => () => abort.current?.abort(), []);
|
|
767
942
|
react.useEffect(() => {
|
|
768
943
|
if (!fromServer) return;
|
|
769
|
-
setValues((cur) =>
|
|
944
|
+
setValues((cur) => {
|
|
945
|
+
const next = { ...cur, ...fromServer.values };
|
|
946
|
+
const suggestion = next.tax_regime && !next.use ? suggestInvoiceUse(region.fiscal_id, next.tax_regime) : null;
|
|
947
|
+
if (suggestion) {
|
|
948
|
+
next.use = suggestion;
|
|
949
|
+
setSuggestedUse(suggestion);
|
|
950
|
+
}
|
|
951
|
+
return next;
|
|
952
|
+
});
|
|
770
953
|
setPhase({ kind: "review", extraction: fromServer, revision: 1 });
|
|
771
954
|
}, [fromServer]);
|
|
772
955
|
react.useEffect(() => {
|
|
773
956
|
if (phase.kind === "error") errorRef.current?.focus();
|
|
774
957
|
if (phase.kind === "review" && phase.revision > 0) reviewRef.current?.focus();
|
|
775
958
|
}, [phase]);
|
|
776
|
-
const accept = react.useMemo(() =>
|
|
959
|
+
const accept = react.useMemo(() => {
|
|
960
|
+
const files = (spec?.accept ?? []).filter((a) => !a.startsWith("image/"));
|
|
961
|
+
return files.length ? files.join(",") : "application/pdf";
|
|
962
|
+
}, [spec]);
|
|
963
|
+
const imageMode = spec?.image_mode && spec.image_mode !== "none" ? spec.image_mode : null;
|
|
964
|
+
const photoInput = react.useRef(null);
|
|
965
|
+
const [suggestedUse, setSuggestedUse] = react.useState(null);
|
|
966
|
+
const cameraActive = phase.kind === "camera";
|
|
777
967
|
const fieldErrors = react.useMemo(() => {
|
|
778
968
|
const out = {};
|
|
779
969
|
for (const [k, v] of Object.entries(data?.fieldErrors ?? {})) out[k] = all.server.errors[v] ?? v;
|
|
@@ -790,9 +980,70 @@ function IdentityDocumentReader(props) {
|
|
|
790
980
|
setPhase({ kind: "review", extraction: null, revision: 1 });
|
|
791
981
|
setLive("");
|
|
792
982
|
}, [onManual]);
|
|
983
|
+
const showExtraction = react.useCallback(
|
|
984
|
+
(sum) => {
|
|
985
|
+
const base = { tax_id: "", legal_name: "", postal_code: "", tax_regime: "", use: "", ...sum.values };
|
|
986
|
+
const suggestion = base.tax_regime ? suggestInvoiceUse(region.fiscal_id, base.tax_regime) : null;
|
|
987
|
+
if (suggestion && !base.use) base.use = suggestion;
|
|
988
|
+
setSuggestedUse(suggestion && base.use === suggestion ? suggestion : null);
|
|
989
|
+
setValues(base);
|
|
990
|
+
setSummary(null);
|
|
991
|
+
setPhase({ kind: "review", extraction: sum, revision: 1 });
|
|
992
|
+
setLive(m.done);
|
|
993
|
+
},
|
|
994
|
+
[region.fiscal_id, m.done]
|
|
995
|
+
);
|
|
996
|
+
const readQr = react.useCallback(
|
|
997
|
+
async (text) => {
|
|
998
|
+
if (!spec) return;
|
|
999
|
+
if (!text) return setPhase({ kind: "error", code: "no_qr" });
|
|
1000
|
+
const eng = await import('@asteby/storefront/fiscal-document');
|
|
1001
|
+
const x = eng.extractIdentityDocument("", spec, {
|
|
1002
|
+
catalogs: region.fiscal_id ? eng.catalogsFromRegion(region.fiscal_id) : {},
|
|
1003
|
+
qr: text,
|
|
1004
|
+
...now ? { now: now() } : {}
|
|
1005
|
+
});
|
|
1006
|
+
if (!x.fields.tax_id) return setPhase({ kind: "error", code: "no_qr" });
|
|
1007
|
+
const evidence = eng.buildDocumentEvidence(x, { sha256: null, source: "qr" });
|
|
1008
|
+
showExtraction(summarizeExtraction(x, evidence, region.fiscal_id?.regimes.map((r) => r.code)));
|
|
1009
|
+
},
|
|
1010
|
+
[spec, region, now, showExtraction]
|
|
1011
|
+
);
|
|
1012
|
+
const readImage = react.useCallback(
|
|
1013
|
+
async (file) => {
|
|
1014
|
+
if (!spec) return;
|
|
1015
|
+
if (file.size > spec.max_bytes) return setPhase({ kind: "error", code: "too_large", sizeLimit: spec.max_bytes });
|
|
1016
|
+
const ctl = new AbortController();
|
|
1017
|
+
abort.current = ctl;
|
|
1018
|
+
setPhase({ kind: "reading", fileName: file.name });
|
|
1019
|
+
setLive(fill(m.reading, { document: documentName }));
|
|
1020
|
+
try {
|
|
1021
|
+
const eng = await import('@asteby/storefront/fiscal-document');
|
|
1022
|
+
const text = await eng.decodeImageQr(file, { maxBytes: spec.max_bytes, signal: ctl.signal });
|
|
1023
|
+
if (ctl.signal.aborted) return;
|
|
1024
|
+
await readQr(text);
|
|
1025
|
+
} catch (err) {
|
|
1026
|
+
if (ctl.signal.aborted) return;
|
|
1027
|
+
const code = err?.code;
|
|
1028
|
+
setLive("");
|
|
1029
|
+
setPhase({ kind: "error", code: code === "too_large" ? "too_large" : code === "not_image" ? "not_image" : "unreadable", sizeLimit: spec.max_bytes });
|
|
1030
|
+
} finally {
|
|
1031
|
+
if (abort.current === ctl) abort.current = null;
|
|
1032
|
+
if (photoInput.current) photoInput.current.value = "";
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
[spec, documentName, m.reading, readQr]
|
|
1036
|
+
);
|
|
1037
|
+
const camera = useCameraQr({
|
|
1038
|
+
active: cameraActive,
|
|
1039
|
+
onCode: (text) => {
|
|
1040
|
+
void readQr(text).then(() => setLive(`${m.scan.cameraRead}. ${m.done}`)).catch(() => setPhase({ kind: "error", code: "unreadable" }));
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
793
1043
|
const read = react.useCallback(
|
|
794
1044
|
async (file) => {
|
|
795
1045
|
if (!spec) return;
|
|
1046
|
+
if (imageMode && isImage(file)) return readImage(file);
|
|
796
1047
|
if (!isPdf(file, spec.accept ?? [])) return setPhase({ kind: "error", code: "not_pdf" });
|
|
797
1048
|
if (file.size > spec.max_bytes) return setPhase({ kind: "error", code: "too_large", sizeLimit: spec.max_bytes });
|
|
798
1049
|
const ctl = new AbortController();
|
|
@@ -817,10 +1068,7 @@ function IdentityDocumentReader(props) {
|
|
|
817
1068
|
});
|
|
818
1069
|
const evidence = eng.buildDocumentEvidence(x, { sha256, source: "pdf_client" });
|
|
819
1070
|
const sum = summarizeExtraction(x, evidence, region.fiscal_id?.regimes.map((r) => r.code));
|
|
820
|
-
|
|
821
|
-
setSummary(null);
|
|
822
|
-
setPhase({ kind: "review", extraction: sum, revision: 1 });
|
|
823
|
-
setLive(m.done);
|
|
1071
|
+
showExtraction(sum);
|
|
824
1072
|
} catch (err) {
|
|
825
1073
|
if (ctl.signal.aborted) return;
|
|
826
1074
|
const code = err?.code;
|
|
@@ -835,7 +1083,7 @@ function IdentityDocumentReader(props) {
|
|
|
835
1083
|
if (fileInput.current) fileInput.current.value = "";
|
|
836
1084
|
}
|
|
837
1085
|
},
|
|
838
|
-
[spec, workerSrc, region, now, documentName, m.reading,
|
|
1086
|
+
[spec, workerSrc, region, now, documentName, m.reading, imageMode, readImage, showExtraction]
|
|
839
1087
|
);
|
|
840
1088
|
if (!spec) return null;
|
|
841
1089
|
const onDrop = (e) => {
|
|
@@ -845,7 +1093,7 @@ function IdentityDocumentReader(props) {
|
|
|
845
1093
|
if (f) void read(f);
|
|
846
1094
|
};
|
|
847
1095
|
const confirm = () => {
|
|
848
|
-
const problems = validateFiscalValues(region, values, personTypeOf);
|
|
1096
|
+
const problems = validateFiscalValues(region, values, personTypeResolver(region.fiscal_id, personTypeOf));
|
|
849
1097
|
const keys = Object.keys(problems);
|
|
850
1098
|
if (keys.length) {
|
|
851
1099
|
const labels = {
|
|
@@ -910,6 +1158,27 @@ function IdentityDocumentReader(props) {
|
|
|
910
1158
|
]
|
|
911
1159
|
}
|
|
912
1160
|
),
|
|
1161
|
+
imageMode ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.actions, "data-sf-reader-scan": true, children: [
|
|
1162
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1163
|
+
"input",
|
|
1164
|
+
{
|
|
1165
|
+
ref: photoInput,
|
|
1166
|
+
type: "file",
|
|
1167
|
+
accept: "image/*",
|
|
1168
|
+
capture: "environment",
|
|
1169
|
+
hidden: true,
|
|
1170
|
+
tabIndex: -1,
|
|
1171
|
+
"aria-label": m.scan.photo,
|
|
1172
|
+
"data-sf-reader-photo-input": true,
|
|
1173
|
+
onChange: (e) => {
|
|
1174
|
+
const f = e.target.files?.[0];
|
|
1175
|
+
if (f) void readImage(f);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
),
|
|
1179
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.secondary, onClick: () => photoInput.current?.click(), "data-sf-reader-photo": true, children: m.scan.photo }),
|
|
1180
|
+
hydrated && cameraSupported() ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.secondary, onClick: () => setPhase({ kind: "camera" }), "data-sf-reader-camera": true, children: m.scan.camera }) : null
|
|
1181
|
+
] }) : null,
|
|
913
1182
|
/* @__PURE__ */ jsxRuntime.jsx("p", { id: helpId, className: cx.privacy, "data-sf-reader-privacy": true, children: privacy }),
|
|
914
1183
|
serverUpload ? /* @__PURE__ */ jsxRuntime.jsx("noscript", { children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", name: "intent", value: "extract-document", formEncType: "multipart/form-data", formMethod: "post", formNoValidate: true, className: cx.secondary, "data-sf-reader-upload": true, children: m.uploadServer }) }) : null,
|
|
915
1184
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { "data-sf-reader-links": true, children: [
|
|
@@ -926,6 +1195,48 @@ function IdentityDocumentReader(props) {
|
|
|
926
1195
|
] }),
|
|
927
1196
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.secondary, onClick: cancel, "data-sf-reader-cancel": true, children: m.cancel })
|
|
928
1197
|
] });
|
|
1198
|
+
const cameraView = phase.kind === "camera" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.root, "data-sf-reader": true, "data-state": "camera", children: [
|
|
1199
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { "data-sf-reader-title": true, children: m.scan.cameraTitle }),
|
|
1200
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { "data-sf-reader-viewfinder": true, children: [
|
|
1201
|
+
/* @__PURE__ */ jsxRuntime.jsx("video", { ref: camera.videoRef, playsInline: true, muted: true, "aria-label": m.scan.cameraGuide, "data-sf-reader-video": true }),
|
|
1202
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", "data-sf-reader-frame": true })
|
|
1203
|
+
] }),
|
|
1204
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.status, "data-sf-reader-camera-status": camera.status, children: camera.status === "denied" ? m.scan.cameraDenied : camera.status === "unsupported" ? m.scan.cameraUnavailable : camera.status === "starting" ? m.scan.cameraStarting : m.scan.cameraGuide }),
|
|
1205
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.privacy, "data-sf-reader-privacy": true, children: privacy }),
|
|
1206
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.actions, "data-sf-reader-actions": true, children: [
|
|
1207
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.secondary, onClick: () => photoInput.current?.click(), "data-sf-reader-photo": true, children: m.scan.choosePhoto }),
|
|
1208
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1209
|
+
"button",
|
|
1210
|
+
{
|
|
1211
|
+
type: "button",
|
|
1212
|
+
className: cx.primary,
|
|
1213
|
+
onClick: () => {
|
|
1214
|
+
camera.stop();
|
|
1215
|
+
setPhase({ kind: "idle" });
|
|
1216
|
+
},
|
|
1217
|
+
"data-sf-reader-camera-close": true,
|
|
1218
|
+
children: m.scan.closeCamera
|
|
1219
|
+
}
|
|
1220
|
+
)
|
|
1221
|
+
] }),
|
|
1222
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1223
|
+
"input",
|
|
1224
|
+
{
|
|
1225
|
+
ref: photoInput,
|
|
1226
|
+
type: "file",
|
|
1227
|
+
accept: "image/*",
|
|
1228
|
+
capture: "environment",
|
|
1229
|
+
hidden: true,
|
|
1230
|
+
tabIndex: -1,
|
|
1231
|
+
"aria-label": m.scan.choosePhoto,
|
|
1232
|
+
"data-sf-reader-photo-input": true,
|
|
1233
|
+
onChange: (e) => {
|
|
1234
|
+
const f = e.target.files?.[0];
|
|
1235
|
+
if (f) void readImage(f);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
)
|
|
1239
|
+
] });
|
|
929
1240
|
const errored = phase.kind === "error" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.root, "data-sf-reader": true, "data-state": "error", children: [
|
|
930
1241
|
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: errorRef, role: "alert", tabIndex: -1, className: cx.error, "data-sf-reader-error": phase.code, children: fill(m.errors[phase.code], { size: mib(phase.sizeLimit ?? spec.max_bytes) }) }),
|
|
931
1242
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.actions, "data-sf-reader-actions": true, children: [
|
|
@@ -958,6 +1269,7 @@ function IdentityDocumentReader(props) {
|
|
|
958
1269
|
fromDocument: phase.extraction?.from_document ?? [],
|
|
959
1270
|
documentName,
|
|
960
1271
|
regimeCandidates: phase.extraction && phase.extraction.regime_candidates.length > 1 ? phase.extraction.regime_candidates : void 0,
|
|
1272
|
+
suggestedUse,
|
|
961
1273
|
...personTypeOf ? { personTypeOf } : {},
|
|
962
1274
|
locale,
|
|
963
1275
|
...messages ? { messages } : {},
|
|
@@ -985,6 +1297,7 @@ function IdentityDocumentReader(props) {
|
|
|
985
1297
|
/* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", className: cx.status, "data-sf-reader-live": true, style: visuallyHidden, children: live }),
|
|
986
1298
|
phase.kind === "idle" ? idle : null,
|
|
987
1299
|
reading || null,
|
|
1300
|
+
cameraView || null,
|
|
988
1301
|
errored || null,
|
|
989
1302
|
review || null
|
|
990
1303
|
] });
|
|
@@ -994,7 +1307,8 @@ function Warnings({ x, m, cx }) {
|
|
|
994
1307
|
const items = [];
|
|
995
1308
|
if (x.qr_match === false) items.push(m.warnings.qrMismatch);
|
|
996
1309
|
if (x.stale === true && x.age_days !== null) items.push(fill(m.warnings.stale, { days: x.age_days }));
|
|
997
|
-
if (x.
|
|
1310
|
+
if (x.evidence.source === "qr") items.push(m.warnings.qrPartial);
|
|
1311
|
+
else if (x.regime_candidates.length === 0 && !x.values.tax_regime) items.push(m.warnings.regimeMissing);
|
|
998
1312
|
else if (x.missing.some((k) => k !== "tax_regime" && k !== "document_id" && k !== "issued_on")) items.push(m.warnings.fieldsMissing);
|
|
999
1313
|
if (!items.length) return null;
|
|
1000
1314
|
return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: cx.warning, "data-sf-reader-warnings": true, children: items.map((t) => /* @__PURE__ */ jsxRuntime.jsxs("li", { "data-sf-reader-warning": true, children: [
|
|
@@ -1002,6 +1316,101 @@ function Warnings({ x, m, cx }) {
|
|
|
1002
1316
|
t
|
|
1003
1317
|
] }, t)) });
|
|
1004
1318
|
}
|
|
1319
|
+
function useModalDialog({ open, onClose, initialFocus }) {
|
|
1320
|
+
const ref = react.useRef(null);
|
|
1321
|
+
const opener = react.useRef(null);
|
|
1322
|
+
const onCloseRef = react.useRef(onClose);
|
|
1323
|
+
onCloseRef.current = onClose;
|
|
1324
|
+
react.useEffect(() => {
|
|
1325
|
+
const el = ref.current;
|
|
1326
|
+
if (!el || !open) return;
|
|
1327
|
+
opener.current = typeof document !== "undefined" ? document.activeElement : null;
|
|
1328
|
+
const hasModal = typeof el.showModal === "function";
|
|
1329
|
+
if (!el.open) {
|
|
1330
|
+
if (hasModal) {
|
|
1331
|
+
try {
|
|
1332
|
+
el.showModal();
|
|
1333
|
+
} catch {
|
|
1334
|
+
el.setAttribute("open", "");
|
|
1335
|
+
}
|
|
1336
|
+
} else el.setAttribute("open", "");
|
|
1337
|
+
}
|
|
1338
|
+
const target = initialFocus?.current ?? el.querySelector("[data-sf-autofocus]") ?? el;
|
|
1339
|
+
if (!target.hasAttribute("tabindex") && target === el) el.setAttribute("tabindex", "-1");
|
|
1340
|
+
target.focus({ preventScroll: true });
|
|
1341
|
+
const onCancel = (e) => {
|
|
1342
|
+
e.preventDefault();
|
|
1343
|
+
onCloseRef.current("esc");
|
|
1344
|
+
};
|
|
1345
|
+
const onKey = (e) => {
|
|
1346
|
+
if (e.key === "Escape") {
|
|
1347
|
+
e.preventDefault();
|
|
1348
|
+
onCloseRef.current("esc");
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
el.addEventListener("cancel", onCancel);
|
|
1352
|
+
if (!hasModal) el.addEventListener("keydown", onKey);
|
|
1353
|
+
return () => {
|
|
1354
|
+
el.removeEventListener("cancel", onCancel);
|
|
1355
|
+
el.removeEventListener("keydown", onKey);
|
|
1356
|
+
if (el.open) {
|
|
1357
|
+
if (typeof el.close === "function") {
|
|
1358
|
+
try {
|
|
1359
|
+
el.close();
|
|
1360
|
+
} catch {
|
|
1361
|
+
el.removeAttribute("open");
|
|
1362
|
+
}
|
|
1363
|
+
} else el.removeAttribute("open");
|
|
1364
|
+
}
|
|
1365
|
+
const back = opener.current;
|
|
1366
|
+
if (back instanceof HTMLElement && back.isConnected) back.focus({ preventScroll: true });
|
|
1367
|
+
};
|
|
1368
|
+
}, [open]);
|
|
1369
|
+
const onBackdropClick = react.useCallback((e) => {
|
|
1370
|
+
if (e.target === e.currentTarget) onCloseRef.current("backdrop");
|
|
1371
|
+
}, []);
|
|
1372
|
+
return { ref, onBackdropClick };
|
|
1373
|
+
}
|
|
1374
|
+
function ScanTaxDocument(props) {
|
|
1375
|
+
const { onConfirm, onManual, classNames: cx = {}, messages, confirmLabel, ...reader } = props;
|
|
1376
|
+
const locale = useResolvedLocale(props.locale);
|
|
1377
|
+
const m = fiscalMessages(locale, messages).reader;
|
|
1378
|
+
const spec = props.document ?? props.region.fiscal_id?.documents?.[0];
|
|
1379
|
+
const [open, setOpen] = react.useState(false);
|
|
1380
|
+
const titleId = react.useId();
|
|
1381
|
+
const { ref, onBackdropClick } = useModalDialog({ open, onClose: () => setOpen(false) });
|
|
1382
|
+
const opener = react.useRef(null);
|
|
1383
|
+
if (!spec) return null;
|
|
1384
|
+
const name = props.documentName ?? spec.label.toLowerCase();
|
|
1385
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.actions, "data-sf-scan": true, children: [
|
|
1386
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { ref: opener, type: "button", className: cx.trigger ?? cx.primary, onClick: () => setOpen(true), "aria-haspopup": "dialog", "data-sf-scan-open": true, children: fill(m.scan.open, { document: spec.label }) }),
|
|
1387
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.manual ?? cx.secondary, onClick: onManual, "data-sf-scan-manual": true, children: m.scan.manual }),
|
|
1388
|
+
open ? /* @__PURE__ */ jsxRuntime.jsx("dialog", { ref, role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, onClick: onBackdropClick, onClose: () => setOpen(false), className: cx.sheet, "data-sf-scan-sheet": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.sheetPanel, "data-sf-scan-panel": true, children: [
|
|
1389
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.sheetHeader, "data-sf-scan-header": true, children: [
|
|
1390
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: cx.sheetTitle, "data-sf-scan-title": true, children: fill(m.scan.title, { document: name }) }),
|
|
1391
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.sheetClose, onClick: () => setOpen(false), "aria-label": m.scan.close, "data-sf-scan-close": true, "data-sf-autofocus": true, children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2715" }) })
|
|
1392
|
+
] }),
|
|
1393
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1394
|
+
IdentityDocumentReader,
|
|
1395
|
+
{
|
|
1396
|
+
...reader,
|
|
1397
|
+
locale,
|
|
1398
|
+
...messages ? { messages } : {},
|
|
1399
|
+
...confirmLabel ? { confirmLabel } : {},
|
|
1400
|
+
classNames: cx,
|
|
1401
|
+
onConfirm: (result) => {
|
|
1402
|
+
setOpen(false);
|
|
1403
|
+
onConfirm(result);
|
|
1404
|
+
},
|
|
1405
|
+
onManual: () => {
|
|
1406
|
+
setOpen(false);
|
|
1407
|
+
onManual();
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
)
|
|
1411
|
+
] }) }) : null
|
|
1412
|
+
] });
|
|
1413
|
+
}
|
|
1005
1414
|
var profileValues = (p) => ({
|
|
1006
1415
|
tax_id: p.tax_id,
|
|
1007
1416
|
legal_name: p.legal_name,
|
|
@@ -1187,8 +1596,7 @@ function labelOf(list, code, locale) {
|
|
|
1187
1596
|
const e = list?.find((x) => x.code === code);
|
|
1188
1597
|
return e ? `${e.code} \xB7 ${entryLabel(e, locale)}` : code;
|
|
1189
1598
|
}
|
|
1190
|
-
function
|
|
1191
|
-
if (!available) return "unavailable";
|
|
1599
|
+
function orderState(invoice) {
|
|
1192
1600
|
if (!invoice) return "not_requested";
|
|
1193
1601
|
const stage = invoice.stage ?? "";
|
|
1194
1602
|
const blocked = invoice.blocked_reason ?? "";
|
|
@@ -1203,11 +1611,18 @@ function invoiceCardState(invoice, available = true) {
|
|
|
1203
1611
|
if (invoice.can_request === false && blocked) return "unavailable";
|
|
1204
1612
|
return "not_requested";
|
|
1205
1613
|
}
|
|
1614
|
+
function invoiceCardState(invoice, available = true, reason) {
|
|
1615
|
+
if (available) return orderState(invoice);
|
|
1616
|
+
if (reason === "off" || reason === "no_region") return "not_enabled";
|
|
1617
|
+
const own = orderState(invoice);
|
|
1618
|
+
if (invoice && (own !== "not_requested" || invoice.can_request === true || invoice.action === "request")) return own;
|
|
1619
|
+
return reason === "unreachable" ? "unreachable" : "unknown";
|
|
1620
|
+
}
|
|
1206
1621
|
function InvoiceStatusCard(props) {
|
|
1207
|
-
const { invoice, available = true, onRequest, requestHref, onCorrect, correctHref, onRefresh, refreshMs = 1e4, refreshForMs = 6e4, contact, messages, classNames: cx = {}, headingLevel = 3 } = props;
|
|
1622
|
+
const { invoice, available = true, availability, testMode, windowDays, onRequest, requestHref, onCorrect, correctHref, onRefresh, refreshMs = 1e4, refreshForMs = 6e4, contact, messages, classNames: cx = {}, headingLevel = 3 } = props;
|
|
1208
1623
|
const locale = useResolvedLocale(props.locale);
|
|
1209
1624
|
const m = fiscalMessages(locale, messages).status;
|
|
1210
|
-
const state = invoiceCardState(invoice, available);
|
|
1625
|
+
const state = invoiceCardState(invoice, available, availability);
|
|
1211
1626
|
const titleId = `sf-invoice-card-${react.useId().replace(/:/g, "")}`;
|
|
1212
1627
|
const [tired, setTired] = react.useState(false);
|
|
1213
1628
|
const refresh = react.useRef(onRefresh);
|
|
@@ -1240,7 +1655,10 @@ function InvoiceStatusCard(props) {
|
|
|
1240
1655
|
switch (state) {
|
|
1241
1656
|
case "not_requested": {
|
|
1242
1657
|
title = m.title;
|
|
1243
|
-
body = /* @__PURE__ */ jsxRuntime.
|
|
1658
|
+
body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1659
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.body, children: m.requestHelp }),
|
|
1660
|
+
windowDays && windowDays > 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.meta, "data-sf-invoice-window": true, children: fill(m.windowNote, { days: windowDays }) }) : null
|
|
1661
|
+
] });
|
|
1244
1662
|
const can = invoice?.can_request !== false;
|
|
1245
1663
|
if (can && (onRequest || requestHref)) {
|
|
1246
1664
|
actions = onRequest ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.primary, onClick: onRequest, "data-sf-invoice-request": true, children: m.request }) : /* @__PURE__ */ jsxRuntime.jsx("a", { href: requestHref, className: cx.primary, "data-sf-invoice-request": true, children: m.request });
|
|
@@ -1298,6 +1716,18 @@ function InvoiceStatusCard(props) {
|
|
|
1298
1716
|
title = m.cancelledTitle;
|
|
1299
1717
|
body = /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.body, children: m.cancelledBody });
|
|
1300
1718
|
break;
|
|
1719
|
+
case "not_enabled":
|
|
1720
|
+
case "unknown":
|
|
1721
|
+
case "unreachable": {
|
|
1722
|
+
title = state === "not_enabled" ? m.notEnabledTitle : m.unknownTitle;
|
|
1723
|
+
body = /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.body, children: state === "not_enabled" ? m.notEnabledBody : state === "unreachable" ? m.unreachableBody : m.unknownBody });
|
|
1724
|
+
const mail = contact?.url ?? (contact?.email ? `mailto:${contact.email}` : null);
|
|
1725
|
+
actions = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1726
|
+
state === "unreachable" && onRefresh ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.secondary, onClick: manualRefresh, "data-sf-invoice-refresh": true, children: m.refresh }) : null,
|
|
1727
|
+
mail ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: mail, className: cx.secondary, "data-sf-invoice-contact": true, children: m.contact }) : null
|
|
1728
|
+
] });
|
|
1729
|
+
break;
|
|
1730
|
+
}
|
|
1301
1731
|
default:
|
|
1302
1732
|
title = m.unavailableTitle;
|
|
1303
1733
|
body = /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.body, children: m.unavailableBody });
|
|
@@ -1309,6 +1739,7 @@ function InvoiceStatusCard(props) {
|
|
|
1309
1739
|
title
|
|
1310
1740
|
] }),
|
|
1311
1741
|
/* @__PURE__ */ jsxRuntime.jsx("div", { "aria-live": live || state === "issued" ? "polite" : void 0, "data-sf-invoice-live": true, children: body }),
|
|
1742
|
+
testMode ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.meta, "data-sf-invoice-sandbox": true, children: m.sandbox }) : null,
|
|
1312
1743
|
actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx.actions, "data-sf-invoice-actions": true, children: actions }) : null
|
|
1313
1744
|
] });
|
|
1314
1745
|
}
|
|
@@ -1321,6 +1752,9 @@ var ICON = {
|
|
|
1321
1752
|
needs_review: "\u25D0",
|
|
1322
1753
|
window_closed: "\u2013",
|
|
1323
1754
|
unavailable: "\u2013",
|
|
1755
|
+
not_enabled: "\u2013",
|
|
1756
|
+
unknown: "?",
|
|
1757
|
+
unreachable: "?",
|
|
1324
1758
|
cancelled: "\xD7"
|
|
1325
1759
|
};
|
|
1326
1760
|
|
|
@@ -1328,17 +1762,23 @@ exports.FiscalIdentityFields = FiscalIdentityFields;
|
|
|
1328
1762
|
exports.IdentityDocumentReader = IdentityDocumentReader;
|
|
1329
1763
|
exports.InvoiceStatusCard = InvoiceStatusCard;
|
|
1330
1764
|
exports.InvoiceToggle = InvoiceToggle;
|
|
1765
|
+
exports.ScanTaxDocument = ScanTaxDocument;
|
|
1766
|
+
exports.cameraSupported = cameraSupported;
|
|
1331
1767
|
exports.entryLabel = entryLabel;
|
|
1332
1768
|
exports.fiscalMessages = fiscalMessages;
|
|
1333
1769
|
exports.invoiceCardState = invoiceCardState;
|
|
1334
1770
|
exports.maskTaxId = maskTaxId;
|
|
1335
1771
|
exports.normalizeTaxId = normalizeTaxId;
|
|
1336
1772
|
exports.personTypeByLength = personTypeByLength;
|
|
1773
|
+
exports.personTypeFromPatterns = personTypeFromPatterns;
|
|
1774
|
+
exports.personTypeResolver = personTypeResolver;
|
|
1337
1775
|
exports.postalCodeProblem = postalCodeProblem;
|
|
1338
1776
|
exports.regimesFor = regimesFor;
|
|
1777
|
+
exports.suggestInvoiceUse = suggestInvoiceUse;
|
|
1339
1778
|
exports.summarizeExtraction = summarizeExtraction;
|
|
1340
1779
|
exports.taxIdProblem = taxIdProblem;
|
|
1341
1780
|
exports.toInvoiceRequest = toInvoiceRequest;
|
|
1781
|
+
exports.useCameraQr = useCameraQr;
|
|
1342
1782
|
exports.usesFor = usesFor;
|
|
1343
1783
|
exports.validateFiscalValues = validateFiscalValues;
|
|
1344
1784
|
//# sourceMappingURL=fiscal.cjs.map
|