PageSourceSearch

https://smart-penny.com/assets/ReceiptScanner-CZ4OInOK.js

js smart-penny.com collected 2026-10-09 22:04:16 UTC 9,391 bytes, 1 lines download raw bytes

1import{r as t,v as T,j as e,B as l,X as B,u as K,a as Q,s as R,A as Y,m as Z}from"./index-CCiodsGZ.js";import{D as ee,a as $,b as se,c as ae,d as te,e as ne}from"./badge-Bhbic_E_.js";import{b as ie,c as re,d as oe,e as ce,f as q}from"./select-DbcSiZpj.js";import{L as w}from"./label-D25ZngxJ.js";import{S as le}from"./switch-camera-DPKWQXbX.js";import{C as I,U as de}from"./BankConnectionsDialog-BL5OaX5R.js";import{p as me}from"./dateHelpers-CnPO6s5-.js";import{L as ue}from"./loader-circle-BFr5KSsv.js";import{C as pe}from"./circle-check-big-Bs1xPWrp.js";import"./index-70QQoWkT.js";import"./format-TYZEh-mT.js";import"./users-CS_In3pV.js";import"./HelpDrawer-CPqX6lcf.js";import"./ContactSupportDialog-C8uPuSlv.js";import"./mail-SOMQrZEj.js";import"./chevron-left-B3khCwEx.js";import"./MobileFixedHeaderWrapper-iaMik2q8.js";import"./trending-up-DKmTJJoN.js";import"./checkbox-C9uLko79.js";import"./alert-dialog-BQrFQ59l.js";import"./circle-alert-DrLLMRx2.js";import"./arrow-left-cqWTetYG.js";import"./trash-2-u02B44m3.js";const he=({onCapture:p,onClose:g})=>{const h=t.useRef(null),v=t.useRef(null),[b,d]=t.useState(null),[x,E]=t.useState("environment"),[S,n]=t.useState(!1);t.useEffect(()=>(y(),j(),()=>{f()}),[x]);const j=async()=>{try{const m=(await navigator.mediaDevices.enumerateDevices()).filter(o=>o.kind==="videoinput");n(m.length>1)}catch(a){console.error("Error checking cameras:",a)}},y=async()=>{try{const a=await navigator.mediaDevices.getUserMedia({video:{facingMode:x,width:{ideal:1920},height:{ideal:1080}}});h.current&&(h.current.srcObject=a,d(a))}catch(a){console.error("Camera access error:",a),a instanceof DOMException&&(a.name==="NotAllowedError"?T.error("Camera permission denied. Please enable camera access in your browser settings."):a.name==="NotFoundError"?T.error("No camera found on this device."):T.error("Unable to access camera. Please try again."))}},f=()=>{b&&(b.getTracks().forEach(a=>a.stop()),d(null))},_=()=>{if(h.current&&v.current){const a=h.current,m=v.current;m.width=a.videoWidth,m.height=a.videoHeight;const o=m.getContext("2d");if(o){o.drawImage(a,0,0);const D=m.toDataURL("image/jpeg",.9);f(),p(D)}}},N=()=>{E(a=>a==="environment"?"user":"environment")};return e.jsxs("div",{className:"fixed inset-0 z-50 bg-background flex flex-col",children:[e.jsxs("div",{className:"flex items-center justify-between p-4 border-b",children:[e.jsx("h3",{className:"text-lg font-semibold",children:"Take Photo"}),e.jsx(l,{variant:"ghost",size:"icon",onClick:g,children:e.jsx(B,{className:"h-5 w-5"})})]}),e.jsxs("div",{className:"flex-1 min-h-0 relative bg-black overflow-hidden",children:[e.jsx("video",{ref:h,autoPlay:!0,playsInline:!0,className:"w-full h-full object-contain"}),e.jsx("canvas",{ref:v,className:"hidden"})]}),e.jsxs("div",{className:"p-6 pb-8 flex items-center justify-center gap-4 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60",style:{paddingBottom:"max(2rem, env(safe-area-inset-bottom))"},children:[S&&e.jsx(l,{variant:"outline",size:"icon",onClick:N,className:"h-12 w-12",children:e.jsx(le,{className:"h-5 w-5"})}),e.jsx(l,{onClick:_,size:"lg",className:"h-16 w-16 rounded-full shadow-lg ring-2 ring-primary/20",children:e.jsx(I,{className:"h-6 w-6"})})]})]})},F="__none__",ze=({userId:p,jointAccountId:g,onTransactionScanned:h,showText:v=!1,triggerButton:b})=>{const[d,x]=t.useState(!1),[E,S]=t.useState(!1),[n,j]=t.useState(null),[y,f]=t.useState(null),[_,N]=t.useState(!1),[a,m]=t.useState(!1),[o,D]=t.useState(null),[H,V]=t.useState([]),P=t.useRef(null),{toast:C}=K(),{isReadOnly:L}=Q();t.useEffect(()=>{d&&p&&W()},[d,p,g]),t.useEffect(()=>{d&&X()},[d]);const W=async()=>{let s=R.from("budget_envelopes").select("id, name");g?s=s.or(`and(user_id.eq.${p},joint_account_id.is.null),joint_account_id.eq.${g}`):s=s.eq("user_id",p).is("joint_account_id",null);const{data:u}=await s;V(u||[])},X=()=>{m(typeof navigator<"u"&&"mediaDevices"in navigator&&"getUserMedia"in navigator.mediaDevices)},M=async s=>{var k,z,A,O;let u;if(typeof s=="string")u=s,f(s);else{if(!s.type.startsWith("image/")){C({title:"Invalid file",description:"Please select an image file",variant:"destructive"});return}const i=new FileReader;i.onload=c=>{var r;return f((r=c.target)==null?void 0:r.result)},i.readAsDataURL(s),u=await new Promise(c=>{const r=new FileReader;r.onloadend=()=>
1c(r.result),r.readAsDataURL(s)})}S(!0),j(null);try{const{data:i,error:c}=await R.functions.invoke("scan-receipt",{body:{imageData:u}});if(c)throw c;const r={amount:Number(i.amount||0),description:i.description||"",date:i.date||new Date().toISOString().split("T")[0],envelopeId:i.envelopeId||null,suggestedEnvelope:i.suggestedEnvelope||null,confidence:Number(i.confidence||0)};j(r),D(r.envelopeId),C({title:"Receipt scanned!",description:`Found: ${r.description} - $${Number(r.amount||0).toFixed(2)}`})}catch(i){console.error("Error scanning receipt:",i);let c=i.message||"Could not process the receipt image";(k=i.message)!=null&&k.includes("429")||(z=i.message)!=null&&z.includes("Rate limit")?c="Rate limit exceeded. Please try again in a moment.":((A=i.message)!=null&&A.includes("402")||(O=i.message)!=null&&O.includes("Credits exhausted"))&&(c="AI credits exhausted. Please add credits to continue."),C({title:"Scan failed",description:c,variant:"destructive"})}finally{S(!1)}},G=async s=>{N(!1),x(!0),await M(s)},J=async()=>{if(n){if(L){C({title:"Subscription Required",description:"Reactivate your subscription to add transactions."});return}try{const{error:s}=await R.from("transactions").insert({user_id:p,joint_account_id:g||null,amount:n.amount,description:n.description,transaction_date:n.date,type:"expense",envelope_id:o});if(s)throw s;o&&o!==n.envelopeId&&await R.functions.invoke("learn-categorization",{body:{description:n.description,old_envelope_id:n.envelopeId,new_envelope_id:o}}),C({title:"Transaction added",description:"Receipt has been saved to your transactions"}),h(),U()}catch(s){C({title:"Failed to save",description:s.message,variant:"destructive"})}}},U=()=>{x(!1),j(null),f(null),S(!1),N(!1),D(null)};return e.jsxs(e.Fragment,{children:[_&&e.jsx(he,{onCapture:G,onClose:()=>N(!1)}),e.jsxs(ee,{open:d,onOpenChange:x,children:[b?e.jsx($,{asChild:!0,children:b}):e.jsx($,{asChild:!0,children:e.jsxs(l,{variant:"outline",size:"sm",children:[e.jsx(I,{className:`w-4 h-4 ${v?"mr-2":"xl:mr-2"}`}),e.jsx("span",{className:v?"":"hidden xl:inline",children:"Scan Receipt"})]})}),e.jsxs(se,{className:"max-w-md",children:[e.jsxs(ae,{children:[e.jsx(te,{children:"Scan Receipt"}),e.jsx(ne,{children:"Take a photo or upload an image of your receipt"})]}),e.jsxs("div",{className:"space-y-4",children:[y&&e.jsxs("div",{className:"relative rounded-lg overflow-hidden border",children:[e.jsx("img",{src:y,alt:"Receipt preview",className:"w-full h-48 object-contain bg-muted"}),e.jsx(l,{variant:"ghost",size:"icon",className:"absolute top-2 right-2",onClick:()=>{f(null),j(null)},children:e.jsx(B,{className:"w-4 h-4"})})]}),!y&&e.jsxs("div",{className:"grid gap-2",children:[a&&e.jsxs(l,{variant:"outline",onClick:()=>{x(!1),N(!0)},className:"w-full",children:[e.jsx(I,{className:"w-4 h-4 mr-2"}),"Take Photo"]}),e.jsx("input",{ref:P,type:"file",accept:"image/*",className:"hidden",onChange:s=>{var k;const u=(k=s.target.files)==null?void 0:k[0];u&&M(u)}}),e.jsxs(l,{variant:"outline",onClick:()=>{var s;return(s=P.current)==null?void 0:s.click()},className:"w-full",children:[e.jsx(de,{className:"w-4 h-4 mr-2"}),"Upload Image"]})]}),E&&e.jsxs(Y,{children:[e.jsx(ue,{className:"h-4 w-4 animate-spin"}),e.jsx(Z,{children:"Analyzing receipt..."})]}),n&&!E&&e.jsxs("div",{className:"space-y-4 border rounded-lg p-4",children:[e.jsxs("div",{className:"flex items-start gap-2",children:[e.jsx(pe,{className:"h-5 w-5 text-green-600 mt-0.5"}),e.jsxs("div",{className:"flex-1 space-y-3",children:[e.jsxs("div",{children:[e.jsx(w,{className:"text-xs text-muted-foreground",children:"Amount"}),e.jsxs("p",{className:"text-lg font-semibold",children:["$",Number(n.amount||0).toFixed(2)]})]}),e.jsxs("div",{children:[e.jsx(w,{className:"text-xs text-muted-foreground",children:"Merchant"}),e.jsx("p",{children:n.description})]}),e.jsxs("div",{children:[e.jsx(w,{className:"text-xs text-muted-foreground",children:"Date"}),e.jsx("p",{children:(()=>{const s=me(n.date);return isNaN(s.getTime())?"(unknown date)":s.toLocaleDateString()})()})]}),n.suggestedEnvelope&&e.jsxs("div",{children:[e.jsx(w,{className:"text-xs text-muted-foreground",children:"Suggested Envelope"}),e.jsx("p",{className:"text-sm font-medium text-primary",children:n.suggestedEnvelope})]}),e.jsxs("div",{children:[e.jsx(w,{className:"text-xs text-muted-foreground",children:"Envelope"}),e.jsxs(ie,{value:o??F,onValueChange:s=>D(s===F?null:s),children:[e.jsx(re,{children:e.jsx(oe,{placeholder:"Choose envelope (optional)"})}),e.jsxs(ce,{children:[e.jsx(q,{value:F,children:"None"}),H.map(s=>e.jsx(q,{value:s.id,children:s.name},s.id))]})]})]}),e.jsxs("div",{children:[e.jsx(w,{className:"text-xs text-muted-foreground",children:"Confidence"}),e.jsxs("p",{className:"text-sm",children:[(n.confidence*100).toFixed(0),"%"]})]})]})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(l,{onClick:J,className:"flex-1",disabled:L,children:"Save Transaction"}),e.jsx(l,{variant:"outline",onClick:U,children:"Cancel"})]})]})]})]})]})]})};export{ze as ReceiptScanner};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.