fix(ancpi): UAT debounce + list tooltips + expired download + ePay retry

1. UAT search: 150ms debounce prevents slow re-renders on keystroke
2. Lista mea tooltips: "Scoate Extrase CF" shows exact credit cost,
   status badges show expiry dates and clear instructions
3. Expired extracts: both Descarcă (old version) + Actualizează shown
4. ePay auto-connect: retry 2x with 3s delay, check session before
   connect, re-attempt on disconnect detection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 12:14:34 +02:00
parent 5a6ab36aa7
commit 62777e9778
3 changed files with 216 additions and 92 deletions
@@ -47,6 +47,7 @@ export function EpayConnect({
const cbRef = useRef(onStatusChange);
cbRef.current = onStatusChange;
const autoConnectAttempted = useRef(false);
const autoConnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const fetchStatus = useCallback(async () => {
try {
@@ -55,15 +56,23 @@ export function EpayConnect({
setStatus(data);
cbRef.current?.(data);
if (data.connected) setError("");
return data;
} catch {
/* silent */
return null;
}
}, []);
// Poll every 30s
// Poll every 30s — detect disconnection and allow re-connect
useEffect(() => {
void fetchStatus();
pollRef.current = setInterval(() => void fetchStatus(), 30_000);
pollRef.current = setInterval(() => {
void fetchStatus().then((data) => {
if (data && !data.connected && autoConnectAttempted.current) {
autoConnectAttempted.current = false;
}
});
}, 30_000);
return () => {
if (pollRef.current) clearInterval(pollRef.current);
};
@@ -92,13 +101,70 @@ export function EpayConnect({
}
}, [connecting, status.connected, fetchStatus]);
// Auto-connect when triggerConnect becomes true
// Auto-connect when triggerConnect becomes true, with retry on failure
useEffect(() => {
if (triggerConnect && !status.connected && !connecting && !autoConnectAttempted.current) {
autoConnectAttempted.current = true;
void connect();
}
}, [triggerConnect, status.connected, connecting, connect]);
if (!triggerConnect || status.connected || connecting || autoConnectAttempted.current) return;
autoConnectAttempted.current = true;
let cancelled = false;
const maxRetries = 2;
const attemptConnect = async (attempt: number) => {
if (cancelled) return;
// On first attempt, check session to avoid unnecessary connect
if (attempt === 0) {
const current = await fetchStatus();
if (cancelled) return;
if (current?.connected) return;
}
setConnecting(true);
setError("");
let shouldRetry = false;
try {
const res = await fetch("/api/ancpi/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
});
const data = (await res.json()) as { success?: boolean; credits?: number; error?: string };
if (cancelled) return;
if (!res.ok || data.error) {
setError(data.error ?? "Eroare conectare ePay");
shouldRetry = attempt < maxRetries;
} else {
await fetchStatus();
}
} catch {
if (cancelled) return;
setError("Eroare retea");
shouldRetry = attempt < maxRetries;
}
if (cancelled) return;
if (shouldRetry) {
// Keep connecting state true during retry wait
autoConnectTimerRef.current = setTimeout(() => {
void attemptConnect(attempt + 1);
}, 3000);
} else {
setConnecting(false);
}
};
void attemptConnect(0);
return () => {
cancelled = true;
if (autoConnectTimerRef.current) {
clearTimeout(autoConnectTimerRef.current);
autoConnectTimerRef.current = null;
}
};
}, [triggerConnect, status.connected, connecting, fetchStatus]);
const disconnect = async () => {
try {
@@ -108,6 +174,10 @@ export function EpayConnect({
body: JSON.stringify({ action: "disconnect" }),
});
autoConnectAttempted.current = false;
if (autoConnectTimerRef.current) {
clearTimeout(autoConnectTimerRef.current);
autoConnectTimerRef.current = null;
}
await fetchStatus();
} catch {
/* silent */