Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,17 @@
"react-joyride": "^3.1.0",
"react-leaflet": "^5.0.0",
"react-router-dom": "^7.14.0",
"tailwindcss": "^4.2.2"
"tailwindcss": "^4.2.2",
"vite-plugin-pwa": "^1.3.0",
"workbox-window": "^7.4.1"
Comment thread
SaumyaSngh323 marked this conversation as resolved.
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"@types/leaflet": "^1.9.21",
"@types/node": "^25.9.1",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vite-pwa/assets-generator": "^1.0.2",
"@vitejs/plugin-react": "^6.0.1",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
Expand Down
3 changes: 3 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import './index.css'
import App from './App.tsx'
import { initTheme } from './lib/theme';
import { syncOfflineScans } from './utils/syncManager'
import { initTheme } from './lib/theme'
Comment on lines +7 to 9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove duplicate import.

initTheme is imported twice on lines 7 and 9. This will likely cause a build or linting error.

🔧 Fix: Remove the duplicate import
 import { initTheme } from './lib/theme';
 import { syncOfflineScans } from './utils/syncManager'
-import { initTheme } from './lib/theme'
 import "./i18n";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { initTheme } from './lib/theme';
import { syncOfflineScans } from './utils/syncManager'
import { initTheme } from './lib/theme'
import { initTheme } from './lib/theme';
import { syncOfflineScans } from './utils/syncManager'
import "./i18n";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main.tsx` around lines 7 - 9, There is a duplicate import of initTheme in
the module; remove one of the import statements so initTheme is only imported
once (remove either the first or the second import line that references
initTheme) and ensure any other imports like syncOfflineScans remain unchanged;
update the import block that contains initTheme to have a single import
declaration for initTheme from './lib/theme'.

import "./i18n";

// Initialize theme before rendering the app to prevent flicker
initTheme();
syncOfflineScans()

// PostHog is only initialized when the key is present.
// Contributors running locally without the key will have it silently disabled.
Expand Down
44 changes: 44 additions & 0 deletions src/utils/offlineQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const DB_NAME = 'FreshScanDB'
const STORE_NAME = 'scanQueue'

export function openDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const req = indexedDB.open(DB_NAME, 1)
req.onupgradeneeded = (e: IDBVersionChangeEvent) => {
const db = (e.target as IDBOpenDBRequest).result
db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true })
}
req.onsuccess = () => resolve(req.result)
req.onerror = () => reject(req.error)
})
}

export async function queueScan(scanData: Record<string, unknown>) {
const db = await openDB()
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite')
tx.objectStore(STORE_NAME).add({ ...scanData, timestamp: Date.now() })
tx.oncomplete = resolve
tx.onerror = () => reject(tx.error)
})
}
Comment thread
SaumyaSngh323 marked this conversation as resolved.

export async function getPendingScans() {
const db = await openDB()
return new Promise<Record<string, unknown>[]>((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readonly')
const req = tx.objectStore(STORE_NAME).getAll()
req.onsuccess = () => resolve(req.result as Record<string, unknown>[])
req.onerror = () => reject(req.error)
})
}
Comment on lines +26 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return type should match the queue item interface.

The return type Record<string, unknown>[] is too loose and should use the same ScanQueueItem interface suggested for queueScan. This ensures type safety across the entire queue lifecycle.

📝 Proposed type fix
-export async function getPendingScans() {
+export async function getPendingScans(): Promise<(ScanQueueItem & { id: number; timestamp: number })[]> {
   const db = await openDB()
-  return new Promise<Record<string, unknown>[]>((resolve, reject) => {
+  return new Promise((resolve, reject) => {
     const tx = db.transaction(STORE_NAME, 'readonly')
     const req = tx.objectStore(STORE_NAME).getAll()
-    req.onsuccess = () => resolve(req.result as Record<string, unknown>[])
+    req.onsuccess = () => resolve(req.result)
     req.onerror = () => reject(req.error)
   })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/offlineQueue.ts` around lines 26 - 34, Change getPendingScans to
return the concrete ScanQueueItem[] type instead of Record<string, unknown>[].
Update its Promise generic and the onsuccess cast to use ScanQueueItem so the
function signature and resolved value match the queueScan item interface; locate
getPendingScans, ScanQueueItem, queueScan, STORE_NAME and openDB in this file
and replace the loose Record typing with ScanQueueItem throughout the function.


export async function removeScan(id: number) {
const db = await openDB()
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite')
tx.objectStore(STORE_NAME).delete(id)
tx.oncomplete = resolve
tx.onerror = () => reject(tx.error)
})
}
40 changes: 40 additions & 0 deletions src/utils/syncManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getPendingScans, removeScan } from './offlineQueue'

// 1. Define a clean interface for a Scan item to resolve type issues
interface ScanItem {
id: number;
[key: string]: unknown; // Allows any other dynamic fields
}

async function uploadScan(scan: ScanItem) {
const response = await fetch('/api/scans', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(scan),
})
if (!response.ok) throw new Error('Upload failed')
return response.json()
}
Comment thread
SaumyaSngh323 marked this conversation as resolved.

export async function syncOfflineScans() {
if (!navigator.onLine) return

// Cast the pending scans array to our ScanItem interface
const pending = (await getPendingScans()) as ScanItem[]

for (const scan of pending) {
try {
await uploadScan(scan)
await removeScan(scan.id)
console.log(`✅ Synced scan ID: ${scan.id}`)
} catch (err) {
console.error(`❌ Failed to sync scan ${scan.id}:`, err)
}
}
}

// Auto sync when internet comes back
window.addEventListener('online', () => {
console.log('🌐 Back online – syncing offline scans...')
syncOfflineScans()
})
48 changes: 44 additions & 4 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import { VitePWA } from 'vite-plugin-pwa'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
Expand All @@ -10,6 +14,45 @@ export default defineConfig({
tailwindcss(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
manifest: {
name: 'FreshScan AI',
short_name: 'FreshScan',
description: 'AI-powered fish freshness scanner',
theme_color: '#ffffff',
background_color: '#ffffff',
display: 'standalone',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png'
}
]
},
Comment thread
SaumyaSngh323 marked this conversation as resolved.
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https?:\/\/localhost:8000\/api\//,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Runtime caching pattern only matches localhost development requests.

The urlPattern at line 42 uses ^https?:\/\/localhost:8000\/api\/ which will only match requests during local development. In production, API requests won't go to localhost:8000, so this cache rule will never apply.

You need to use a pattern that matches production API URLs or use a relative path pattern.

♻️ Proposed fix to cache production API requests
-            urlPattern: /^https?:\/\/localhost:8000\/api\//,
+            urlPattern: ({ url }) => url.pathname.startsWith('/api/'),
             handler: 'NetworkFirst',

This will match /api/* requests regardless of the hostname (localhost or production domain).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
urlPattern: /^https?:\/\/localhost:8000\/api\//,
urlPattern: ({ url }) => url.pathname.startsWith('/api/'),
handler: 'NetworkFirst',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite.config.ts` at line 42, The runtimeCaching rule's urlPattern in
vite.config.ts currently uses the hostname-bound regex
/^https?:\/\/localhost:8000\/api\// which only matches local dev requests;
update the urlPattern used in the service worker runtimeCaching entry (the
urlPattern field) to a host-agnostic pattern that matches your API path in
production (for example a relative-path regex like /^\/api\// or /\/api\//) so
/api/* requests are cached regardless of hostname.

handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24
}
}
}
]
}
})
Comment on lines 15 to +54

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Critical: VitePWA plugin configured twice — incomplete merge removed critical caching rules.

The PR adds a new VitePWA configuration (lines 15-54) but fails to remove the old configuration (lines 56-87). This creates:

  1. Syntax error: Two VitePWA() calls within the same plugins array
  2. Lost runtime caching: The old config (lines 66-85) includes critical CacheFirst rules for .onnx model files (~12-26 MB) and .wasm files. The new config (lines 40-52) only caches /api/ requests, completely removing model/wasm caching.
  3. Service worker crash: Without runtime caching for large model files, the service worker will attempt to precache them and timeout/crash (as flagged by the PR reviewer).

This directly confirms the reviewer's concern: "The PR reportedly overwrote vite.config.ts and removed a recent globIgnores fix. If merged, the app would attempt to precache a large .wasm file (~26MB) and crash."

🔧 Proposed fix to merge configurations correctly

Remove the duplicate old configuration and merge the runtime caching rules:

     VitePWA({
       registerType: 'autoUpdate',
       includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
       manifest: {
         name: 'FreshScan AI',
         short_name: 'FreshScan',
         description: 'AI-powered fish freshness scanner',
         theme_color: '`#ffffff`',
         background_color: '`#ffffff`',
         display: 'standalone',
         icons: [
           {
             src: 'pwa-192x192.png',
             sizes: '192x192',
             type: 'image/png'
           },
           {
             src: 'pwa-512x512.png',
             sizes: '512x512',
             type: 'image/png'
           }
         ]
       },
       workbox: {
-        globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
+        globPatterns: ['**/*.{js,css,html,ico,svg,gif,png}'],
         runtimeCaching: [
+          {
+            urlPattern: /\/models\/.*\.onnx$/i,
+            handler: 'CacheFirst',
+            options: {
+              cacheName: 'onnx-models',
+              expiration: { maxAgeSeconds: 30 * 24 * 60 * 60 },
+              cacheableResponse: { statuses: [0, 200] },
+            },
+          },
+          {
+            urlPattern: /.*\.wasm$/i,
+            handler: 'CacheFirst',
+            options: {
+              cacheName: 'wasm-cache',
+              expiration: { maxAgeSeconds: 30 * 24 * 60 * 60 },
+              cacheableResponse: { statuses: [0, 200] },
+            },
+          },
           {
             urlPattern: /^https?:\/\/localhost:8000\/api\//,
             handler: 'NetworkFirst',
             options: {
               cacheName: 'api-cache',
               expiration: {
                 maxEntries: 50,
                 maxAgeSeconds: 60 * 60 * 24
               }
             }
           }
         ]
       }
     })
   ],
-
-      // Use the existing manifest.json in public/
-      manifest: false,
-      workbox: {
-        // Precache ONLY small app-shell assets (JS/CSS/HTML/icons).
-        // DO NOT include .wasm or .onnx here — they are large (12–26 MB each)
-        // and would cause the Service Worker install to time out.
-        // They are handled below with lazy runtime caching instead.
-        globPatterns: ['**/*.{js,css,html,ico,svg,gif,png}'],
-        // Runtime caching: WASM and ONNX files are cached on first use
-        // (CacheFirst) so subsequent offline scans load instantly.
-        runtimeCaching: [
-          {
-            urlPattern: /\/models\/.*\.onnx$/i,
-            handler: 'CacheFirst',
-            options: {
-              cacheName: 'onnx-models',
-              expiration: { maxAgeSeconds: 30 * 24 * 60 * 60 }, // 30 days
-              cacheableResponse: { statuses: [0, 200] },
-            },
-          },
-          {
-            urlPattern: /.*\.wasm$/i,
-            handler: 'CacheFirst',
-            options: {
-              cacheName: 'wasm-cache',
-              expiration: { maxAgeSeconds: 30 * 24 * 60 * 60 },
-              cacheableResponse: { statuses: [0, 200] },
-            },
-          },
-        ],
-      },
-    }),
-  ],
-

Note: Also exclude .woff2 from globPatterns if font files are large, and add .gif (for favicon) as shown in the old config.

Also applies to: 56-87

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite.config.ts` around lines 15 - 54, There are two VitePWA(...) plugin calls
causing a syntax error and the new one overwrote important runtimeCaching rules;
remove the duplicate plugin and merge the old runtimeCaching entries into the
single VitePWA call (keep the existing api NetworkFirst rule and add back
CacheFirst rules for large assets like .onnx and .wasm with appropriate
cacheName/expiration settings), ensure globPatterns and globIgnores are updated
so large model/wasm files are not precached (use globIgnores to exclude *.wasm
and large model files) and keep any asset types you need (e.g., .woff2/.gif) in
includeAssets/globPatterns as appropriate; locate and update the VitePWA
invocation and its runtimeCaching, globPatterns, and globIgnores blocks to
perform this merge and remove the old duplicate call.

],
// Use the existing manifest.json in public/
manifest: false,
workbox: {
Expand Down Expand Up @@ -45,15 +88,12 @@ export default defineConfig({
],

server: {
// In local dev, proxy /api/* to the FastAPI backend at :8000.
// This avoids CORS issues and means the frontend never needs to
// hard-code the backend port.
// In production, VITE_API_URL is set externally so this block is unused.
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
Comment on lines 92 to 96

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify backend API endpoints and scan upload implementation

echo "=== Checking if /api/scans endpoint exists in backend ==="
fd -e py -e ts -e js . | xargs rg -n "POST.*['\"]/?scans['\"]|@.*route.*['\"]/?scans['\"]" 2>/dev/null || echo "No POST /scans route found"

echo ""
echo "=== Checking syncManager upload format ==="
rg -A 5 "fetch\('/api/scans'" src/utils/syncManager.ts

echo ""
echo "=== Checking if backend expects multipart/form-data ==="
fd -e py . | xargs rg -n "multipart|UploadFile|File\(" 2>/dev/null | head -20

Repository: jpdevhub/FreshScanAi

Length of output: 932


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find any occurrences of '/scans' or 'scans' route strings in backend ==="
rg -n --hidden --no-ignore -S '"/scans"|'\''/scans'\''|/scans|scans' backend || true

echo ""
echo "=== Inspect backend/main.py around UploadFile/File handlers (likely scan upload endpoints) ==="
python3 - <<'PY'
import re, pathlib
p = pathlib.Path("backend/main.py")
txt = p.read_text(encoding="utf-8", errors="ignore").splitlines()
for i,line in enumerate(txt, start=1):
    if "UploadFile" in line or "File(" in line:
        start=max(1,i-10); end=min(len(txt), i+20)
        print(f"\n--- backend/main.py:{i} ---")
        for j in range(start,end+1):
            print(f"{j:5d}:{txt[j-1]}")
PY

echo ""
echo "=== Check FastAPI app/prefix/router mounting in backend/main.py ==="
rg -n 'include_router|APIRouter|prefix=|app\.' backend/main.py || true

Repository: jpdevhub/FreshScanAi

Length of output: 17815


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Show syncManager.ts upload request code around fetch('/api/scans') ==="
fd -e ts -e tsx -e js -e jsx syncManager src || true
fd -e ts -e tsx -e js -e jsx syncManager.ts . || true

FILE="$(fd -e ts -e tsx -e js -e jsx syncManager.ts . | head -n 1)"
echo "Using: $FILE"

nl -ba "$FILE" | sed -n '1,200p' | sed -n '1,120p' >/dev/null || true
# Print from around the fetch call
nl -ba "$FILE" | rg -n "fetch\\('\\/api\\/scans'\\)" -n --context=12 || true

Repository: jpdevhub/FreshScanAi

Length of output: 425


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/utils/syncManager.ts"
[ -f "$FILE" ] || { echo "Missing $FILE"; exit 1; }

echo "=== Locate fetch('/api/scans' usage in $FILE ==="
rg -n "fetch\\('\\/api\\/scans'\\)|\\/api\\/scans" "$FILE" || true

echo ""
echo "=== Print syncManager.ts around first /api/scans occurrence ==="
LINE="$(rg -n "fetch\\('\\/api\\/scans'\\)|\\/api\\/scans" "$FILE" | head -n 1 | cut -d: -f1 || true)"
if [ -n "${LINE:-}" ]; then
  START=$((LINE-20)); END=$((LINE+25));
  START=$(( START<1 ? 1 : START ))
  sed -n "${START},${END}p" "$FILE" | cat -n
else
  echo "No /api/scans occurrence found."
fi

echo ""
echo "=== Also check whether syncManager calls /api/v1/scan or /api/v1/scan-auto ==="
rg -n "\\/api\\/v1\\/scan|\\/api\\/v1\\/scan-auto" "$FILE" || true

Repository: jpdevhub/FreshScanAi

Length of output: 652


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/utils/syncManager.ts"
[ -f "$FILE" ] || { echo "Missing $FILE"; exit 1; }

echo "=== Fixed-string matches for endpoints in $FILE ==="
for s in "/api/scans" "/api/v1/scan" "/api/v1/scan-auto" "/api/v1/scans"; do
  echo "--- $s ---"
  rg -nF "$s" "$FILE" || true
  echo ""
done

echo "=== Print around first '/api/scans' match ==="
LINE="$(rg -nF "/api/scans" "$FILE" | head -n 1 | cut -d: -f1 || true)"
if [ -n "${LINE:-}" ]; then
  START=$((LINE-30)); END=$((LINE+30));
  START=$(( START<1 ? 1 : START ))
  sed -n "${START},${END}p" "$FILE" | cat -n
else
  echo "No /api/scans match found."
fi

Repository: jpdevhub/FreshScanAi

Length of output: 1723


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "getPendingScans|offlineQueue" src/utils -S
fd -e ts offlineQueue src/utils -a 2>/dev/null || true
FILE="$(fd -e ts offlineQueue src/utils | head -n 1 || true)"
echo "Offline queue file: $FILE"
[ -f "$FILE" ] && sed -n '1,220p' "$FILE" | cat -n
rg -n "uploadScan\\(|syncOfflineScans\\(|scan\\.id|pending" src/utils -S

Repository: jpdevhub/FreshScanAi

Length of output: 2919


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="vite.config.ts"
[ -f "$FILE" ] || { echo "Missing $FILE"; exit 1; }
echo "=== vite.config.ts: showing lines 70-120 ==="
sed -n '70,120p' "$FILE" | cat -n
echo ""
echo "=== Find proxy config for /api ==="
rg -n "\"/api\"|'/api'|proxy" "$FILE" || true

Repository: jpdevhub/FreshScanAi

Length of output: 1081


Fix sync manager upload: wrong backend route and wrong content type

File: vite.config.ts
Lines: 92-96

      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
      },
  • src/utils/syncManager.ts syncs offline scans by POSTing JSON to /api/scans (Content-Type: application/json, body: JSON.stringify(scan)).
  • With the /api proxy, that request goes to the backend as /scans, but backend/main.py only defines scan uploads as multipart endpoints:
    • POST /api/v1/scan (expects body_image/eye_image/gill_image via File(...) + vendor_id/is_target_domain via Form(...))
    • POST /api/v1/scan-auto (expects image via File(...))
    • There is no POST route for /scans (or POST /api/v1/scans).

Adjust syncManager to call the correct endpoint (/api/v1/scan or /api/v1/scan-auto) and send a FormData multipart request with the required file fields (and vendor_id/is_target_domain for /api/v1/scan).

🧰 Tools
🪛 Biome (2.4.16)

[error] 92-92: Expected a semicolon or an implicit semicolon after a statement, but found none

(parse)


[error] 94-94: Expected a semicolon or an implicit semicolon after a statement, but found none

(parse)


[error] 95-95: Expected a statement but instead found ','.

(parse)


[error] 96-96: Expected a statement but instead found ','.

(parse)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite.config.ts` around lines 92 - 96, The syncManager currently POSTs JSON to
'/api/scans' which the backend doesn't accept; update the upload logic in
src/utils/syncManager.ts (the function that builds and sends the offline scan
request) to POST multipart FormData to the correct backend routes (/api/v1/scan
for vendor uploads requiring body_image/eye_image/gill_image plus vendor_id and
is_target_domain, or /api/v1/scan-auto for single-image auto uploads). Build a
FormData instance instead of JSON.stringify(scan), append the file fields using
the original File/Blob objects (keys: body_image, eye_image, gill_image or
image), append vendor_id and is_target_domain for /api/v1/scan, remove explicit
Content-Type so the browser sets the multipart boundary, and update the fetch
URL to use '/api/v1/scan' or '/api/v1/scan-auto' accordingly and handle the
response/errors as before.

},
})
});
Comment thread
SaumyaSngh323 marked this conversation as resolved.
Loading