refactor: improve ephemeral ux

This commit is contained in:
zhom
2026-08-16 17:22:43 +04:00
parent 1a36fb9c12
commit a0175eab0d
17 changed files with 667 additions and 110 deletions
+83 -29
View File
@@ -29,6 +29,7 @@ import {
LuSettings,
LuShield,
LuShieldCheck,
LuTimer,
LuTrash2,
LuUpload,
LuUsers,
@@ -735,6 +736,13 @@ function ProfileInfoLayout({
[visibleActions],
);
// An ephemeral profile is discarded when the browser closes, so it has
// nowhere to keep cookies, extensions or a synced copy. The sections were
// hidden outright, which left no way to discover that and read as the app
// being broken or the plan lacking the feature. Keep them listed and explain.
const isEphemeral = profile.ephemeral === true;
const isWayfernProfile = profile.browser === "wayfern";
const deleteAction = findAction("delete");
const fingerprintAction = findAction("fingerprint");
const cookiesManageAction = findAction("cookiesManage");
@@ -814,20 +822,20 @@ function ProfileInfoLayout({
cookieCount !== null && cookieCount > 0
? cookieCount.toLocaleString()
: undefined,
hidden: !cookiesAction,
hidden: !cookiesAction && !(isEphemeral && isWayfernProfile),
},
{
id: "extensions",
icon: <LuPuzzle className="size-3.5" />,
label: t("profileInfo.sections.extensions"),
badge: extensionGroupName ?? undefined,
hidden: !extensionAction,
hidden: !extensionAction && !isEphemeral,
},
{
id: "sync",
icon: <LuRefreshCw className="size-3.5" />,
label: t("profileInfo.sections.sync"),
hidden: !syncAction,
hidden: !syncAction && !isEphemeral,
},
{
id: "automation",
@@ -1073,34 +1081,55 @@ function ProfileInfoLayout({
/>
)}
{section === "cookies" && (
<CookiesSectionInline
profile={profile}
isRunning={isRunning}
isDisabled={isDisabled}
onCopyCookies={cookiesCopyAction?.onClick}
onImportCookies={cookiesManageAction?.onClick}
t={t}
/>
)}
{section === "cookies" &&
(isEphemeral ? (
<EphemeralSectionNotice
title={t("profileInfo.sections.cookies")}
description={t("profileInfo.ephemeral.cookiesUnavailable")}
t={t}
/>
) : (
<CookiesSectionInline
profile={profile}
isRunning={isRunning}
isDisabled={isDisabled}
onCopyCookies={cookiesCopyAction?.onClick}
onImportCookies={cookiesManageAction?.onClick}
t={t}
/>
))}
{section === "extensions" && (
<ExtensionsSectionInline
profile={profile}
isDisabled={isDisabled}
t={t}
/>
)}
{section === "extensions" &&
(isEphemeral ? (
<EphemeralSectionNotice
title={t("profileInfo.sections.extensions")}
description={t("profileInfo.ephemeral.extensionsUnavailable")}
t={t}
/>
) : (
<ExtensionsSectionInline
profile={profile}
isDisabled={isDisabled}
t={t}
/>
))}
{section === "sync" && (
<SyncSectionInline
profile={profile}
syncMode={syncMode}
syncStatus={syncStatus}
isDisabled={isDisabled}
t={t}
/>
)}
{section === "sync" &&
(isEphemeral ? (
<EphemeralSectionNotice
title={t("profileInfo.sections.sync")}
description={t("profileInfo.ephemeral.syncUnavailable")}
t={t}
/>
) : (
<SyncSectionInline
profile={profile}
syncMode={syncMode}
syncStatus={syncStatus}
isDisabled={isDisabled}
t={t}
/>
))}
{section === "automation" && (
<LaunchHookEditor profile={profile} t={t} />
@@ -1507,6 +1536,31 @@ function NetworkSectionInline({
);
}
/// Explains why a section has nothing to offer on an ephemeral profile.
/// Mirrors the locked-fingerprint empty state so the two read as one pattern.
function EphemeralSectionNotice({
title,
description,
t,
}: {
title: string;
description: string;
t: (key: string, options?: Record<string, unknown>) => string;
}) {
return (
<div className="flex flex-col items-center gap-3 rounded-lg border p-6 text-center">
<LuTimer className="size-4 shrink-0 text-muted-foreground" />
<h3 className="text-sm font-medium text-foreground">{title}</h3>
<p className="max-w-[48ch] text-sm text-pretty text-muted-foreground">
{description}
</p>
<p className="max-w-[48ch] text-xs text-pretty text-muted-foreground">
{t("profileInfo.ephemeral.hint")}
</p>
</div>
);
}
function ExtensionsSectionInline({
profile,
isDisabled,