fix(inbox): a Takeout import's notice opens the user's own import

The import's notice reaches a user who may not see the Scheduler, an
admin page, yet its link opened it. It now opens Settings › Account,
where the import's status is; any other run still opens the Scheduler.
The job id is held to the server's by the backend-ids contract. The
notice's Hungarian names the list as the app does elsewhere.
This commit is contained in:
2026-10-23 19:15:06 +02:00
parent 3553eac8c4
commit e0b02a0c14
8 changed files with 88 additions and 23 deletions
@@ -11,6 +11,7 @@ import {
ExternalLink,
Eye,
FolderOpen,
History,
KeyRound,
RotateCcw,
Search,
@@ -19,7 +20,7 @@ import {
UserPlus,
X,
} from "lucide-react";
import { api, type AppNotification } from "../../lib/api";
import { api, TAKEOUT_JOB, type AppNotification } from "../../lib/api";
import { jobRunText } from "../../lib/jobResult";
import { notifyActionError } from "../../lib/actionErrors";
import { useLiveQuery } from "../../lib/useLiveQuery";
@@ -192,6 +193,10 @@ export default function NotificationsPanel() {
focusSettingsTab("tokens");
setPage("settings");
};
const openTakeout = () => {
focusSettingsTab("account");
setPage("settings");
};
// A finished download opens on its file, in its folder; a notice from before it named one, on Files.
const openDone = (n: AppNotification) => {
const { job_id: id, folder_id: folder } = n.data ?? {};
@@ -213,34 +218,43 @@ export default function NotificationsPanel() {
const kind = serverKind(n);
return menuGroups(
[
...(kind === "scheduler"
...(kind === "takeout"
? [
{
id: "open",
label: t("inbox.openScheduler"),
icon: icon(Activity),
onSelect: openScheduler,
label: t("inbox.openTakeout"),
icon: icon(History),
onSelect: openTakeout,
},
]
: kind === "download"
: kind === "scheduler"
? [
{
id: "open",
label: t("inbox.openFile"),
icon: icon(FolderOpen),
onSelect: () => openDone(n),
label: t("inbox.openScheduler"),
icon: icon(Activity),
onSelect: openScheduler,
},
]
: kind === "token"
: kind === "download"
? [
{
id: "open",
label: t("inbox.openTokens"),
icon: icon(KeyRound),
onSelect: openTokens,
label: t("inbox.openFile"),
icon: icon(FolderOpen),
onSelect: () => openDone(n),
},
]
: []),
: kind === "token"
? [
{
id: "open",
label: t("inbox.openTokens"),
icon: icon(KeyRound),
onSelect: openTokens,
},
]
: []),
...(!n.read
? [
{
@@ -411,6 +425,7 @@ export default function NotificationsPanel() {
onRead={() => readMut.mutate(n.id)}
onDismiss={() => dismissMut.mutate(n.id)}
onOpenScheduler={openScheduler}
onOpenTakeout={openTakeout}
onOpenFile={() => openDone(n)}
onOpenTokens={openTokens}
/>
@@ -478,7 +493,9 @@ const DONE_TITLE: Record<string, string | undefined> = {
upload: "inbox.uploadDone.title",
};
function serverKind(n: AppNotification): "scheduler" | "download" | "token" | null {
function serverKind(n: AppNotification): "scheduler" | "takeout" | "download" | "token" | null {
// A Takeout import's run is the user's own: told like any run, opened where they started it.
if (n.type === "scheduler" && n.data?.job_id === TAKEOUT_JOB) return "takeout";
if (n.type === "scheduler" && typeof n.data?.job_id === "string") return "scheduler";
if (n.type === "download_done") return "download";
if (n.type === "api_token_expiring" && typeof n.data?.name === "string") return "token";
@@ -527,6 +544,7 @@ function NotificationRow({
onRead,
onDismiss,
onOpenScheduler,
onOpenTakeout,
onOpenFile,
onOpenTokens,
}: {
@@ -538,6 +556,7 @@ function NotificationRow({
onRead: () => void;
onDismiss: () => void;
onOpenScheduler: () => void;
onOpenTakeout: () => void;
onOpenFile: () => void;
onOpenTokens: () => void;
}) {
@@ -549,7 +568,8 @@ function NotificationRow({
// Known notification types are rendered from i18n (so they're trilingual) using the typed
// payload; the server-stored title/body are an English fallback for any unknown type.
const isMaintenance = n.type === "maintenance" && typeof n.data?.count === "number";
const isScheduler = serverKind(n) === "scheduler";
const isTakeout = serverKind(n) === "takeout";
const isScheduler = serverKind(n) === "scheduler" || isTakeout;
const isDownloadDone = serverKind(n) === "download";
const isTokenExpiring = serverKind(n) === "token";
const isEmailUndelivered = n.type === "email_undelivered" && typeof n.data?.reason === "string";
@@ -633,10 +653,12 @@ function NotificationRow({
variant="link"
className="text-sm mt-1.5"
data-nav-open=""
onClick={onOpenScheduler}
icon={<Activity className="w-3.5 h-3.5" />}
onClick={isTakeout ? onOpenTakeout : onOpenScheduler}
icon={
isTakeout ? <History className="w-3.5 h-3.5" /> : <Activity className="w-3.5 h-3.5" />
}
>
{t("inbox.openScheduler")}
{t(isTakeout ? "inbox.openTakeout" : "inbox.openScheduler")}
</Button>
)}
{isTokenExpiring && (