Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | 5x 7x 5x 73x 7x 73x 73x 17x 17x 73x 73x 73x 7x 7x 7x 7x 73x 73x 62x 58x 73x | import { customInstance } from "@services/api";
import { AxiosError } from "axios";
import { useCallback, useEffect, useRef } from "react";
import { useMutation } from "react-query";
import {
MATCH_TRANSLATION_MAX_ATTEMPTS,
matchTranslationUrl,
} from "../matchTranslation.constants";
// Response contract for POST /merchants/{id}/mastercard-match/translation
// (Story 1 / BE). The backend proxies OpenAI, persists the result keyed to the
// MATCH request, and returns the stored summary on repeat calls.
export interface MatchTranslationResponse {
summary: string;
}
export type MatchTranslationStatus = "idle" | "loading" | "success" | "error";
interface UseMatchTranslationArgs {
merchantID: number;
// The Mastercard termination-inquiry JSON to translate. Undefined while the
// live inquiry is still loading, on error, or when there is no result.
matchResult?: Record<string, unknown>;
// A summary already persisted for this MATCH (surfaced on saved reports via
// the report read-back). When present the panel shows it with no new call.
existingSummary?: string | null;
}
export interface UseMatchTranslationResult {
status: MatchTranslationStatus;
summary?: string;
translate: () => void;
canTranslate: boolean;
canRetry: boolean;
}
const generateMatchTranslation = async (
merchantID: number,
matchResult: Record<string, unknown>,
): Promise<MatchTranslationResponse | null> => {
return customInstance({
url: matchTranslationUrl(merchantID),
method: "POST",
data: { matchResult },
});
};
export const useMatchTranslation = ({
merchantID,
matchResult,
existingSummary,
}: UseMatchTranslationArgs): UseMatchTranslationResult => {
// react-query's own retry is disabled; retries are user-initiated and
// bounded by MATCH_TRANSLATION_MAX_ATTEMPTS (AC011).
const mutation = useMutation<
MatchTranslationResponse | null,
AxiosError,
Record<string, unknown>
>((payload) => generateMatchTranslation(merchantID, payload), {
retry: false,
});
const attemptsRef = useRef(0);
// When the underlying MATCH result changes (the live inquiry re-fires with a
// new ref on every open, or the user reruns the check), clear any prior
// error/success and restore the retry budget — otherwise, once the bounded
// attempts are exhausted against one result, the panel stays stuck in its
// error state and the new result can never be translated. `mutation.reset` is
// bound to a stable observer, so a change of `matchResult` is the only trigger
// we need to depend on.
useEffect(() => {
attemptsRef.current = 0;
mutation.reset();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [matchResult]);
const generatedSummary = mutation.data?.summary;
const summary = existingSummary ?? generatedSummary ?? undefined;
const translate = useCallback(() => {
// AC005 — never re-request once a summary exists; AC011 — cap attempts.
Iif (!matchResult || summary) return;
Iif (attemptsRef.current >= MATCH_TRANSLATION_MAX_ATTEMPTS) return;
attemptsRef.current += 1;
mutation.mutate(matchResult);
}, [matchResult, summary, mutation]);
let status: MatchTranslationStatus = "idle";
if (mutation.isLoading) status = "loading";
else if (summary) status = "success";
else if (mutation.isError) status = "error";
return {
status,
summary,
translate,
canTranslate: !!matchResult && !summary,
canRetry: attemptsRef.current < MATCH_TRANSLATION_MAX_ATTEMPTS,
};
};
|