import { useTranslation } from "@browser-skill/i18n/react";
import { RiCloseLine } from "@remixicon/react";
import { type TransitionEvent, useEffect, useRef, useState } from "react";
import logoUrl from "../../assets/logo.png";
export interface BorrowRequestData {
id: string;
isActiveTab: boolean;
tabTitle: string;
timeoutMs: number;
onAllow: () => void;
onDeny: () => void;
}
interface Props {
requests: BorrowRequestData[];
}
const EXIT_ANIMATION_MS = 150;
export function BorrowConfirmationOverlay({ requests }: Props) {
const activeRequest = requests.find((r) => r.isActiveTab);
const inactiveRequests = requests.filter((r) => !r.isActiveTab);
return (
<>
{activeRequest && (
)}
{inactiveRequests.length > 0 && (
{inactiveRequests.map((req) => (
))}
)}
>
);
}
function BorrowBackdrop({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
function BorrowToastStack({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
/** Matches BorrowProgressBar / CountdownRing transition duration (duration-1000). */
const PROGRESS_TRANSITION_MS = 1000;
function BorrowRequestItem({ request, isModal }: { request: BorrowRequestData; isModal: boolean }) {
const { t } = useTranslation("extension");
const totalSeconds = Math.ceil(request.timeoutMs / 1000);
const [secondsLeft, setSecondsLeft] = useState(totalSeconds);
const [exiting, setExiting] = useState(false);
const [awaitingAutoDeny, setAwaitingAutoDeny] = useState(false);
const settledRef = useRef(false);
const onAllowRef = useRef(request.onAllow);
const onDenyRef = useRef(request.onDeny);
onAllowRef.current = request.onAllow;
onDenyRef.current = request.onDeny;
function triggerAllow() {
if (settledRef.current) return;
settledRef.current = true;
setAwaitingAutoDeny(false);
setExiting(true);
setTimeout(() => onAllowRef.current(), EXIT_ANIMATION_MS);
}
function handleAllow() {
triggerAllow();
}
function handleDeny() {
if (settledRef.current) return;
settledRef.current = true;
setAwaitingAutoDeny(false);
setExiting(true);
setTimeout(() => onDenyRef.current(), EXIT_ANIMATION_MS);
}
useEffect(() => {
if (secondsLeft <= 0) {
if (!settledRef.current) {
setAwaitingAutoDeny(true);
}
return;
}
const id = setTimeout(() => setSecondsLeft((s) => s - 1), 1000);
return () => clearTimeout(id);
}, [secondsLeft]);
// Prefer transitionend for visual sync, but fall back to a timer so
// background tabs / suppressed CSS transitions still auto-deny.
useEffect(() => {
if (!awaitingAutoDeny || settledRef.current) return;
const id = setTimeout(() => {
handleDeny();
}, PROGRESS_TRANSITION_MS);
return () => clearTimeout(id);
}, [awaitingAutoDeny]);
const progress = secondsLeft / totalSeconds;
const truncatedTitle =
request.tabTitle.length > 30 ? `${request.tabTitle.slice(0, 30)}…` : request.tabTitle;
const cardClass = `transition-opacity duration-150 ease-out ${exiting ? "opacity-0" : "opacity-100"}`;
if (isModal) {
return (
{t("borrowConfirmation.title")}
{t("borrowConfirmation.body")}
{
if (!awaitingAutoDeny || secondsLeft !== 0) return;
if (propertyName !== "stroke-dashoffset") return;
handleDeny();
}}
/>
{t("borrowConfirmation.autoDeny", { count: secondsLeft })}
);
}
return (
{t("borrowConfirmation.targetTab")}
"{truncatedTitle}"
{
if (!awaitingAutoDeny || secondsLeft !== 0) return;
if (event.propertyName !== "width") return;
handleDeny();
}}
/>
);
}
function BorrowToastHeader({ onDeny }: { onDeny: () => void }) {
const { t } = useTranslation("extension");
return (
{t("borrowConfirmation.titleInactive")}
);
}
function BorrowProgressBar({
progress,
onProgressTransitionEnd,
}: {
progress: number;
onProgressTransitionEnd?: (event: TransitionEvent) => void;
}) {
return (
);
}
function ActionButtons({
onDeny,
onAllow,
gapClass = "gap-2.5",
}: {
onDeny: () => void;
onAllow: () => void;
gapClass?: string;
}) {
const { t } = useTranslation("extension");
return (
);
}
function CountdownRing({
progress,
seconds,
onProgressTransitionEnd,
}: {
progress: number;
seconds: number;
onProgressTransitionEnd?: (propertyName: string) => void;
}) {
const r = 28;
const circ = 2 * Math.PI * r;
const offset = circ * (1 - progress);
return (
);
}