update session expiration dialog texts, add monitor aware position

This commit is contained in:
Eduard Gert
2026-06-08 18:09:22 +02:00
parent b067544c8a
commit 6b2ae1c34c
19 changed files with 321 additions and 226 deletions
@@ -1,20 +1,16 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { Events } from "@wailsio/runtime";
import { errorDialog } from "@/lib/dialogs.ts";
import { ClockIcon } from "lucide-react";
import { AlertCircleIcon, ClockIcon } from "lucide-react";
import { Button } from "@/components/buttons/Button";
import { ConfirmDialog } from "@/components/dialog/ConfirmDialog";
import { DialogActions } from "@/components/dialog/DialogActions";
import { DialogDescription } from "@/components/dialog/DialogDescription";
import { DialogHeading } from "@/components/dialog/DialogHeading";
import { SquareIcon } from "@/components/SquareIcon";
import {
Connection,
Profiles as ProfilesSvc,
Session,
WindowManager,
} from "@bindings/services";
import { Connection, Profiles as ProfilesSvc, Session, WindowManager } from "@bindings/services";
import { useAutoSizeWindow } from "@/hooks/useAutoSizeWindow";
import { formatErrorMessage } from "@/lib/errors.ts";
@@ -40,7 +36,7 @@ function formatRemaining(seconds: number): string {
return `${pad(minutes)}:${pad(secs)}`;
}
export default function SessionAboutToExpireDialog() {
export default function SessionExpirationDialog() {
const { t } = useTranslation();
const contentRef = useAutoSizeWindow<HTMLDivElement>(WINDOW_WIDTH);
const [params] = useSearchParams();
@@ -68,6 +64,20 @@ export default function SessionAboutToExpireDialog() {
return () => window.clearInterval(id);
}, [remaining]);
// Auto-close when the daemon flips back to Connected — covers extend
// flows started from outside this window (tray notification action,
// another UI surface) so the user isn't left staring at a stale dialog.
useEffect(() => {
const off = Events.On("netbird:status", (ev: { data: { status?: string } }) => {
if (ev?.data?.status === "Connected") {
WindowManager.CloseSessionExpiration().catch(console.error);
}
});
return () => {
off();
};
}, []);
// Mirrors tray.go::runExtendSession: starts the daemon SSO extend flow,
// opens the browser for the user to sign in, blocks on the daemon until
// the new deadline arrives. Tunnel stays up; success simply closes the
@@ -100,10 +110,10 @@ export default function SessionAboutToExpireDialog() {
// relevant.
return;
}
WindowManager.CloseSessionAboutToExpire().catch(console.error);
WindowManager.CloseSessionExpiration().catch(console.error);
} catch (e) {
await errorDialog({
Title: t("sessionAboutToExpire.extendFailedTitle"),
Title: t("sessionExpiration.extendFailedTitle"),
Message: formatErrorMessage(e),
});
} finally {
@@ -121,10 +131,10 @@ export default function SessionAboutToExpireDialog() {
profileName: active.profileName || "default",
username,
});
WindowManager.CloseSessionAboutToExpire().catch(console.error);
WindowManager.CloseSessionExpiration().catch(console.error);
} catch (e) {
await errorDialog({
Title: t("sessionAboutToExpire.logoutFailedTitle"),
Title: t("sessionExpiration.logoutFailedTitle"),
Message: formatErrorMessage(e),
});
} finally {
@@ -132,33 +142,41 @@ export default function SessionAboutToExpireDialog() {
}
}, [busy, t]);
const close = useCallback(() => {
WindowManager.CloseSessionExpiration().catch(console.error);
}, []);
return (
<ConfirmDialog ref={contentRef}>
<SquareIcon icon={ClockIcon} />
<SquareIcon icon={expired ? AlertCircleIcon : ClockIcon} />
<div className={"flex flex-col items-center gap-1"}>
<DialogHeading>
{expired
? t("sessionAboutToExpire.expired")
? t("sessionExpiration.expired")
: soon
? t("sessionAboutToExpire.title")
: t("sessionAboutToExpire.titleLater")}
? t("sessionExpiration.title")
: t("sessionExpiration.titleLater")}
</DialogHeading>
<DialogDescription>
{soon
? t("sessionAboutToExpire.description")
: t("sessionAboutToExpire.descriptionLater")}
{expired
? t("sessionExpiration.expiredDescription")
: soon
? t("sessionExpiration.description")
: t("sessionExpiration.descriptionLater")}
</DialogDescription>
</div>
<div
className={
"font-mono font-semibold text-2xl tabular-nums text-nb-gray-50 tracking-wider"
}
aria-live={"polite"}
>
{formatRemaining(remaining)}
</div>
{!expired && (
<div
className={
"font-mono font-semibold text-2xl tabular-nums text-nb-gray-50 tracking-wider"
}
aria-live={"polite"}
>
{formatRemaining(remaining)}
</div>
)}
<DialogActions>
<Button
@@ -167,18 +185,20 @@ export default function SessionAboutToExpireDialog() {
size={"md"}
className={"w-full"}
onClick={stay}
disabled={expired || busy}
disabled={busy}
>
{t("sessionAboutToExpire.stay")}
{expired
? t("sessionExpiration.authenticate")
: t("sessionExpiration.stay")}
</Button>
<Button
variant={"secondary"}
size={"md"}
className={"w-full"}
onClick={logout}
onClick={expired ? close : logout}
disabled={busy}
>
{t("sessionAboutToExpire.logout")}
{expired ? t("sessionExpiration.close") : t("sessionExpiration.logout")}
</Button>
</DialogActions>
</ConfirmDialog>
@@ -1,55 +0,0 @@
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { Events } from "@wailsio/runtime";
import { AlertCircleIcon } from "lucide-react";
import { Button } from "@/components/buttons/Button";
import { ConfirmDialog } from "@/components/dialog/ConfirmDialog";
import { DialogActions } from "@/components/dialog/DialogActions";
import { DialogDescription } from "@/components/dialog/DialogDescription";
import { DialogHeading } from "@/components/dialog/DialogHeading";
import { SquareIcon } from "@/components/SquareIcon";
import { WindowManager } from "@bindings/services";
import { useAutoSizeWindow } from "@/hooks/useAutoSizeWindow";
const EVENT_TRIGGER_LOGIN = "trigger-login";
const WINDOW_WIDTH = 360;
export default function SessionExpiredDialog() {
const { t } = useTranslation();
const contentRef = useAutoSizeWindow<HTMLDivElement>(WINDOW_WIDTH);
const signIn = useCallback(() => {
void Events.Emit(EVENT_TRIGGER_LOGIN);
WindowManager.CloseSessionExpired().catch(console.error);
}, []);
const later = useCallback(() => {
WindowManager.CloseSessionExpired().catch(console.error);
}, []);
return (
<ConfirmDialog ref={contentRef}>
<SquareIcon icon={AlertCircleIcon} />
<div className={"flex flex-col items-center gap-1"}>
<DialogHeading>{t("sessionExpired.title")}</DialogHeading>
<DialogDescription>{t("sessionExpired.description")}</DialogDescription>
</div>
<DialogActions>
<Button
autoFocus
variant={"primary"}
size={"md"}
className={"w-full"}
onClick={signIn}
>
{t("sessionExpired.signIn")}
</Button>
<Button variant={"secondary"} size={"md"} className={"w-full"} onClick={later}>
{t("sessionExpired.later")}
</Button>
</DialogActions>
</ConfirmDialog>
);
}