}
>
diff --git a/frontend/src/components/scan/ScanResultView.test.tsx b/frontend/src/components/scan/ScanResultView.test.tsx
index 1df88597..70bc3fb0 100644
--- a/frontend/src/components/scan/ScanResultView.test.tsx
+++ b/frontend/src/components/scan/ScanResultView.test.tsx
@@ -50,11 +50,13 @@ vi.mock("@/components/scan/ScanMissSubmitCTA", () => ({
ScanMissSubmitCTA: ({
ean,
hasPendingSubmission,
+ country,
}: {
ean: string;
hasPendingSubmission?: boolean;
+ country?: string;
}) => (
-
),
}));
@@ -183,6 +185,19 @@ describe("ScanNotFoundView", () => {
expect(cta).toHaveAttribute("data-pending", "true");
});
+ it("passes country prop to ScanMissSubmitCTA", () => {
+ render(
+
,
+ );
+ const cta = screen.getByTestId("scan-miss-submit-cta");
+ expect(cta).toHaveAttribute("data-country", "DE");
+ });
+
it("renders scan-another and history links", () => {
render(
void;
+ country?: string;
}
export function ScanNotFoundView({
ean,
scanResult,
onReset,
+ country,
}: ScanNotFoundProps) {
const { t } = useTranslation();
@@ -101,6 +103,7 @@ export function ScanNotFoundView({
diff --git a/frontend/src/hooks/use-submissions.ts b/frontend/src/hooks/use-submissions.ts
index c5a8b021..11c70def 100644
--- a/frontend/src/hooks/use-submissions.ts
+++ b/frontend/src/hooks/use-submissions.ts
@@ -95,6 +95,8 @@ export function useSubmitProduct() {
category?: string;
photoUrl?: string;
notes?: string;
+ scanCountry?: string;
+ suggestedCountry?: string;
}) => {
const result = await submitProduct(supabase, params);
if (!result.ok) throw new Error(result.error.message);
diff --git a/frontend/src/lib/api-gateway.test.ts b/frontend/src/lib/api-gateway.test.ts
index 370263a4..e48962aa 100644
--- a/frontend/src/lib/api-gateway.test.ts
+++ b/frontend/src/lib/api-gateway.test.ts
@@ -203,11 +203,25 @@ describe("recordScanViaGateway", () => {
const result = await recordScanViaGateway(fakeSupabase, "5901234123457");
expect(mockInvoke).toHaveBeenCalledWith("api-gateway", {
- body: { action: "record-scan", ean: "5901234123457" },
+ body: { action: "record-scan", ean: "5901234123457", scan_country: null },
});
expect(result).toEqual({ ok: true, data: { scan_id: 42 } });
});
+ it("should pass scan_country to gateway when provided", async () => {
+ mockInvoke.mockResolvedValue({
+ data: { ok: true, data: { scan_id: 43 } },
+ error: null,
+ });
+
+ const result = await recordScanViaGateway(fakeSupabase, "5901234123457", "PL");
+
+ expect(mockInvoke).toHaveBeenCalledWith("api-gateway", {
+ body: { action: "record-scan", ean: "5901234123457", scan_country: "PL" },
+ });
+ expect(result).toEqual({ ok: true, data: { scan_id: 43 } });
+ });
+
it("should return gateway error response as-is", async () => {
const gatewayError = {
ok: false,
@@ -253,6 +267,7 @@ describe("recordScanViaGateway", () => {
expect(mockRpc).toHaveBeenCalledWith("api_record_scan", {
p_ean: "5901234123457",
+ p_scan_country: null,
});
expect(result).toEqual({ ok: true, data: { scan_id: 99 } });
});
@@ -444,6 +459,8 @@ describe("submitProductViaGateway", () => {
p_category: "Chips",
p_photo_url: null,
p_notes: null,
+ p_scan_country: null,
+ p_suggested_country: null,
});
expect(result.ok).toBe(true);
});
@@ -609,7 +626,7 @@ describe("createApiGateway", () => {
const result = await gateway.recordScan("5901234123457");
expect(mockInvoke).toHaveBeenCalledWith("api-gateway", {
- body: { action: "record-scan", ean: "5901234123457" },
+ body: { action: "record-scan", ean: "5901234123457", scan_country: null },
});
expect(result).toEqual({ ok: true, data: { scan_id: 1 } });
});
diff --git a/frontend/src/lib/api-gateway.ts b/frontend/src/lib/api-gateway.ts
index 8f17ce32..3bae1fa7 100644
--- a/frontend/src/lib/api-gateway.ts
+++ b/frontend/src/lib/api-gateway.ts
@@ -120,14 +120,19 @@ async function invokeGateway(
export async function recordScanViaGateway(
supabase: SupabaseClient,
ean: string,
+ scanCountry?: string,
): Promise {
- const result = await invokeGateway(supabase, "record-scan", { ean });
+ const result = await invokeGateway(supabase, "record-scan", {
+ ean,
+ scan_country: scanCountry ?? null,
+ });
// Graceful degradation: if gateway is unreachable, fall back to direct RPC
if (!result.ok && result.error === "gateway_unreachable") {
try {
const { data, error } = await supabase.rpc("api_record_scan", {
p_ean: ean,
+ p_scan_country: scanCountry ?? null,
});
if (error) {
return {
@@ -155,6 +160,8 @@ export interface SubmitProductParams {
category?: string | null;
photo_url?: string | null;
notes?: string | null;
+ scan_country?: string | null;
+ suggested_country?: string | null;
/** Turnstile CAPTCHA token. Required when trust is low or velocity is high. */
turnstile_token?: string | null;
}
@@ -180,6 +187,8 @@ export async function submitProductViaGateway(
p_category: params.category ?? null,
p_photo_url: params.photo_url ?? null,
p_notes: params.notes ?? null,
+ p_scan_country: params.scan_country ?? null,
+ p_suggested_country: params.suggested_country ?? null,
});
if (error) {
return {
@@ -287,7 +296,7 @@ export async function saveSearchViaGateway(
// ─── Gateway Factory ────────────────────────────────────────────────────────
export interface ApiGateway {
- recordScan: (ean: string) => Promise;
+ recordScan: (ean: string, scanCountry?: string) => Promise;
submitProduct: (params: SubmitProductParams) => Promise;
trackEvent: (params: TrackEventParams) => Promise;
saveSearch: (params: SaveSearchParams) => Promise;
@@ -309,7 +318,8 @@ export interface ApiGateway {
*/
export function createApiGateway(supabase: SupabaseClient): ApiGateway {
return {
- recordScan: (ean: string) => recordScanViaGateway(supabase, ean),
+ recordScan: (ean: string, scanCountry?: string) =>
+ recordScanViaGateway(supabase, ean, scanCountry),
submitProduct: (params: SubmitProductParams) =>
submitProductViaGateway(supabase, params),
trackEvent: (params: TrackEventParams) =>
diff --git a/frontend/src/lib/api.test.ts b/frontend/src/lib/api.test.ts
index e655f6b6..6426fd55 100644
--- a/frontend/src/lib/api.test.ts
+++ b/frontend/src/lib/api.test.ts
@@ -684,11 +684,21 @@ describe("Product Comparisons API functions", () => {
describe("Scanner & Submissions API functions", () => {
beforeEach(() => vi.clearAllMocks());
- it("recordScan passes ean", async () => {
+ it("recordScan passes ean with null country by default", async () => {
mockCallRpc.mockResolvedValue({ ok: true, data: { scan_id: "s1" } });
await recordScan(fakeSupabase, "5901234123457");
expect(mockCallRpc).toHaveBeenCalledWith(fakeSupabase, "api_record_scan", {
p_ean: "5901234123457",
+ p_scan_country: null,
+ });
+ });
+
+ it("recordScan passes scan country when provided", async () => {
+ mockCallRpc.mockResolvedValue({ ok: true, data: { scan_id: "s2" } });
+ await recordScan(fakeSupabase, "5901234123457", "PL");
+ expect(mockCallRpc).toHaveBeenCalledWith(fakeSupabase, "api_record_scan", {
+ p_ean: "5901234123457",
+ p_scan_country: "PL",
});
});
@@ -722,6 +732,8 @@ describe("Scanner & Submissions API functions", () => {
p_category: null,
p_photo_url: null,
p_notes: null,
+ p_scan_country: null,
+ p_suggested_country: null,
});
});
@@ -742,6 +754,28 @@ describe("Scanner & Submissions API functions", () => {
p_category: "Chips",
p_photo_url: "https://example.com/img.jpg",
p_notes: "Found at Żabka",
+ p_scan_country: null,
+ p_suggested_country: null,
+ });
+ });
+
+ it("submitProduct passes scan and suggested country when provided", async () => {
+ mockCallRpc.mockResolvedValue({ ok: true, data: { submission_id: "sub3" } });
+ await submitProduct(fakeSupabase, {
+ ean: "123",
+ productName: "Test",
+ scanCountry: "DE",
+ suggestedCountry: "DE",
+ });
+ expect(mockCallRpc).toHaveBeenCalledWith(fakeSupabase, "api_submit_product", {
+ p_ean: "123",
+ p_product_name: "Test",
+ p_brand: null,
+ p_category: null,
+ p_photo_url: null,
+ p_notes: null,
+ p_scan_country: "DE",
+ p_suggested_country: "DE",
});
});
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 148e3c58..83e71b40 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -723,9 +723,11 @@ export function deleteComparison(
export function recordScan(
supabase: SupabaseClient,
ean: string,
+ scanCountry?: string,
): Promise> {
return callRpc(supabase, "api_record_scan", {
p_ean: ean,
+ p_scan_country: scanCountry ?? null,
});
}
@@ -751,6 +753,8 @@ export function submitProduct(
category?: string;
photoUrl?: string;
notes?: string;
+ scanCountry?: string;
+ suggestedCountry?: string;
},
): Promise> {
return callRpc(supabase, "api_submit_product", {
@@ -760,6 +764,8 @@ export function submitProduct(
p_category: params.category ?? null,
p_photo_url: params.photoUrl ?? null,
p_notes: params.notes ?? null,
+ p_scan_country: params.scanCountry ?? null,
+ p_suggested_country: params.suggestedCountry ?? null,
});
}